From fd9443871dc21a5e47b8e1e50a6fd5f5dabd90c8 Mon Sep 17 00:00:00 2001 From: Mike Samuel Date: Fri, 26 Jun 2026 13:30:44 -0600 Subject: [PATCH 1/6] frontend: internal representation of method calls skips separate bind step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, method calls were represented using separate bind and call steps. There's something elegant to treating complex calls like the below as multiple distinct steps. subject.method(arg0, arg1) ┣━━━━━┛ ┃ ┃ ┃ ┃SUBJECT ┃ ┃ ┃ ┣━━━━━━━━━━━━┛ ┃ ┃ ┃ BOUND METHOD ┃ ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ BOUND AND PARAMETERIZED METHOD ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ COMPLETE METHOD CALL It was convenient to type each part separately. - The subject has type `MyClass` - Imagine, the method, pre-binding to a subject, has type `(MyClass) -> (U, U) -> Foo`, so when bound to `MyClass` the *T* binds to *Bar* and the overall type for the bound method has type `(U, U) -> Foo` - Since there are explicit type parameters, the bound and parameterized method has type `(TypeActual, TypeActual) -> Foo` - Then the method call has type `Foo`. That only works if you can represent calls to overloaded methods as one type, an intersection (`&`) type of function types. Note also that the type of the bound method is a function type that returns a function type. There's currying going on. Code reasoning about this has to repeatedly pack and unpack intersection types and curry and uncurry type info. Translating intersection types is hard; most typed languages (TypeScript being an exception) do not allow user's to explicitly specify intersection types, though many use them under the hood. Also, reasoning about whole calls is more common than reasoning about the parts above (again, outside of the Typer), so many parts of the frontend and the TmpL translator were complicated by the need to look through calls so that the could reassemble full argument lists (subject and args) across two calls. Ongoing changes to the internal representation of types and to type solving made it easier to sequester overload handling to a small portion of the *Typer* and *TypeSolver*, obviating the need for intersection types, and the need for union types (previously used for nullable type representation and unions of success and failure types in result types). This commit reworks *DotHelper*; instead of *BindMemberAccessor* specifying bind semantics, now *CallMemberAccessor* specifies full call semantics. There is a lot of cleanup and reworking of *Typer* internally to type the new representation, and cleanup of existing code that produces method calls to produce the newer, flatter calls. A lot of this reworking of the typer prefigures work that will be necessary to use *Type2* and *Signature* throughout instead of the old *StaticType* representations. Signed-off-by: Mike Samuel --- .../lang/temper/be/tmpl/TmpLTranslator.kt | 13 - .../lang/temper/be/tmpl/TranslateDotHelper.kt | 87 ++-- .../lang/temper/builtin/RegexLiteralMacro.kt | 10 +- .../lang/temper/builtin/StringExprMacro.kt | 48 +- docs/for-users/.snippet-hashes.json | 6 +- .../temper-docs/docs/reference/builtins.md | 6 +- .../temper/frontend/CleanupTemporaries.kt | 10 +- .../temper/frontend/GeneratorHelperFns.kt | 16 +- .../temper/frontend/MaybeAdjustDotHelper.kt | 69 ++- .../kotlin/lang/temper/frontend/Weaver.kt | 6 +- .../OptimizeContextualAutoescapingBlocks.kt | 158 +++--- .../temper/frontend/generate/TypeChecker.kt | 14 +- .../temper/frontend/json/JsonInteropPass.kt | 90 ++-- .../frontend/syntax/DotOperationDesugarer.kt | 36 +- .../typestage/BindMethodTypeHelper.kt | 59 --- .../typestage/FilterOutMaskedMembers.kt | 198 ++++++++ .../InlineToRepairUnrealizedGoals.kt | 27 +- .../frontend/typestage/SimplifyDotHelper.kt | 63 +-- .../lang/temper/frontend/typestage/Typer.kt | 448 +++++++++--------- .../temper/frontend/CleanupTemporariesTest.kt | 88 ++-- .../lang/temper/frontend/DefineStageTest.kt | 172 +++---- .../temper/frontend/GenerateCodeStageTest.kt | 252 +++++----- .../temper/frontend/SyntaxMacroStageTest.kt | 21 +- .../lang/temper/frontend/TypeStageTest.kt | 295 ++++++------ .../frontend/typestage/TyperPlanTest.kt | 49 +- .../temper/frontend/typestage/TyperTest.kt | 162 ++++++- .../kotlin/lang/temper/type/DotHelper.kt | 105 +--- .../kotlin/lang/temper/type/MemberShape.kt | 14 +- .../kotlin/lang/temper/type/Types.kt | 26 + .../lang/temper/type2/TypeBindingMapper.kt | 17 + .../kotlin/lang/temper/type2/UntypedCall.kt | 3 +- .../kotlin/lang/temper/value/PseudoCode.kt | 169 ++++--- .../kotlin/lang/temper/type/StaticTypeTest.kt | 22 + .../lang/temper/value/PseudoCodeTest.kt | 57 ++- .../lang/temper/interp/InterpreterTest.kt | 56 +-- 35 files changed, 1582 insertions(+), 1290 deletions(-) delete mode 100644 frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/BindMethodTypeHelper.kt diff --git a/be/src/commonMain/kotlin/lang/temper/be/tmpl/TmpLTranslator.kt b/be/src/commonMain/kotlin/lang/temper/be/tmpl/TmpLTranslator.kt index 52378c47..901db06b 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/tmpl/TmpLTranslator.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/tmpl/TmpLTranslator.kt @@ -58,7 +58,6 @@ import lang.temper.name.SourceName import lang.temper.name.Symbol import lang.temper.name.TemperName import lang.temper.name.Temporary -import lang.temper.type.BindMemberAccessor import lang.temper.type.DotHelper import lang.temper.type.MethodKind import lang.temper.type.MethodShape @@ -1763,15 +1762,6 @@ class TmpLTranslator internal constructor( translateDotHelperCall( tree, callee = effectiveCallee, - outerCallTree = null, - typeActuals = typeActuals, - ) - } else if (dotHelperFromCallOrNull(effectiveCallee)?.memberAccessor is BindMemberAccessor) { - check(effectiveCallee is CallTree) - translateDotHelperCall( - effectiveCallee, - callee = effectiveCallee.child(0), - outerCallTree = tree, typeActuals = typeActuals, ) } else { @@ -2790,20 +2780,17 @@ class TmpLTranslator internal constructor( translateDotHelperCall( callTree, callTree.child(0), - null, emptyList(), ) private fun translateDotHelperCall( callTree: CallTree, callee: Tree, - outerCallTree: CallTree?, typeActuals: List, ): StmtOrExpr { val dotTranslation = TranslateDotHelper.translate( callTree = callTree, callee = callee, - outerCallTree = outerCallTree, typeActuals = typeActuals, translator = this, ) diff --git a/be/src/commonMain/kotlin/lang/temper/be/tmpl/TranslateDotHelper.kt b/be/src/commonMain/kotlin/lang/temper/be/tmpl/TranslateDotHelper.kt index 03215c5a..1c41040b 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/tmpl/TranslateDotHelper.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/tmpl/TranslateDotHelper.kt @@ -3,17 +3,16 @@ package lang.temper.be.tmpl import lang.temper.common.Either import lang.temper.common.mapFirst import lang.temper.common.subListToEnd -import lang.temper.frontend.typestage.BindMethodTypeHelper import lang.temper.log.Position import lang.temper.name.ResolvedName -import lang.temper.type.BindMemberAccessor +import lang.temper.type.CallMemberAccessor import lang.temper.type.DotHelper import lang.temper.type.DotMember -import lang.temper.type.ExternalBind +import lang.temper.type.ExternalCall import lang.temper.type.ExternalGet import lang.temper.type.ExternalSet import lang.temper.type.GetMemberAccessor -import lang.temper.type.InternalBind +import lang.temper.type.InternalCall import lang.temper.type.InternalGet import lang.temper.type.InternalMemberAccessor import lang.temper.type.InternalSet @@ -36,7 +35,6 @@ import lang.temper.type2.Nullity import lang.temper.type2.Signature2 import lang.temper.type2.Type2 import lang.temper.type2.TypeParamRef -import lang.temper.type2.hackMapNewStyleToOld import lang.temper.type2.hackMapOldStyleToNew import lang.temper.type2.hackTryStaticTypeToSig import lang.temper.type2.withNullity @@ -78,9 +76,8 @@ internal object TranslateDotHelper { // we need either the getter or the setter. val method = it as? MethodShape val appropriate = when (fn.memberAccessor) { - is GetMemberAccessor, is BindMemberAccessor -> - method?.methodKind != MethodKind.Setter - + is GetMemberAccessor -> method?.methodKind == MethodKind.Getter + is CallMemberAccessor -> method?.methodKind == MethodKind.Normal is SetMemberAccessor -> method?.methodKind == MethodKind.Setter } if (appropriate) { @@ -161,12 +158,10 @@ internal object TranslateDotHelper { fun translate( callTree: CallTree, callee: Tree, - /** For a call to a bound method, has the non-subject arguments. */ - outerCallTree: CallTree?, typeActuals: List, translator: TmpLTranslator, ): TranslatedDotHelper { - val pos = (outerCallTree ?: callTree).pos + val pos = callTree.pos val calleePos = callee.pos val dotHelper = callee.functionContained as DotHelper val dotMember = dotHelper.member @@ -178,7 +173,7 @@ internal object TranslateDotHelper { ) } - val callType = (outerCallTree ?: callTree).typeOrInvalid + val callType = callTree.typeOrInvalid val pool = translator.pool val subjectIndexInCallTree = 1 + dotHelper.memberAccessor.firstArgumentIndex @@ -191,21 +186,9 @@ internal object TranslateDotHelper { ?.get(it.name as ResolvedName) } - val declaredCalleeType: Signature2? - val actualCalleeType: Signature2? - if (outerCallTree == null) { - declaredCalleeType = hackTryStaticTypeToSig(callTree.typeInferences?.variant) - actualCalleeType = hackTryStaticTypeToSig(callTree.childOrNull(0)?.typeInferences?.type) - } else if (dotHelper.memberAccessor is BindMemberAccessor) { - val innerCallee = callTree.childOrNull(0) + val declaredCalleeType: Signature2? = firstMember?.descriptor + val actualCalleeType: Signature2? = hackTryStaticTypeToSig(callTree.childOrNull(0)?.typeInferences?.type) - actualCalleeType = innerCallee?.typeInferences?.type?.let { - hackTryStaticTypeToSig(BindMethodTypeHelper.uncurry(it)) - } - declaredCalleeType = firstMember?.descriptor - } else { - TODO(callTree.toLispy()) - } fun translatedExpr(e: TmpL.Expression) = TranslatedDotHelper( Either.Left(e), actualCalleeType = actualCalleeType, @@ -219,24 +202,16 @@ internal object TranslateDotHelper { adjustments = adjustments, ) - val mergedArgumentList: List = buildList { - addAll( - callTree.children.subListToEnd(subjectIndexInCallTree), - ) - if (outerCallTree != null) { - addAll( - outerCallTree.children.subListToEnd(1), + val mergedArgumentList: List = callTree.children.subListToEnd(subjectIndexInCallTree) + .mapIndexed { argIndex, argTree -> + Argument( + actualCalleeType = actualCalleeType, + declaredCalleeType = declaredCalleeType, + argIndex = argIndex, + tree = argTree, + adjustments = adjustments, ) } - }.mapIndexed { argIndex, argTree -> - Argument( - actualCalleeType = actualCalleeType, - declaredCalleeType = declaredCalleeType, - argIndex = argIndex, - tree = argTree, - adjustments = adjustments, - ) - } val subject = mergedArgumentList[0] val otherArgs = mergedArgumentList.subListToEnd(1) @@ -254,17 +229,11 @@ internal object TranslateDotHelper { ) } val calleeType = callee.typeOrInvalid - val unboundCalleeType = when (dotHelper.memberAccessor) { - is BindMemberAccessor -> hackTryStaticTypeToSig( - BindMethodTypeHelper.uncurry(hackMapNewStyleToOld(calleeType)), - ) - // Fine as-is - else -> withType( - calleeType, - fn = { _, sig, _ -> sig }, - fallback = { null }, - ) - }.orInvalid + val unboundCalleeType = withType( + calleeType, + fn = { _, sig, _ -> sig }, + fallback = { null }, + ).orInvalid if (connectedReference is InlineTmpLSupportCode) { return@translate translatedExpr( @@ -285,8 +254,8 @@ internal object TranslateDotHelper { val callable = TmpL.FnReference(TmpL.Id(calleePos, name), unboundCalleeType) return@translate when (connectedMethod.methodKind to dotHelper.memberAccessor) { - MethodKind.Normal to ExternalBind, - MethodKind.Normal to InternalBind, + MethodKind.Normal to ExternalCall, + MethodKind.Normal to InternalCall, MethodKind.Getter to ExternalGet, MethodKind.Getter to InternalGet, MethodKind.Setter to ExternalSet, @@ -395,7 +364,7 @@ internal object TranslateDotHelper { ), ) } - is BindMemberAccessor -> if (outerCallTree != null) { + is CallMemberAccessor -> { val method = firstMember ?: return garbageTranslatedHelper( pos, @@ -417,7 +386,7 @@ internal object TranslateDotHelper { } return translatedExpr( translation - ?: translator.untranslatableExpr(pos, (outerCallTree ?: callTree).toLispy()), + ?: translator.untranslatableExpr(pos, callTree.toLispy()), ) } @@ -462,7 +431,9 @@ internal object TranslateDotHelper { callInferences = callTypeInferences, sig = sig, ), - parameters = args.map { it.translate(translator) }, + parameters = args.map { argument -> + argument.translate(translator) + }, type = type, ), ) diff --git a/builtin/src/commonMain/kotlin/lang/temper/builtin/RegexLiteralMacro.kt b/builtin/src/commonMain/kotlin/lang/temper/builtin/RegexLiteralMacro.kt index fee57897..56f8b289 100644 --- a/builtin/src/commonMain/kotlin/lang/temper/builtin/RegexLiteralMacro.kt +++ b/builtin/src/commonMain/kotlin/lang/temper/builtin/RegexLiteralMacro.kt @@ -10,7 +10,7 @@ import lang.temper.name.Symbol import lang.temper.stage.Stage import lang.temper.type.DotHelper import lang.temper.type.DotMember -import lang.temper.type.ExternalBind +import lang.temper.type.ExternalCall import lang.temper.value.CallTree import lang.temper.value.Fail import lang.temper.value.MacroEnvironment @@ -147,11 +147,9 @@ internal object RegexLiteralMacro : BuiltinMacro(regexLiteralBuiltinName.builtin // Replace the macro call with regex constructor calls. val callPos = macroEnv.pos macroEnv.replaceMacroCallWith { - Call { - Call(callPos) { - V(callPos.leftEdge, Value(DotHelper(ExternalBind, DotMember(Symbol("compiled"))))) - buildRegex(regex, macroEnv) - } + Call(callPos) { + V(callPos.leftEdge, Value(DotHelper(ExternalCall, DotMember(Symbol("compiled"))))) + buildRegex(regex, macroEnv) } } return NotYet diff --git a/builtin/src/commonMain/kotlin/lang/temper/builtin/StringExprMacro.kt b/builtin/src/commonMain/kotlin/lang/temper/builtin/StringExprMacro.kt index bfb364ae..e6b2a8c0 100644 --- a/builtin/src/commonMain/kotlin/lang/temper/builtin/StringExprMacro.kt +++ b/builtin/src/commonMain/kotlin/lang/temper/builtin/StringExprMacro.kt @@ -16,7 +16,7 @@ import lang.temper.name.TemperName import lang.temper.stage.Stage import lang.temper.type.DotHelper import lang.temper.type.DotMember -import lang.temper.type.ExternalBind +import lang.temper.type.ExternalCall import lang.temper.type.ExternalGet import lang.temper.type.InvalidType import lang.temper.type.StaticType @@ -190,10 +190,8 @@ object StringExprMacro : BuiltinStatelessMacroValue, NamedBuiltinFun { }, plantResult = { bufferName -> Call { - Call { - V(Value(DotHelper(ExternalBind, toStringDotName))) - Rn(bufferName) - } + V(Value(DotHelper(ExternalCall, toStringDotName))) + Rn(bufferName) } }, ) @@ -366,10 +364,8 @@ object StringExprMacro : BuiltinStatelessMacroValue, NamedBuiltinFun { pending.clear() val pos = toFlush.spanningPosition(toFlush.first().pos) Call(pos) { - Call(pos.leftEdge) { - V(Value(DotHelper(ExternalBind, appendSafeDotName))) - Rn(accumulator) - } + V(Value(DotHelper(ExternalCall, appendSafeDotName))) + Rn(accumulator) if (toFlush.size == 1) { Replant(freeTree(toFlush.first())) } else { @@ -389,10 +385,8 @@ object StringExprMacro : BuiltinStatelessMacroValue, NamedBuiltinFun { } else if (lastWasInterpolation) { lastWasInterpolation = false Call(t.pos) { - Call(t.pos.leftEdge) { - V(Value(DotHelper(ExternalBind, appendDotName))) - Rn(accumulator) - } + V(Value(DotHelper(ExternalCall, appendDotName))) + Rn(accumulator) Replant(freeTree(t)) } } else { @@ -491,10 +485,10 @@ private fun pointAppendsAtAccumulator(funTree: FunTree, isTagged: Boolean) { V(Types.vVoid) } - val vAppendDotHelper = Value(DotHelper(ExternalBind, appendDotName)) + val vAppendDotHelper = Value(DotHelper(ExternalCall, appendDotName)) val vAppendSafeDotHelper = if (isTagged) { // Accumulators have separate append and appendSafe methods - Value(DotHelper(ExternalBind, appendSafeDotName)) + Value(DotHelper(ExternalCall, appendSafeDotName)) } else { // StringBuilders can just use the same one. // We also need an implicit `?.toString() ?: "null"` @@ -556,10 +550,8 @@ private fun pointAppendsAtAccumulator(funTree: FunTree, isTagged: Boolean) { // acc.appendSafe("...") val pos = parts.spanningPosition(next.pos) Call(pos) { - Call(pos.leftEdge) { - V(vAppendSafeDotHelper) - Rn(accumulatorName) - } + V(vAppendSafeDotHelper) + Rn(accumulatorName) if (parts.size == 1) { Replant(parts[0]) } else { @@ -573,10 +565,8 @@ private fun pointAppendsAtAccumulator(funTree: FunTree, isTagged: Boolean) { interpolateSymbol -> Edit(t, i..i + 1) { // acc.append(expr) Call(next.pos) { - Call(next.pos.leftEdge) { - V(vAppendDotHelper) - Rn(accumulatorName) - } + V(vAppendDotHelper) + Rn(accumulatorName) if (isTagged) { Replant(next) } else { @@ -778,18 +768,14 @@ internal object CoerceToString : SpecialFunction, BuiltinMacro("str", null) { private fun Planting.buildToStringCall(subject: Value<*>) = Call { - Call { - V(Value(DotHelper(memberAccessor = ExternalBind, member = toStringDotName))) - V(subject) - } + V(Value(DotHelper(memberAccessor = ExternalCall, member = toStringDotName))) + V(subject) } private fun Planting.buildToStringCall(subject: Tree) = Call { - Call { - V(Value(DotHelper(memberAccessor = ExternalBind, member = toStringDotName))) - Replant(subject) - } + V(Value(DotHelper(memberAccessor = ExternalCall, member = toStringDotName))) + Replant(subject) } private fun Planting.buildStringifyCall(arg: Tree, argType: StaticType?) { diff --git a/docs/for-users/.snippet-hashes.json b/docs/for-users/.snippet-hashes.json index 472b92bd..96c4d5a2 100644 --- a/docs/for-users/.snippet-hashes.json +++ b/docs/for-users/.snippet-hashes.json @@ -646,7 +646,7 @@ "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/if/snippet.md/1/snippet.md": "13DEDB9BB18C0D5DB805A3542B13BFAD851067731BB00094DEDE66B3B9DEF08B", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/if/snippet.md/2/snippet.md": "EAB3A60A87E8992ECC822494F7BBA896EFF8A541BAB0456D1BCF2004E62F4C1F", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/if/snippet.md/3/snippet.md": "829A45E32B51873A3D06844F4BD632FACBE6F6C6D0DA9EBE1B7DEB3CA9236385", - "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/if/snippet.md/4/snippet.md": "A8FA1C2C1E1C27AED456CDBDF1239D3F97E380162E1D0EE613D215F4BFA67BF1", + "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/if/snippet.md/4/snippet.md": "D4C548BCAFC9C41CB21450738F9B17A63600C023566A077C5A63CDF9EFD3DED8", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/if/snippet.md/5/snippet.md": "F447E84C4A13D3ECCAC185D91D527EB4ABF8014508E5F003F0D6ABEF9AD70BA4", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/import/snippet.md/0/snippet.md": "5C05E64A0A34B4C741860DD75F2F72E9605AA4B5359E049A565167A03E9C8661", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/import/snippet.md/1/snippet.md": "834387A74145305C1E35190E6A4550A129EC044A36C53E9C3CB5F609A8DED85C", @@ -665,7 +665,7 @@ "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/throws/snippet.md/0/snippet.md": "9EA79DF1E2303D760AB2A68AFB4731C27F67A2D5200C3EF8CF67344BA155BC60", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/when/snippet.md/0/snippet.md": "5BD911DA006BA2476BD287525B31ECAACC4F36F4E883150C3FDA644ED3B6085B", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/while/snippet.md/0/snippet.md": "F491CA0EB2CED986A5818944CBF50989D2C4E43FF5D346C1A6503792C11093E1", - "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/while/snippet.md/1/snippet.md": "29A3267EC121C73FD4CDB3D9046A596A7E1EC6440BA027DE6F0ED3DF08E611B7", + "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/while/snippet.md/1/snippet.md": "C4C02DC507F404877FF743EF8A6C945E6F3CB9C3E382AFC134386F07D7380F30", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/yield/snippet.md/0/snippet.md": "52EC854AE11F8BA6D904678EFCA4C854774D6EC91627921D52078BD945BC1C45", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/builtin/%7E/snippet.md/0/snippet.md": "634A53649F0932A8B031EA0E505383F2CA0FA380FB44169C1535408BEDF96276", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/float64-comparison-details/snippet.md/0/snippet.md": "8483784601D50744696A7A810B6E9783CC0B45EAF619124F8A7715EC140153D7", @@ -695,7 +695,7 @@ "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/stmt/for-of-loop/snippet.md/2/snippet.md": "CF7FB2A6ADC9A8D0404A1E707FD03C1B37EF3CE8FA371E98835169BDC585A17F", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/stmt/for-of-loop/snippet.md/3/snippet.md": "0E6034B28823D32D5E74447F0319974AB1CB6401DAEA244D668600BA021DC22B", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/stmt/simple-for-loop/snippet.md/0/snippet.md": "28B2B1AC1C936F9898F1DB05F81732236FE0CB066362F2DD76CFAE16847BC2EC", - "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/stmt/simple-for-loop/snippet.md/1/snippet.md": "4EFD20EE8ADD41BA8325920C7DF40F20ACC0F155015C0DB36F2932A089488080", + "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/stmt/simple-for-loop/snippet.md/1/snippet.md": "4C804C60F9C013047224CC2E14BF6820CFC998AD068276C9A462AFE1F9A5FEC4", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/stmt/simple-for-loop/snippet.md/2/snippet.md": "1567800CA7C478B0101F63ADCEF4138F2523C19DB3FA679FA8DBD0525197FACB", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/stmt/simple-for-loop/snippet.md/3/snippet.md": "FB5CDF63FF3977CDE295C04B933217AC84DA38B2DA8E22D62804DAA23286DF4D", "build-user-docs/build/snippet/temper-code/build-user-docs/build/snippet/syntax/BlockLambda/snippet.md/0/snippet.md": "2C7F7E8AF43BA579B9344CB24656B8CC5502AA1D155C4383B341B3EF681072EA", diff --git a/docs/for-users/temper-docs/docs/reference/builtins.md b/docs/for-users/temper-docs/docs/reference/builtins.md index 9248f3b5..46bdb28c 100644 --- a/docs/for-users/temper-docs/docs/reference/builtins.md +++ b/docs/for-users/temper-docs/docs/reference/builtins.md @@ -2098,7 +2098,7 @@ Unlike in some other languages, curly brackets (`{...}`) are **required** around ```temper for (var i = 0; i < 3; ++i) console.log(i.toString()) // Curly brackets missing -// ❌ Expected function type, but got Invalid!, No type for subject of .toString!, Expected function type, but got Invalid! +// ❌ Expected function type, but got Function!, No type for subject of .toString! ``` @@ -2313,7 +2313,7 @@ You can't do that in Temper; always put `{`...`}` around the bodies. ```temper if (true) console.log("Runs"); else console.log("Does not run"); -// ❌ Expected a TopLevel here!, Expected function type, but got Invalid! +// ❌ Expected a TopLevel here!, Expected function type, but got Function! ``` @@ -2869,7 +2869,7 @@ Unlike in some other languages, curly brackets (`{...}`) are **required** around var i = 0; while (i < 3) console.log((i++).toString()) // Curly brackets missing -// ❌ Expected function type, but got Invalid! +// ❌ Expected function type, but got Function! ``` diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/CleanupTemporaries.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/CleanupTemporaries.kt index 06282a5e..20e2ad90 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/CleanupTemporaries.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/CleanupTemporaries.kt @@ -29,8 +29,6 @@ import lang.temper.name.SourceName import lang.temper.name.StableTemperName import lang.temper.name.Symbol import lang.temper.name.Temporary -import lang.temper.type.BindMemberAccessor -import lang.temper.type.DotHelper import lang.temper.type.StaticType import lang.temper.type.WellKnownTypes import lang.temper.type.isVoidLike @@ -1528,18 +1526,12 @@ private fun mayReorderOver(t: Tree, readsAndWrites: ReadsAndWrites): Boolean = w is StayLeaf -> true is CallTree -> { // conservatively may not, but allow for some patterns: - // - do_bind_methodName(subject) where subject can be reordered over // - nym`<>`(callee, TypeActuals) where callee can be reordered over + // - read of a static is just a stable name val callee = t.childOrNull(0)?.functionContained when (callee) { BuiltinFuns.angleFn if t.size >= 2 -> mayReorderOver(t.child(1), readsAndWrites) is GetStaticOp -> true - is DotHelper if callee.memberAccessor is BindMemberAccessor -> { - val subject = t.childOrNull( - callee.memberAccessor.enclosingTypeIndexOrNegativeOne + 2, - ) - subject != null && mayReorderOver(subject, readsAndWrites) - } else -> false } } diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/GeneratorHelperFns.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/GeneratorHelperFns.kt index a0766f7b..8194a941 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/GeneratorHelperFns.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/GeneratorHelperFns.kt @@ -12,7 +12,7 @@ import lang.temper.name.Symbol import lang.temper.type.Abstractness import lang.temper.type.DotHelper import lang.temper.type.DotMember -import lang.temper.type.ExternalBind +import lang.temper.type.ExternalCall import lang.temper.type.MkType import lang.temper.type.NominalType import lang.temper.type.TypeFormal @@ -40,10 +40,8 @@ import lang.temper.value.StaySink import lang.temper.value.StaylessMacroValue import lang.temper.value.TBoolean import lang.temper.value.TClass -import lang.temper.value.TFunction import lang.temper.value.Tree import lang.temper.value.Value -import lang.temper.value.and import lang.temper.value.functionContained import lang.temper.value.unpackPositionedOr @@ -202,7 +200,7 @@ object GeneratorStepperFn : CallableValue, StaylessMacroValue { override fun invoke(macroEnv: MacroEnvironment, interpMode: InterpMode): PartialResult = if (interpMode == InterpMode.Full) { val nextCallHelper = DotHelper( - ExternalBind, + ExternalCall, DotMember(Symbol("next")), emptyList(), ) @@ -212,20 +210,12 @@ object GeneratorStepperFn : CallableValue, StaylessMacroValue { V(generator) } } - val boundMethod = macroEnv.dispatchCallTo( + macroEnv.dispatchCallTo( callTree, Value(nextCallHelper), callTree.children.subListToEnd(1), interpMode, ) - boundMethod.and { boundMethodValue -> - (TFunction.unpackOrNull(boundMethodValue) as? CallableValue) - ?.invoke(ActualValues.Empty, cb, interpMode) - ?: cb.fail( - MessageTemplate.ExpectedValueOfType, - values = listOf(TFunction, boundMethodValue), - ) - } } else { NotYet } diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/MaybeAdjustDotHelper.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/MaybeAdjustDotHelper.kt index c646142d..420b4f90 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/MaybeAdjustDotHelper.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/MaybeAdjustDotHelper.kt @@ -3,15 +3,16 @@ package lang.temper.frontend import lang.temper.builtin.BuiltinFuns import lang.temper.builtin.GetStaticOp import lang.temper.common.soleMatchingOrNull +import lang.temper.log.Position import lang.temper.type.Abstractness import lang.temper.type.AccessibleFilter -import lang.temper.type.BindMemberAccessor +import lang.temper.type.CallMemberAccessor import lang.temper.type.DotHelper import lang.temper.type.DotMember import lang.temper.type.ExternalGet +import lang.temper.type.ExternalMemberAccessor import lang.temper.type.GetMemberAccessor import lang.temper.type.InstanceExtensionResolution -import lang.temper.type.InternalBind import lang.temper.type.InternalGet import lang.temper.type.InternalMemberAccessor import lang.temper.type.InternalSet @@ -23,6 +24,8 @@ import lang.temper.type.StaticPropertyShape import lang.temper.type.TypeParameterShape import lang.temper.type.TypeShape import lang.temper.value.CallTree +import lang.temper.value.Planting +import lang.temper.value.TreeTemplate import lang.temper.value.Value import lang.temper.value.freeTree import lang.temper.value.reifiedTypeContained @@ -42,7 +45,7 @@ import lang.temper.value.typeShapeAtLeafOrNull * - `x.name` become a static property access if `x` inlines to a type value. That cannot * happen until after `x` is distinguishable from local names by name resolution. * - `x.f()` might become a call of a [property get][GetMemberAccessor] instead of a - * [method bind][BindMemberAccessor] if there is no method named `f`, but there is a + * [method call][CallMemberAccessor] if there is no method named `f`, but there is a * property named `f` with function type. * * @return true iff adjustments were made @@ -126,38 +129,54 @@ internal fun maybeAdjustDotHelper( return true } - // do_iget_p( t, type (T)) -> igetStatic(T, \p) - // do_ibind_p(t, type (T)) -> igetStatic(T, \p) - // do_get_p( type (T)) -> getStatic(T, \p) - // do_bind_p( type (T)) -> getStatic(T, \p) + // do_iget_p( t, type (T)) -> igetStatic(T, \p) + // do_icall_p(t, type (T), arg) -> igetStatic(T, \p)(arg) + // do_get_p( type (T)) -> getStatic(T, \p) + // do_call_p( type (T), arg) -> getStatic(T, \p)(arg) if ( member is DotMember && !extensionsToPreserve && - (accessor is GetMemberAccessor || accessor is BindMemberAccessor) && - typeReceiver != null && - t.size == firstArgIndex + 1 + ( + (accessor is GetMemberAccessor && t.size == firstArgIndex + 1) || + (accessor is CallMemberAccessor && t.size >= firstArgIndex + 1) + ) && + typeReceiver != null ) { val dotName = member.dotName - edge.replace gets@{ pos -> + fun Planting.makeReplacement(pos: Position): TreeTemplate<*> { + val alt = GetStaticOp.externalStaticGot(typeReceiver, dotName) + if (alt != null) { + return V(calleePos, alt) + } val gets = when (accessor) { - InternalGet, InternalBind -> BuiltinFuns.vIGets - else -> GetStaticOp.externalStaticGot(typeReceiver, dotName)?.let { got -> - return@gets V(got) - } ?: BuiltinFuns.vGets + is InternalMemberAccessor -> BuiltinFuns.vIGets + is ExternalMemberAccessor -> BuiltinFuns.vGets } - Call(pos) { + return Call(pos) { V(calleePos, gets) Replant(freeTree(firstArg)) V(pos.rightEdge, dotName) } } + edge.replace gets@{ pos -> + if (accessor is CallMemberAccessor) { + Call(pos) { + makeReplacement(pos) + for (arg in t.children.drop(firstArgIndex + 1)) { + Replant(freeTree(arg)) + } + } + } else { + makeReplacement(pos) + } + } return true } // When `p` is a property name, not a method name: - // do_ibind_p(t, subject) -> do_iget(t, subject) - // do_bind_p( subject) -> do_get( subject) - if (accessor is BindMemberAccessor && member is DotMember) { + // do_icall_p(t, subject, arg) -> do_iget(t, subject)(arg) + // do_call_p( subject, arg) -> do_get( subject)(arg) + if (accessor is CallMemberAccessor && member is DotMember) { val typeShapes = thisType?.let { listOf(it) } ?: subjectTypeShapes if (typeShapes != null && (!hasEnclosingType || thisType != null)) { @@ -190,10 +209,16 @@ internal fun maybeAdjustDotHelper( member, adjDotHelper.extensions, ) + val args = t.children.drop(endOfDotParts) edge.replace { Call(t.pos) { - V(calleePos, Value(newDotHelper)) - dotParts.forEach { + Call(t.pos) { + V(calleePos, Value(newDotHelper)) + dotParts.forEach { + Replant(freeTree(it)) + } + } + args.forEach { Replant(freeTree(it)) } } @@ -203,7 +228,7 @@ internal fun maybeAdjustDotHelper( // call this recursively if it's internal. if (newDotHelper.memberAccessor == InternalGet) { maybeAdjustDotHelper( - edge.target as CallTree, + edge.target.child(0) as CallTree, newDotHelper, subjectTypeShapes, preserveExtensions = preserveExtensions, diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/Weaver.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/Weaver.kt index 73a010ac..75210f35 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/Weaver.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/Weaver.kt @@ -24,7 +24,6 @@ import lang.temper.name.ImplicitsCodeLocation import lang.temper.name.ResolvedName import lang.temper.name.TemperName import lang.temper.name.Temporary -import lang.temper.type.BindMemberAccessor import lang.temper.type.DotHelper import lang.temper.type.ExternalSet import lang.temper.type.InternalSet @@ -419,12 +418,9 @@ class Weaver private constructor( child is CallTree -> { // Some calls are intermediate steps to other calls: // - angle bracket calls associate type parameters with a callee. - // - do_bind_xyz calls associate a subject with a method dot name // These should stay in situ. - val callee = child.childOrNull(0) - when (val calleeFn = callee?.functionContained) { + when (child.childOrNull(0)?.functionContained) { BuiltinFuns.angleFn -> child.size != 1 || shouldExtract(child, 1) - is DotHelper -> calleeFn.memberAccessor !is BindMemberAccessor else -> true } } diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/function/OptimizeContextualAutoescapingBlocks.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/function/OptimizeContextualAutoescapingBlocks.kt index 7134f352..1fec8e65 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/function/OptimizeContextualAutoescapingBlocks.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/function/OptimizeContextualAutoescapingBlocks.kt @@ -30,10 +30,10 @@ import lang.temper.name.Symbol import lang.temper.name.TemperName import lang.temper.name.Temporary import lang.temper.type.Abstractness -import lang.temper.type.BindMemberAccessor +import lang.temper.type.CallMemberAccessor import lang.temper.type.DotHelper import lang.temper.type.DotMember -import lang.temper.type.ExternalBind +import lang.temper.type.ExternalCall import lang.temper.type.ExternalGet import lang.temper.type.Member import lang.temper.type.MethodKind @@ -559,10 +559,8 @@ private class AutoescCommon(val root: BlockTree) { ) : ValueConstruction { override fun plant(p: Planting) { p.Call { - Call { - V(Value(DotHelper(ExternalBind, DotMember(methodName)))) - subject.plant(this) - } + V(Value(DotHelper(ExternalCall, DotMember(methodName)))) + subject.plant(this) } } } @@ -823,79 +821,75 @@ private fun optimizeAutoescaperUse( // and remember it as something we need to change. if (t is CallTree && t.size >= 2) { val callee = t.child(0) - if (callee is CallTree && callee.size == 2) { - val fn = callee.child(0).functionContained - if (fn is DotHelper && fn.memberAccessor is BindMemberAccessor) { - val subject = callee.child(1) - if (subject is RightNameLeaf && subject.content == accumulatorName) { - val (_, classification) = methodClassification(fn.member) - if (classification != null) { - val arg = t.child(1) - val (argToPropagateOver: Value<*>, argPos) = when (classification) { - AppendClassification.AppendSafe -> - (arg.valueContained ?: return null) to arg.pos - AppendClassification.AppendUnsafe -> TNull.value to arg.pos.leftEdge - } + val fn = callee.functionContained + if (fn is DotHelper && fn.memberAccessor is CallMemberAccessor) { + val subject = callee.child(fn.memberAccessor.firstArgumentIndex + 1) + if (subject is RightNameLeaf && subject.content == accumulatorName) { + val (_, classification) = methodClassification(fn.member) + if (classification != null) { + val arg = t.child(1) + val (argToPropagateOver: Value<*>, argPos) = when (classification) { + AppendClassification.AppendSafe -> + (arg.valueContained ?: return null) to arg.pos + AppendClassification.AppendUnsafe -> TNull.value to arg.pos.leftEdge + } - val after = iCtx.interpret(argPos) { - Call { - V(propagateOver) - V(contextPropagator) - V(state) - V(argPos, argToPropagateOver) - V(sameStateFn) - V(vCallout) - } - } as? Value<*> ?: return null - withCallouts { problem, severity -> - problems.add(CalledOutProblem(severity, argPos, problem)) + val after = iCtx.interpret(argPos) { + Call { + V(propagateOver) + V(contextPropagator) + V(state) + V(argPos, argToPropagateOver) + V(sameStateFn) + V(vCallout) } - val effectValueList = TList.unpackOrNull(after.readField(effectsDotName)) - ?: return null - val effects = buildList { - for (effectValue in effectValueList) { - val shape = (effectValue.typeTag as? TClass)?.typeShape - when (shape?.name) { - appendParseEffectName -> { - val text = TString.unpackOrNull( - effectValue.readField(textDotName), - ) ?: return@propagateOverStmt null - add(AppendEffectDetail(text)) - } - eventParseEffectName -> { - val eventValue = effectValue.readField(eventDotName) - ?: return@propagateOverStmt null - val eventName = use.common.nameFor(eventValue) - ?: return@propagateOverStmt null - add(EventEffectDetail(eventName)) - } - else -> return@propagateOverStmt null + } as? Value<*> ?: return null + withCallouts { problem, severity -> + problems.add(CalledOutProblem(severity, argPos, problem)) + } + val effectValueList = TList.unpackOrNull(after.readField(effectsDotName)) + ?: return null + val effects = buildList { + for (effectValue in effectValueList) { + val shape = (effectValue.typeTag as? TClass)?.typeShape + when (shape?.name) { + appendParseEffectName -> { + val text = TString.unpackOrNull( + effectValue.readField(textDotName), + ) ?: return@propagateOverStmt null + add(AppendEffectDetail(text)) + } + eventParseEffectName -> { + val eventValue = effectValue.readField(eventDotName) + ?: return@propagateOverStmt null + val eventName = use.common.nameFor(eventValue) + ?: return@propagateOverStmt null + add(EventEffectDetail(eventName)) } + else -> return@propagateOverStmt null } } - effectValues = effectValueList - state = after.readField(iCtx, stateAfterGetter, argPos) ?: return null - val escapers = when (classification) { - AppendClassification.AppendUnsafe -> { - val escaperValue = iCtx.interpret(ref.pos) { - Call { - Call(escaperForDotHelper) { - V(escaperPicker) - } - V(state) - V(vCallout) - } - } as? Value<*> ?: return null - withCallouts { problem, severity -> - problems.add(CalledOutProblem(severity, ref.pos, problem)) + } + effectValues = effectValueList + state = after.readField(iCtx, stateAfterGetter, argPos) ?: return null + val escapers = when (classification) { + AppendClassification.AppendUnsafe -> { + val escaperValue = iCtx.interpret(ref.pos) { + Call(escaperForDotHelper) { + V(escaperPicker) + V(state) + V(vCallout) } - - escaperUnraveler.escapers(escaperValue) ?: return null + } as? Value<*> ?: return null + withCallouts { problem, severity -> + problems.add(CalledOutProblem(severity, ref.pos, problem)) } - AppendClassification.AppendSafe -> null + + escaperUnraveler.escapers(escaperValue) ?: return null } - toChange.add(ChangeDetail(edge, effects, escapers, classification)) + AppendClassification.AppendSafe -> null } + toChange.add(ChangeDetail(edge, effects, escapers, classification)) } } } @@ -1032,10 +1026,8 @@ private fun optimizeAutoescaperUse( edge to { fun Planting.plantSafeAdjusted(safePs: AppendStmtPositions, safe: String): UnpositionedTreeTemplate<*> = Call(safePs.pos) { - Call(safePs.callee) { - V(safePs.callee, Value(collectorAppendDotHelper)) - Rn(safePs.subject, collector) - } + V(safePs.callee, Value(collectorAppendDotHelper)) + Rn(safePs.subject, collector) V(safePs.arg, Value(safe, TString)) } fun Planting.plantEvent(name: TemperName): UnpositionedTreeTemplate<*> = @@ -1073,12 +1065,10 @@ private fun optimizeAutoescaperUse( Replant(arg) } else { Call { - Call { - V(Value(applyDotHelper)) - Call(BuiltinFuns.vGets) { - V(Value(ReifiedType(escapers.first()), TType)) - V(Symbol("instance")) - } + V(Value(applyDotHelper)) + Call(BuiltinFuns.vGets) { + V(Value(ReifiedType(escapers.first()), TType)) + V(Symbol("instance")) } escapeArg(escapers.subListToEnd(1)) } @@ -1284,10 +1274,10 @@ private data class ChangeDetail( private val stateAfterGetter = DotHelper(ExternalGet, DotMember(Symbol("stateAfter"))) private val effectsDotName = Symbol("effects") -private val escaperForDotHelper = DotHelper(ExternalBind, DotMember(Symbol("escaperFor"))) -private val appendSafeDotHelper = DotHelper(ExternalBind, appendSafeDotName) -private val appendDotHelper = DotHelper(ExternalBind, appendDotName) -private val applyDotHelper = DotHelper(ExternalBind, DotMember(Symbol("apply"))) +private val escaperForDotHelper = DotHelper(ExternalCall, DotMember(Symbol("escaperFor"))) +private val appendSafeDotHelper = DotHelper(ExternalCall, appendSafeDotName) +private val appendDotHelper = DotHelper(ExternalCall, appendDotName) +private val applyDotHelper = DotHelper(ExternalCall, DotMember(Symbol("apply"))) private val enactDotName = Symbol("enact") private val eventDotName = Symbol("event") private val textDotName = Symbol("text") diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/generate/TypeChecker.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/generate/TypeChecker.kt index 9be36433..d7e777c2 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/generate/TypeChecker.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/generate/TypeChecker.kt @@ -18,6 +18,7 @@ import lang.temper.interp.New import lang.temper.interp.forEachActual import lang.temper.log.MessageTemplate import lang.temper.log.Position +import lang.temper.log.Positioned import lang.temper.name.Symbol import lang.temper.name.TemperName import lang.temper.name.Temporary @@ -261,8 +262,10 @@ internal class TypeChecker( // for now as well as anything else that might slip through in the future. val returnType = (t.typeInferences?.type as? FunctionType)?.returnType val returnDecl = t.parts?.returnDecl - val returnDeclType = returnDecl?.parts?.name?.typeInferences?.type - checkSubType(returnDecl?.pos, returnType, returnDeclType) + if (returnDecl != null) { + val returnDeclType = returnDecl.parts?.name?.typeInferences?.type + checkSubType(returnDecl, returnType, returnDeclType) + } if (returnType?.isBubbly == false) { checkAgainstBubbles(t) } @@ -341,7 +344,7 @@ internal class TypeChecker( val rightType = rightTree.typeInferences?.type // TODO: We probably want to enforce that we have either a left or a right type from the // checker, but baby steps. - checkSubType(t.pos, leftType, rightType) + checkSubType(t, leftType, rightType) // And make sure we don't use void as a value. Focus on actual void, not just void-like for now. if (rightType?.isVoid == true) { // We can assign voids only to simple names that are temporaries or appropriate return decls. @@ -356,13 +359,12 @@ internal class TypeChecker( } } - /** @param pos must be non-null if the other params are. */ - private fun checkSubType(pos: Position?, leftType: StaticType?, rightType: StaticType?) { + private fun checkSubType(src: Positioned, leftType: StaticType?, rightType: StaticType?) { if (leftType != null && rightType != null && !typeContext.isSubType(rightType, leftType)) { logSink.log( level = Log.Error, template = MessageTemplate.ExpectedSubType, - pos = pos!!, + pos = src.pos, values = listOf(leftType, rightType), ) } diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/json/JsonInteropPass.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/json/JsonInteropPass.kt index abbdc69c..52b6e931 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/json/JsonInteropPass.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/json/JsonInteropPass.kt @@ -26,7 +26,7 @@ import lang.temper.name.identifiers.IdentStyle import lang.temper.type.Abstractness import lang.temper.type.DotHelper import lang.temper.type.DotMember -import lang.temper.type.ExternalBind +import lang.temper.type.ExternalCall import lang.temper.type.ExternalGet import lang.temper.type.MkType import lang.temper.type.MutableTypeFormal @@ -489,10 +489,8 @@ internal class JsonInteropPass( V(returnedFromSymbol) V(TBoolean.valueTrue) Block { - Call { - Call(DotHelper(ExternalBind, startObjectDotName)) { - Rn(p) - } + Call(DotHelper(ExternalCall, startObjectDotName)) { + Rn(p) } properties.forEach { prop -> if (!prop.shouldEncode) { @@ -514,10 +512,8 @@ internal class JsonInteropPass( } fun Planting.plantPropertyKey() { - Call { - Call(DotHelper(ExternalBind, objectKeyDotName)) { - Rn(p) - } + Call(DotHelper(ExternalCall, objectKeyDotName)) { + Rn(p) V(Value(prop.jsonPropertyKey, TString)) } } @@ -542,10 +538,8 @@ internal class JsonInteropPass( } if (encodeMethodName != null) { plantPropertyKey() - Call { - Call(DotHelper(ExternalBind, encodeMethodName)) { - Rn(p) - } + Call(DotHelper(ExternalCall, encodeMethodName)) { + Rn(p) V(knownValue) } } @@ -573,17 +567,13 @@ internal class JsonInteropPass( } if (producerMethodName != null) { // p.???Value(propertyName) - Call { - Call(DotHelper(ExternalBind, producerMethodName)) { - Rn(p) - } + Call(DotHelper(ExternalCall, producerMethodName)) { + Rn(p) propertyValueExpr() } } else { - Call { - Call(DotHelper(ExternalBind, encodeToJsonDotName)) { - subs.plantAdapterFor(this, type, prop.pos, propertyName) - } + Call(DotHelper(ExternalCall, encodeToJsonDotName)) { + subs.plantAdapterFor(this, type, prop.pos, propertyName) propertyValueExpr() Rn(p) } @@ -599,10 +589,8 @@ internal class JsonInteropPass( ) } } - Call { - Call(DotHelper(ExternalBind, endObjectDotName)) { - Rn(p) - } + Call(DotHelper(ExternalCall, endObjectDotName)) { + Rn(p) } V(void) } @@ -632,10 +620,8 @@ internal class JsonInteropPass( // Read the JSON property for the named constructor input. // obj.propertyValueOrBubble("prop") fun Planting.propertyTreeExpr() { - Call { - Call(DotHelper(ExternalBind, propertyValueOrBubbleDotName)) { - Rn(objLocal) - } + Call(DotHelper(ExternalCall, propertyValueOrBubbleDotName)) { + Rn(objLocal) V(Value(prop.jsonPropertyKey, TString)) } } @@ -665,21 +651,17 @@ internal class JsonInteropPass( } } else { // (PROPERTY_TREE as ContentType).methodName() - Call { - Call(DotHelper(ExternalBind, methodName)) { - Call(BuiltinFuns.asFn) { - propertyTreeExpr() - V(Value(reifiedVariantType)) - } + Call(DotHelper(ExternalCall, methodName)) { + Call(BuiltinFuns.asFn) { + propertyTreeExpr() + V(Value(reifiedVariantType)) } } } } else { // JSON_ADAPTER_EXPR.decodeFromJson(PROPERTY_TREE_EXPR, ic) - Call { - Call(DotHelper(ExternalBind, decodeFromJsonDotName)) { - subs.plantAdapterFor(this, propType, prop.pos, prop.name) - } + Call(DotHelper(ExternalCall, decodeFromJsonDotName)) { + subs.plantAdapterFor(this, propType, prop.pos, prop.name) propertyTreeExpr() Rn(ic) } @@ -803,14 +785,12 @@ internal class JsonInteropPass( for (t in jsonSubTypes) { V(caseIsSymbol) Rn(t.subTypeName) - Call { - Call(DotHelper(ExternalBind, encodeToJsonDotName)) { + Call(DotHelper(ExternalCall, encodeToJsonDotName)) { + Call { Call { - Call { - Rn(dotBuiltinName) - Rn(t.subTypeName) - V(jsonAdapterDotName.dotName) - } + Rn(dotBuiltinName) + Rn(t.subTypeName) + V(jsonAdapterDotName.dotName) } } Rn(x) @@ -919,14 +899,12 @@ internal class JsonInteropPass( // return ChosenType.jsonAdapter().decodeFromJson(obj, ic) Call { Rn(returnBuiltinName) - Call { - Call(DotHelper(ExternalBind, decodeFromJsonDotName)) { + Call(DotHelper(ExternalCall, decodeFromJsonDotName)) { + Call { Call { - Call { - Rn(dotBuiltinName) - Rn(case.subTypeName) - V(jsonAdapterDotName.dotName) - } + Rn(dotBuiltinName) + Rn(case.subTypeName) + V(jsonAdapterDotName.dotName) } } Rn(objLocal) @@ -956,10 +934,8 @@ internal class JsonInteropPass( ) Decl(propertyValueName) { V(initSymbol) - Call { - Call(DotHelper(ExternalBind, propertyValueOrNullDotName)) { - Rn(objLocal) - } + Call(DotHelper(ExternalCall, propertyValueOrNullDotName)) { + Rn(objLocal) V(Value(propertyKey, TString)) } } diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/syntax/DotOperationDesugarer.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/syntax/DotOperationDesugarer.kt index 72a06ebc..0829871f 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/syntax/DotOperationDesugarer.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/syntax/DotOperationDesugarer.kt @@ -23,11 +23,11 @@ import lang.temper.name.Temporary import lang.temper.type.DotHelper import lang.temper.type.DotMember import lang.temper.type.ExtensionResolution -import lang.temper.type.ExternalBind +import lang.temper.type.ExternalCall import lang.temper.type.ExternalGet import lang.temper.type.ExternalSet import lang.temper.type.InstanceExtensionResolution -import lang.temper.type.InternalBind +import lang.temper.type.InternalCall import lang.temper.type.InternalGet import lang.temper.type.InternalSet import lang.temper.type.Member @@ -536,24 +536,22 @@ private fun desugarDotOperation( replacer = { val call = edgeToReplace.target as CallTree Call(call.pos) { - Call(listOf(subjectPos, nameTree.pos).spanningPosition(subjectPos)) { - plantHandler( - nameTree.pos, - if (enclosingTypeShape != null) { - InternalBind - } else { - ExternalBind - }, - dotName, - extensions, - ) - if (enclosingTypeTree != null) { - // internal calls need the enclosing type at - // InternalCall.enclosingTypeIndexOrNegativeOne - V(enclosingTypeTree.pos, enclosingTypeValue!!) - } - Replant(freeTarget(subjectEdge)) + plantHandler( + nameTree.pos, + if (enclosingTypeShape != null) { + InternalCall + } else { + ExternalCall + }, + dotName, + extensions, + ) + if (enclosingTypeTree != null) { + // internal calls need the enclosing type at + // InternalCall.enclosingTypeIndexOrNegativeOne + V(enclosingTypeTree.pos, enclosingTypeValue!!) } + Replant(freeTarget(subjectEdge)) // `this.f(x) -> (Call this.f x) so arg 0 is at position 1 call.edges.subListToEnd(1).forEach { Replant(freeTarget(it)) diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/BindMethodTypeHelper.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/BindMethodTypeHelper.kt deleted file mode 100644 index 0d8abab6..00000000 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/BindMethodTypeHelper.kt +++ /dev/null @@ -1,59 +0,0 @@ -package lang.temper.frontend.typestage - -import lang.temper.common.subListToEnd -import lang.temper.type.FunctionType -import lang.temper.type.InvalidType -import lang.temper.type.MkType -import lang.temper.type.StaticType -import lang.temper.type.mapFunctionTypesThroughIntersection - -object BindMethodTypeHelper { - /** - * Bind member accessors curry this, instead of applying immediately, so curry each variant: - * - * fn (This, ...): ... -> fn (This): fn (...): ... - */ - fun curry( - thisType: StaticType, - variantType: StaticType, - ): FunctionType = MkType.fn( - emptyList(), - listOf(thisType), - restValuesFormal = null, - returnType = mapFunctionTypesThroughIntersection(variantType) { ft -> - if (ft.valueFormals.isNotEmpty()) { - MkType.fnDetails( - typeFormals = ft.typeFormals, - valueFormals = ft.valueFormals.subListToEnd(1), - restValuesFormal = ft.restValuesFormal, - returnType = ft.returnType, - ) - } else { - InvalidType - } - }, - ) - - /** Reverses [curry] */ - fun uncurry(curried: StaticType): StaticType = - mapFunctionTypesThroughIntersection( - curried, - ) uncurryOne@{ curriedFnType -> - if ( - curriedFnType.typeFormals.isEmpty() && - curriedFnType.valueFormals.size == 1 && - curriedFnType.restValuesFormal == null - ) { - val rt = curriedFnType.returnType - if (rt is FunctionType) { - return@uncurryOne MkType.fnDetails( - typeFormals = rt.typeFormals, - valueFormals = curriedFnType.valueFormals + rt.valueFormals, - restValuesFormal = rt.restValuesFormal, - returnType = rt.returnType, - ) - } - } - InvalidType - } -} diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/FilterOutMaskedMembers.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/FilterOutMaskedMembers.kt index 319b01d0..2d17f2c2 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/FilterOutMaskedMembers.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/FilterOutMaskedMembers.kt @@ -1,9 +1,24 @@ package lang.temper.frontend.typestage +import lang.temper.common.KBitSet +import lang.temper.common.putMultiList import lang.temper.common.removeMatching +import lang.temper.common.subListToEnd +import lang.temper.name.Symbol import lang.temper.name.TemperName import lang.temper.type.NominalType +import lang.temper.type.TypeFormal +import lang.temper.type.TypeShape import lang.temper.type.VisibleMemberShape +import lang.temper.type2.DefinedType +import lang.temper.type2.Descriptor +import lang.temper.type2.MkType2 +import lang.temper.type2.Signature2 +import lang.temper.type2.TypeContext2 +import lang.temper.type2.TypeParamRef +import lang.temper.type2.bindingMap +import lang.temper.type2.mapType +import kotlin.math.min internal fun filterOutMaskedMembers( membersGrouped: MutableMap>, @@ -21,3 +36,186 @@ internal fun filterOutMaskedMembers( } membersGrouped.removeMatching { it.value.isEmpty() } } + +/** + * If we have two, non-conflicting, inherited definitions, they can unnecessarily + * confuse the TypeSolver. + * + * interface I { f(): X } + * interface J extends I {} // J.f has return type String + * interface K { f(): String { "Yep" } } + * + * class C extends J, K {} + * + * Given these two alternative signatures, `fn (K): String` and `fn (I): String` + * the type solver has no way to pick between the two leading to an unresolvable overload + * decision. + * + * TODO: Maybe pre-typing analyze these and synthesize a method that uses `super`, + * so that the Typer never encounters this in code that does not contain other errors. + * + * class C extends J, K { + * + public f(): String { K.super.f() } + * } + * + * In this case, we have the following type hierarchy above (Top types at top) C. + * + * I depth = 2 + * \ + * J K depth = 1 + * \ / + * C depth = 0 + * + * We can prefer K's implementation of .f() to I's because it is at a lower depth + * from their common super-type. + * + * But to figure out that the two implementations, `fn (I): X` and `fn (K): String` + * are redundant, that one can be eliminated without removing options that the type + * solver might need, we need to first examine their signatures in the context of + * the common subtype, C. + * + * Every C is-a I. + * Every C is-a K. + * + * Using those two facts, we can contextualize the descriptors of the f methods to + * + * - `fn (I): String` from I + * - `fn (K): String` from K + * + * Then we can compare the signatures for equality (ignoring the `this` arg types), + * and since they're equivalent, drop any but the shallowest tier ones. + */ +internal fun filterOutDeeperMembers( + membersGrouped: MutableMap>, + inheritanceDepth: Map, + typeContext: TypeContext2, +) { + if (membersGrouped.size < 2) { return } // Nothing to compare + val groupedByWord = mutableMapOf>>() + for ((group, members) in membersGrouped) { + for (member in members) { + groupedByWord.putMultiList(member.symbol, group to member) + } + } + val minDepthThisTypeShapes by lazy { + val minDepth = inheritanceDepth.values.min() + inheritanceDepth.keys.mapNotNull { + if (inheritanceDepth[it] == minDepth) { + it.definition as? TypeShape + } else { + null + } + } + } + for (members in groupedByWord.values) { + val n = members.size + if (n <= 1) { continue } + // filter out deeper ones that have the same sig, projected from a common this variant. + val eliminated = KBitSet() + for (i in 0.. continue + else -> aDepth < bDepth + } + val commonSubTypeShape = minDepthThisTypeShapes.firstOrNull { + aThisType.definition.name in it.rawSuperTypeNames && + bThisType.definition.name in it.rawSuperTypeNames + } ?: continue + // Now that we have a this-type that is a super type of both, project the + // descriptors into that + val commonSubType = MkType2(commonSubTypeShape).actuals( + commonSubTypeShape.formals.map { MkType2(it).get() }, + ).get() + val stt = typeContext.superTypeTreeOf(commonSubType) + val aProjected = stt[aThisType.definition].firstOrNull() as? DefinedType ?: continue + val bProjected = stt[bThisType.definition].firstOrNull() as? DefinedType ?: continue + val aBindings = aProjected.bindingMap + val bBindings = bProjected.bindingMap + // Now we get A's descriptor in the context of the common subtype, and similarly B's descriptor. + val aDescInContext = aDesc.mapType(aBindings) + val bDescInContext = bDesc.mapType(bBindings) + // Assuming type formals are equivalent, and ignoring this args, are they the same. + if (equivalentForOverridePurposes(aDescInContext, bDescInContext)) { + if (aIsShallower) { + eliminated[j] = true + } else { + eliminated[i] = true + break + } + } + } + } + var k = 0 + while (true) { + val next = eliminated.nextSetBit(k) + if (next < 0) { break } + val (thisType, member) = members[next] + k = next + 1 + val group = membersGrouped[thisType] + group?.remove(member) + if (group?.isEmpty() == true) { + membersGrouped.remove(thisType) + } + } + } +} + +internal fun equivalentForOverridePurposes( + a: Descriptor, + b: Descriptor, + equivalences: Map? = null, +): Boolean { + if (a == b) { + return true + } + if (a is Signature2 && b is Signature2) { + val aReqs = if (a.hasThisFormal) { a.requiredInputTypes.subListToEnd(1) } else { a.requiredInputTypes } + val bReqs = if (b.hasThisFormal) { b.requiredInputTypes.subListToEnd(1) } else { b.requiredInputTypes } + if (aReqs.size != bReqs.size) { return false } + if (a.typeFormals.size != b.typeFormals.size) { return false } + if (a.optionalInputTypes.size != b.optionalInputTypes.size) { return false } + if ((a.restInputsType != null) != (b.restInputsType != null)) { return false } + val formalEquivalences = buildMap { + for ((x, y) in a.typeFormals zip b.typeFormals) { + this[x] = y + this[y] = x + } + } + if (!equivalentForOverridePurposes(a.returnType2, b.returnType2, formalEquivalences)) { + return false + } + for (i in aReqs.indices) { + if (!equivalentForOverridePurposes(aReqs[i], bReqs[i], formalEquivalences)) { + return false + } + } + for (i in a.optionalInputTypes.indices) { + if ( + !equivalentForOverridePurposes( + a.optionalInputTypes[i], b.optionalInputTypes[i], formalEquivalences, + ) + ) { + return false + } + } + if ( + a.restInputsType != null && + !equivalentForOverridePurposes(a.restInputsType!!, b.restInputsType!!, formalEquivalences) + ) { + return false + } + return true + } + if (a is TypeParamRef && b is TypeParamRef && equivalences != null && a.nullity == b.nullity) { + return equivalences[a.definition] == b.definition || + equivalences[b.definition] == a.definition + } + return false +} diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/InlineToRepairUnrealizedGoals.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/InlineToRepairUnrealizedGoals.kt index c44d9cb0..ae973ae3 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/InlineToRepairUnrealizedGoals.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/InlineToRepairUnrealizedGoals.kt @@ -17,11 +17,11 @@ import lang.temper.name.unusedAnalogueFor import lang.temper.type.Abstractness import lang.temper.type.AndType import lang.temper.type.DotHelper -import lang.temper.type.ExternalBind +import lang.temper.type.ExternalCall import lang.temper.type.ExternalGet import lang.temper.type.ExternalSet import lang.temper.type.FunctionType -import lang.temper.type.InternalBind +import lang.temper.type.InternalCall import lang.temper.type.InternalGet import lang.temper.type.InternalMemberAccessor import lang.temper.type.InternalSet @@ -62,6 +62,7 @@ import lang.temper.value.Tree import lang.temper.value.TypeInferences import lang.temper.value.Value import lang.temper.value.ValueLeaf +import lang.temper.value.firstArgumentIndex import lang.temper.value.freeTree import lang.temper.value.functionContained import lang.temper.value.inlineUnrealizedGoalSymbol @@ -196,7 +197,7 @@ private class InlineToRepairUnrealizedGoals( FunArg( // edge indices include callee, but there's also a subject that needs to be curried in, // so we subtract one for the callee and add one back. - argIndex = edgeIndex, + argIndex = edgeIndex - parent.firstArgumentIndex, funTree = f, ), ) @@ -652,15 +653,13 @@ private class InlineToRepairUnrealizedGoals( // Look for a callee with the form (Call (Call DotHelper(ExternalBind) subject)) val (dotHelper, thisArg) = run findDotHelper@{ - if (callee is CallTree) { - val possibleDotHelper = callee.childOrNull(0)?.functionContained - if (possibleDotHelper is DotHelper) { - val thisArg = callee.childOrNull( - possibleDotHelper.memberAccessor.enclosingTypeIndexOrNegativeOne + 2, - ) - if (thisArg != null) { - return@findDotHelper possibleDotHelper to thisArg - } + val possibleDotHelper = callee.functionContained + if (possibleDotHelper is DotHelper) { + val thisArg = call.childOrNull( + possibleDotHelper.memberAccessor.enclosingTypeIndexOrNegativeOne + 2, + ) + if (thisArg != null) { + return@findDotHelper possibleDotHelper to thisArg } } null to null @@ -669,7 +668,7 @@ private class InlineToRepairUnrealizedGoals( // We could try other strategies. If callee is by name, // it would be good to have some way to follow any chain // of names back to a named function declaration or exported name. - if (dotHelper != null && dotHelper.memberAccessor is ExternalBind) { + if (dotHelper != null && dotHelper.memberAccessor is ExternalCall) { val thisType = thisArg?.typeInferences?.type ?: return null val thisShape = representativeTypeShapeFor(listOf(thisType)) ?: return null @@ -786,7 +785,7 @@ private fun convertMemberUseToExternal( val accessor = dotHelper.memberAccessor if (accessor !is InternalMemberAccessor) { return } val externalAccessor = when (accessor) { - InternalBind -> ExternalBind + InternalCall -> ExternalCall InternalGet -> ExternalGet InternalSet -> ExternalSet } diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/SimplifyDotHelper.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/SimplifyDotHelper.kt index 271a3b97..7baa8e6a 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/SimplifyDotHelper.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/SimplifyDotHelper.kt @@ -1,11 +1,8 @@ package lang.temper.frontend.typestage -import lang.temper.builtin.BuiltinFuns import lang.temper.common.Either import lang.temper.frontend.maybeAdjustDotHelper -import lang.temper.interp.convertToErrorNode import lang.temper.type.AndType -import lang.temper.type.BindMemberAccessor import lang.temper.type.DotHelper import lang.temper.type.DotMember import lang.temper.type.ExtensionResolution @@ -21,7 +18,6 @@ import lang.temper.type.extractAtoms import lang.temper.value.CallTree import lang.temper.value.Tree import lang.temper.value.Value -import lang.temper.value.functionContained private typealias VariantResolution = Either private typealias Variant = Pair @@ -58,7 +54,7 @@ internal fun simplifyDotHelper( var lastNonExtensionResolution: VariantResolution? = null var lastResolution: VariantResolution? = null for (variant in variants.reversed()) { - if (variant.first == variantMatch || variant.first == variantMatchRefined) { + if (variant.first equivalent variantMatch || variant.first equivalent variantMatchRefined) { lastResolution = variant.second if (variant.second is Either.Left) { lastNonExtensionResolution = variant.second @@ -103,51 +99,21 @@ internal fun simplifyDotHelper( } } is Either.Right -> { - var inheritsSubject: CallTree? = null - var toReplace = calleeEdge - // (Call - // (Bind subject \member) - // ...) - // -> - // (Call - // (RightNameName extensionResolution) - // subject - // ...) - // - // But also, look through <> so that + // Look through <> so that // subject.method(...) // -> // (resolution)(subject, ...) - if (dotHelper.memberAccessor is BindMemberAccessor) { - val callEdge = call.incoming!! - toReplace = callEdge - inheritsSubject = callEdge.source as? CallTree - if ( - inheritsSubject?.childOrNull(0)?.functionContained == BuiltinFuns.angleFn - ) { - inheritsSubject = inheritsSubject.incoming?.source as? CallTree - } - - if (inheritsSubject == null) { - convertToErrorNode(callEdge) - return - } - } val extensionResolution = chosenVariantResolution.item - toReplace.replace { + calleeEdge.replace { Rn(callee.pos, extensionResolution.resolution) } if (extensionResolution is StaticExtensionResolution) { // Remove the receiver type call.removeChildren(1..1) - } else if (inheritsSubject != null) { - val subject = call.child(1) - call.removeChildren(1..1) - inheritsSubject.add(childIndex = 1, newChild = subject) } - retypeTree(toReplace.target) + retypeTree(calleeEdge.target) return } } @@ -172,3 +138,24 @@ internal fun simplifyDotHelper( retypeTree(callEdge.target) } } + +private infix fun StaticType?.equivalent(other: StaticType?): Boolean = + if (this is FunctionType && other is FunctionType) { + val tvf = this.valueFormals + val ovf = other.valueFormals + var same = this.returnType == other.returnType && + this.restValuesFormal == other.restValuesFormal && + tvf.size == ovf.size && + this.typeFormals == other.typeFormals + if (same) { + for ((i, element) in tvf.withIndex()) { + if (element.type != ovf[i].type) { + same = false + break + } + } + } + same + } else { + this == other + } diff --git a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/Typer.kt b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/Typer.kt index e96a78bb..88f76a3c 100644 --- a/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/Typer.kt +++ b/frontend/src/commonMain/kotlin/lang/temper/frontend/typestage/Typer.kt @@ -49,8 +49,8 @@ import lang.temper.name.ResolvedName import lang.temper.name.Symbol import lang.temper.name.TemperName import lang.temper.type.AndType -import lang.temper.type.BindMemberAccessor import lang.temper.type.BubbleType +import lang.temper.type.CallMemberAccessor import lang.temper.type.DotHelper import lang.temper.type.DotMember import lang.temper.type.ExtensionResolution @@ -60,6 +60,7 @@ import lang.temper.type.GetMemberAccessor import lang.temper.type.InstanceExtensionResolution import lang.temper.type.InternalMemberAccessor import lang.temper.type.InvalidType +import lang.temper.type.Member import lang.temper.type.MemberOverride2 import lang.temper.type.MemberShape import lang.temper.type.MethodKind @@ -813,7 +814,7 @@ internal class Typer( } private fun typeRegularCall(tree: CallTree) { - fixupCalleeType(tree) + val typedVariants = fixupCalleeType(tree) val priorProblems = mutableListOf() val isNew = isNewCall(tree) @@ -827,8 +828,6 @@ internal class Typer( val coverFn = calleeFn as? CoverFunction var inputTrees = tree.children.subListToEnd(tree.firstArgumentIndex) - val isDotBind = calleeFn is DotHelper && calleeFn.memberAccessor is BindMemberAccessor - // Does the context in which the call happens place bounds on the return type? // For example, if t is the right hand of the assignment, then we might be able to // get information about the left-hand side. @@ -947,35 +946,23 @@ internal class Typer( null } - val effectiveCalleeType = effectiveCallee?.let { ti.decisionType(it) }?.let { inferredType -> - if (isNew) { - inferredType // Probably just Type. Handled below. - } else if (inferredType is NominalType && functionalInterfaceSymbol in inferredType.definition.metadata) { - inferredType - } else { // Extract function types - distributeCalleeTypeThroughIntersection( - coerceCalleeTypeToIntersectionOfFunctionTypes(inferredType).also { coerced -> - if (isDotBind) { - calleeFn.sigs = buildList { - explodeCalleeType(coerced, CalleePriority.Default) - }.map { - it.sig - } - } - }, + val effectiveCalleeType = effectiveCallee?.let { ti.decisionType(it) } + if (effectiveCalleeType != null && !isNew) { + if (effectiveCalleeType is InvalidType) { + // Already reported as a problem elsewhere + } else if (!isCallable(effectiveCalleeType)) { + priorProblems.add( + TypeReason( + LogEntry( + Log.Error, + MessageTemplate.ExpectedFunctionType, + effectiveCallee.pos, + listOf(effectiveCalleeType), + ), + ), ) } } - if (effectiveCalleeType is FunctionType && isDotBind && calleeFn.sigs != null) { - // We stored the sigs above. The call wrapping this needs those. - val decision = Decision( - type = effectiveCalleeType.returnType, - variant = effectiveCalleeType, - explanations = priorProblems.toList(), - ) - ti.decide(tree, decision) - return - } val calleeVariants: List = buildSet { if (isNew) { @@ -1010,8 +997,17 @@ internal class Typer( calleeFn.sigs?.mapNotNullTo(this) { anySig -> (anySig as? Signature2)?.let { Callee(it, CalleePriority.Default) } } - } else if (effectiveCalleeType != null) { - explodeCalleeType(effectiveCalleeType, CalleePriority.Default) + } else if (typedVariants is TypedMembersAndExtensions) { + for ((type, _) in typedVariants.typedCandidates) { + hackTryStaticTypeToSig(type)?.let { sig -> + add(Callee(sig)) + } + } + } else { + (typedVariants?.fnType ?: effectiveCalleeType) + ?.let { + explodeCalleeType(it, CalleePriority.Default) + } } }.toList() @@ -2360,22 +2356,20 @@ internal class Typer( } } - private fun fixupCalleeType(t: CallTree) { + private fun fixupCalleeType(t: CallTree): ITypedVariants? { val callee = t.childOrNull(0) - val callable = callee?.functionContained ?: return - val fixedTypeAndProblems = when (callable) { + val callable = callee?.functionContained ?: return null + val variants: ITypedVariants? = when (callable) { BuiltinFuns.getpFn -> typeForGetp(t) BuiltinFuns.setpFn -> typeForSetp(t) is GetStaticOp -> typeForGets(t, callable) BuiltinFuns.asFn, BuiltinFuns.assertAsFn -> typeForAs(t, callable) BuiltinFuns.commaFn -> typeForComma(t) - is DotHelper -> typeForDotHelper(t, callable).let { - it.fnType to it.reasons - } + is DotHelper -> typeForDotHelper(t, callable) else -> null } - if (fixedTypeAndProblems != null) { - val (fixedType, problems) = fixedTypeAndProblems + if (variants != null) { + val (fixedType, problems) = variants if (DEBUG) { console.group( "Fixed callee type of ${ @@ -2399,29 +2393,38 @@ internal class Typer( ti.decide(callee, Decision(fixedType, explanations = explanations)) } } + return variants } - private fun typeForGetp(t: CallTree): Pair> { + private fun typeForGetp(t: CallTree): TypedVariants { if (t.size != GETP_ARITY + 1) { - return InvalidType to listOf(BecauseMalformedSpecialCall(t.pos, t)) + return TypedVariants( + InvalidType, + listOf(BecauseMalformedSpecialCall(t.pos, t)), + ) } val propertyReference = t.child(1) val thisArg = t.child(2) + val thisType = ti.decisionType(thisArg) val propertyReferenceType = ti.decisionType(propertyReference) ?: InvalidType - return MkType.fn( + val fnType = MkType.fn( typeFormals = emptyList(), valueFormals = listOf( propertyReferenceType, - ti.decisionType(thisArg) ?: InvalidType, + thisType ?: InvalidType, ), restValuesFormal = null, returnType = propertyReferenceType, - ) to listOf() + ) + return TypedVariants(fnType, listOf()) } - private fun typeForSetp(t: CallTree): Pair> { + private fun typeForSetp(t: CallTree): TypedVariants { if (t.size != SETP_ARITY + 1) { - return InvalidType to listOf(BecauseMalformedSpecialCall(t.pos, t)) + return TypedVariants( + InvalidType, + listOf(BecauseMalformedSpecialCall(t.pos, t)), + ) } val (_, propertyArg, thisArg, newValueArg) = t.children @@ -2451,7 +2454,7 @@ internal class Typer( problems.add(explanation) t } - return MkType.fn( + val fnType = MkType.fn( typeFormals = emptyList(), valueFormals = listOf( TopType, @@ -2460,15 +2463,16 @@ internal class Typer( ), restValuesFormal = null, returnType = rightType, - ) to problems.toList() + ) + return TypedVariants(fnType, problems.toList()) } private fun typeForGets( t: CallTree, callable: GetStaticOp, - ): Pair> { + ): TypedVariants { if (t.size != GETS_ARITY + 1) { - return InvalidType to listOf(BecauseMalformedSpecialCall(t.pos, t)) + return TypedVariants(InvalidType, listOf(BecauseMalformedSpecialCall(t.pos, t))) } // Could also extract callee here, but we pass it in above because easy enough. @@ -2476,35 +2480,76 @@ internal class Typer( val symbolArg = t.child(2) val reifiedType = typeArg.valueContained?.let { asReifiedType(it) } - ?: return InvalidType to listOf(BecauseUnresolvedTypeReference(typeArg.pos)) + ?: return TypedVariants(InvalidType, listOf(BecauseUnresolvedTypeReference(typeArg.pos))) val receiverType = reifiedType.type val memberName = symbolArg.symbolContained - ?: return InvalidType to listOf(BecauseMalformedSpecialCall(t.pos, t)) + ?: return TypedVariants(InvalidType, listOf(BecauseMalformedSpecialCall(t.pos, t))) + val member = DotMember(memberName) val (type, reasons) = - typeForGets(t.pos, typeArg.pos, receiverType, memberName, emptyList(), callable) - return type to reasons + typeForGets(t.pos, typeArg.pos, receiverType, member, emptyList(), callable) + return TypedVariants(type, reasons) + } + + private fun filterStaticExtensionsByReceiverType( + receiverType: StaticType, + staticExtensions: Iterable, + ): List { + val applicableExtensions = staticExtensions.filter { extension -> + val extensionReceiverTypes = ti.extensionReceiverTypes(extension.resolution) + extensionReceiverTypes.any { typeContext.isSubType(receiverType, it) } + } + @Suppress("ControlFlowWithEmptyBody") + if (applicableExtensions.size > 1) { + // TODO: Maybe eliminate resolutions whose receiver type is a strict super-type of + // the others, and whose argument types are equivalent. + + // For example, if we have two extensions like the below, we can + // eliminate the second because the argument types are equivalent, + // and the receiver type (String?) is a strict super-type of + // the other's receiver type (String). + // + // @staticExtension(String, "f") + // let stringF(i: Int): Void { ... } + // + // @staticExtension(String?, "f") + // let stringOrNullF(i: Int): Void { ... } + // + // Also, prefer an explicitly parameterized type to a bare type. + // In the below, stringListF would not apply to a List the + // way the first would, but where we've got explicit type hints, + // prefer them. + // + // @staticExtension(List, "f") + // let listF(): Void { ... } + // + // @staticExtension(List, "f") + // let stringListF(): Void { ... } + } + return applicableExtensions } private fun typeForGets( pos: Position, receiverTypePos: Position, receiverType: StaticType, - memberName: Symbol, + member: Member, extensions: List, callable: GetStaticOp, + expectSymbolArg: Boolean = true, + curry: Boolean = true, ): TypedMembersAndExtensions { val typeShape = (receiverType as? NominalType)?.definition as? TypeShape - val member = typeShape?.staticProperties?.firstOrNull { - it.symbol == memberName && callable.canSee(it) + val memberShape = (member as? DotMember)?.dotName?.let { memberName -> + typeShape?.staticProperties?.firstOrNull { + it.symbol == memberName && callable.canSee(it) + } } - val applicableExtensions = extensions.filter { extension -> - val extensionReceiverTypes = ti.extensionReceiverTypes(extension.resolution) - extensionReceiverTypes.any { typeContext.isSubType(receiverType, it) } - } + val applicableExtensions = + filterStaticExtensionsByReceiverType(receiverType, extensions) - if (member == null && applicableExtensions.isEmpty()) { + if (memberShape == null && applicableExtensions.isEmpty()) { // If we have no applicable extensions, then explain any problems with the // receiver type or missing members. return TypedMembersAndExtensions( @@ -2515,60 +2560,58 @@ internal class Typer( } else { // Errors should be rare, so just spend a second pass to check // if we had any name matches at all. - if (typeShape.staticProperties.any { it.symbol == memberName }) { - BecauseCannotAccessMembers(pos, DotMember(memberName), setOf(typeShape)) + if (member is DotMember && typeShape.staticProperties.any { it.symbol == member.dotName }) { + BecauseCannotAccessMembers(pos, member, setOf(typeShape)) } else { - BecauseNoSuchMember(pos, DotMember(memberName), setOf(typeShape)) + BecauseNoSuchMember(pos, member, setOf(typeShape)) } }, ), ) } - @Suppress("ControlFlowWithEmptyBody") - if (applicableExtensions.size > 1) { - // TODO: Maybe eliminate resolutions whose receiver type is a strict super-type of - // the others, and whose argument types are equivalent. - - // For example, if we have two extensions like the below, we can - // eliminate the second because the argument types are equivalent, - // and the receiver type (String?) is a strict super-type of - // the other's receiver type (String). - // - // @staticExtension(String, "f") - // let stringF(i: Int): Void { ... } - // - // @staticExtension(String?, "f") - // let stringOrNullF(i: Int): Void { ... } - } - val variants = mutableListOf() val resolutions = mutableListOf>>() fun addVariant(simpleType: StaticType, r: Either) { - val getSType = MkType.fn( - typeFormals = emptyList(), - valueFormals = listOf( - Types.function.type, - Types.symbol.type, - ), - restValuesFormal = null, - returnType = simpleType, + val extraArgs = listOfNotNull( + Types.type.type, + if (expectSymbolArg) Types.symbol.type else null, ) + val getSType = if (curry) { + MkType.fn( + typeFormals = emptyList(), + valueFormals = extraArgs, + restValuesFormal = null, + returnType = simpleType, + ) + } else { + fun prefix(t: StaticType): StaticType = when (t) { + is AndType -> MkType.and(t.members.map(::prefix)) + is FunctionType -> MkType.fnDetails( + t.typeFormals, + extraArgs.map { FunctionType.ValueFormal(null, it) } + t.valueFormals, + t.restValuesFormal, + t.returnType, + ) + else -> InvalidType + } + prefix(simpleType) + } variants.add(getSType) resolutions.add(getSType to r) } - if (member != null) { - val memberDescriptor = member.descriptor + if (memberShape != null) { + val memberDescriptor = memberShape.descriptor ?: return TypedMembersAndExtensions( InvalidType, - listOf(BecauseTypeInfoMissingForName(pos, member.name as ResolvedName)), + listOf(BecauseTypeInfoMissingForName(pos, memberShape.name as ResolvedName)), ) val memberType = when (memberDescriptor) { is Signature2 -> typeFromSignature(memberDescriptor) is Type2 -> hackMapNewStyleToOld(memberDescriptor) } - addVariant(memberType, Either.Left(member)) + addVariant(memberType, Either.Left(memberShape)) } for (extension in applicableExtensions) { @@ -2588,7 +2631,7 @@ internal class Typer( ) } - private fun typeForAs(call: CallTree, callable: MacroValue): Pair> { + private fun typeForAs(call: CallTree, callable: MacroValue): TypedVariants { val problems = mutableListOf() var type: StaticType = functionType @@ -2627,15 +2670,27 @@ internal class Typer( problems.add(BecauseArityMismatch(call.pos, 2)) } - return type to problems.toList() + return TypedVariants(type, problems.toList()) + } + + private sealed interface ITypedVariants { + val fnType: StaticType + val reasons: List + operator fun component1() = fnType + operator fun component2() = reasons } + private data class TypedVariants( + override val fnType: StaticType, + override val reasons: List, + ) : ITypedVariants + private data class TypedMembersAndExtensions( - val fnType: StaticType, - val reasons: List, + override val fnType: StaticType, + override val reasons: List, /** For each variant, the associated member shape or extension function name. */ val typedCandidates: List>> = emptyList(), - ) + ) : ITypedVariants private fun typeForDotHelper( t: CallTree, @@ -2658,19 +2713,22 @@ internal class Typer( // @staticExtension declaration. // Delegate to the handler for getStatic special calls. val possibleStaticTypeReceiver = thisArg.staticTypeContained - if (possibleStaticTypeReceiver != null && member is DotMember) { + if (possibleStaticTypeReceiver != null) { if (DEBUG) { console.log("Typing ${toStringViaTokenSink { dotHelper.renderTo(it) }} as if gets") } + return@typeForDotHelper typeForGets( - t.pos, - thisArg.pos, - possibleStaticTypeReceiver, - member.dotName, - dotHelper.extensions.filterIsInstance(), - // No information on whether this is an internal call, so presume it isn't. - // This means we need to ensure internal calls get resolved better before this point. - BuiltinFuns.getsFn, + pos = t.pos, + receiverTypePos = thisArg.pos, + receiverType = possibleStaticTypeReceiver, + member = member, + extensions = dotHelper.extensions.filterIsInstance(), + callable = BuiltinFuns.getsFn, + // gets expects the member name as an arg, but that's built into the dot helper + expectSymbolArg = false, + // calls to statics are reads of the static followed by a call to it. + curry = false, ) } @@ -2718,22 +2776,23 @@ internal class Typer( console.log("thisVariants=$thisVariants") } - val allThisVariants = mutableSetOf() - fun explodeThisType(thisVariant: NominalType) { + // Relates `this` variants to depths so that we can prefer method resolutions that are shallower. + val allThisVariants = mutableMapOf() + fun explodeThisType(thisVariant: NominalType, depth: Int) { if (thisVariant in allThisVariants) { return } - allThisVariants.add(thisVariant) + allThisVariants[thisVariant] = depth thisVariant.definition.superTypes.forEach { superTypeUnbound -> val typeMapper = typeBindingMapper(thisVariant) if (typeMapper != null) { - explodeThisType(typeMapper(superTypeUnbound) as NominalType) + explodeThisType(typeMapper(superTypeUnbound) as NominalType, depth + 1) } else { includeInvalid = true } } } - thisVariants.forEach(::explodeThisType) + thisVariants.forEach { explodeThisType(it, 0) } if (DEBUG) { console.log("allThisVariants=$allThisVariants") } @@ -2756,7 +2815,7 @@ internal class Typer( is SetMemberAccessor -> (it is PropertyShape && it.hasSetter) || (it is MethodShape && it.methodKind == MethodKind.Setter) - is BindMemberAccessor -> + is CallMemberAccessor -> it is MethodShape && // TODO: call of value of property with function type via getter it.methodKind == MethodKind.Normal @@ -2806,7 +2865,7 @@ internal class Typer( } } } - for (thisVariant in allThisVariants) { + for ((thisVariant, _) in allThisVariants) { val typeShape = thisVariant.definition as TypeShape if (DEBUG) { console.log("$thisVariant has members ${typeShape.members}") @@ -2818,20 +2877,31 @@ internal class Typer( // Checking for overloads only on actual member symbol fails should make the common case faster. // TODO Permit overloads with an actual matching member also? // TODO If not, validate against that somewhere else. - for (thisVariant in allThisVariants) { + for ((thisVariant, _) in allThisVariants) { val typeShape = thisVariant.definition as TypeShape processMembers(thisVariant, typeShape) { matchesOverload(member.dotName, it) } } } - // TODO: filter out masked member variants because overrides may narrow a signature. - + // filter out masked member variants because overrides may narrow a signature. val memberTypesGrouped = mutableMapOf>() members.forEach { (nominalType, memberShape) -> memberTypesGrouped.putMultiSet(nominalType, memberShape) } filterOutMaskedMembers(memberTypesGrouped) // Who is that masked method?!? Don't care. + // Finally, if we've got two methods that have identical signatures (modulo this-type), + // filter out the deeper ones. + // This has the effect of providing a single reliable resolution when we've got not-quite-diamond + // methods: + // interface I { f(): Void { console.log("I.f()") } } + // interface J extends I {} + // interface K { f(): Void { console.log("K.f()") } } + // class C extends J, K {} + // Here, C extends K via a shorter path than I, so K.f() is the implementation. + // Filtering out I.f() allows us to avoid confusing the type solver with + // irrelevant overloads. + filterOutDeeperMembers(memberTypesGrouped, allThisVariants, typeContext2) // Might need a type argument since the internal member accessors expect the this-type. val containingType = if (memberAccessor is InternalMemberAccessor) { @@ -2923,14 +2993,6 @@ internal class Typer( } } - if (memberAccessor is BindMemberAccessor && thisType != null) { - for (i in variants.indices) { - val (variantType, source) = variants[i] - val curriedT = BindMethodTypeHelper.curry(thisType, variantType) - variants[i] = curriedT to source - } - } - if (variants.isEmpty()) { val filteredOut = (rejectedMemberHolders - acceptedMemberHolders).toSet() explanations.add( @@ -2946,7 +3008,7 @@ internal class Typer( // When we have no reference types, show the core types we started with. val typeShapes = when (filteredOut.isNotEmpty()) { true -> filteredOut - false -> allThisVariants.map { + false -> allThisVariants.keys.map { // Up above, we already cast each definition as TypeShape, so should work here, too. it.definition as TypeShape }.toSet() @@ -3026,7 +3088,7 @@ internal class Typer( return ti.decisionType(extensionRef) } - private fun typeForComma(t: CallTree): Pair> { + private fun typeForComma(t: CallTree): TypedVariants { // Its variadic, but we can't use a variadic signature since its function // depends on the last argument, but not any of the preceding. // We want context to propagate through it to any generic function calls in @@ -3035,20 +3097,23 @@ internal class Typer( // where the number of AnyValue is one less than the number of arguments. val arity = t.size - 1 if (arity == 0) { - return functionType to listOf(BecauseArityMismatch(t.pos, 1)) + return TypedVariants(functionType, listOf(BecauseArityMismatch(t.pos, 1))) } val typeT = MkType.nominal(commaT) - return MkType.fnDetails( - typeFormals = listOf(commaT), - valueFormals = buildList { - repeat(arity - 1) { - add(FunctionType.ValueFormal(null, anyValueType, isOptional = false)) - } - add(FunctionType.ValueFormal(null, typeT, isOptional = false)) - }, - restValuesFormal = null, - returnType = typeT, - ) to emptyList() + return TypedVariants( + MkType.fnDetails( + typeFormals = listOf(commaT), + valueFormals = buildList { + repeat(arity - 1) { + add(FunctionType.ValueFormal(null, anyValueType, isOptional = false)) + } + add(FunctionType.ValueFormal(null, typeT, isOptional = false)) + }, + restValuesFormal = null, + returnType = typeT, + ), + emptyList(), + ) } companion object { @@ -3319,93 +3384,6 @@ private fun coerceCalleeTypeToIntersectionOfFunctionTypes( else -> OrType.emptyOrType } -/** - * `fn (Inp): Ret1 & fn (Inp): Ret2` -> `fn (Inp): Ret1 & Ret2` - */ -private fun distributeCalleeTypeThroughIntersection( - calleeType: StaticType, -): StaticType { - (calleeType as? AndType)?.let { calleeAndType -> - val membersIterator = calleeAndType.members.iterator() - if (!membersIterator.hasNext()) { return@let } - val member0 = membersIterator.next() as? FunctionType - ?: return@let - val returnTypes = mutableSetOf(member0.returnType) - while (membersIterator.hasNext()) { - val member = membersIterator.next() as? FunctionType - ?: return@let - // Check that the member is consistent with member0 in the following ways: - // - It has the same number of type parameters and those type parameters - // have the same upper bounds when member's is remapped to member0's - // corresponding . - // - It has the same arity and the value formals are the same when remapped. - // If those all pass, add the remapped return type, else bail. - // - // For example: - // fn>(x: T): U & - // fn>(x: A): B? - // That combines to the below: - // fn>(x: T): (U & (U?)) - // Since U is a subtype of U?, that return type is effectively U?. - if ( - member.typeFormals.size != member0.typeFormals.size || - member.valueFormals.size != member0.valueFormals.size || - (member.restValuesFormal == null) != (member0.restValuesFormal == null) - ) { - return@let - } - - val remappedValueFormals: List - val remappedRestValueFormal: StaticType? - val remappedReturnType: StaticType - if (member0.typeFormals.isEmpty()) { - remappedValueFormals = member.valueFormals - remappedRestValueFormal = member.restValuesFormal - remappedReturnType = member.returnType - } else { - val remapper = TypeBindingMapper( - buildMap { - for (i in member.typeFormals.indices) { - this[member.typeFormals[i].name] = MkType.nominal(member0.typeFormals[i]) - } - }, - ) - val typeFormalsConsistent = member.typeFormals.indices.all { i -> - val tf0 = member0.typeFormals[i] - val tf = member.typeFormals[i] - tf.variance == tf0.variance && - tf.superTypes.map { MkType.map(it, remapper) }.toSet() == - tf0.superTypes.toSet() - } - if (!typeFormalsConsistent) { - return@let - } - remappedValueFormals = member.valueFormals.map { f -> - f.copy(staticType = MkType.map(f.staticType, remapper)) - } - remappedRestValueFormal = member.restValuesFormal?.let { - MkType.map(it, remapper) - } - remappedReturnType = MkType.map(member.returnType, remapper) - } - if ( - member0.valueFormals != remappedValueFormals || - member0.restValuesFormal != remappedRestValueFormal - ) { - return@let - } - returnTypes.add(remappedReturnType) - } - return@distributeCalleeTypeThroughIntersection MkType.fnDetails( - typeFormals = member0.typeFormals, - valueFormals = member0.valueFormals, - restValuesFormal = member0.restValuesFormal, - returnType = MkType.and(returnTypes), - ) - } - return calleeType -} - internal val Tree.isMetadataValue: Boolean get() { if (this !is ValueLeaf) { return false } @@ -3516,3 +3494,17 @@ internal val Tree.needsTypeInfo get() = when (this) { is NameLeaf -> true is ValueLeaf -> true } + +internal fun isCallable(t: StaticType): Boolean = when (t) { + is AndType -> t.members.any { isCallable(it) } + InvalidType -> false + is OrType -> t.members.all { isCallable(it) || it is BubbleType } + BubbleType -> false + is FunctionType -> true + is NominalType -> withType( + hackMapOldStyleToNew(t), + fallback = { false }, + fn = { _, _, _ -> true }, + ) + TopType -> false +} diff --git a/frontend/src/commonTest/kotlin/lang/temper/frontend/CleanupTemporariesTest.kt b/frontend/src/commonTest/kotlin/lang/temper/frontend/CleanupTemporariesTest.kt index 34ea1b7a..192565fa 100644 --- a/frontend/src/commonTest/kotlin/lang/temper/frontend/CleanupTemporariesTest.kt +++ b/frontend/src/commonTest/kotlin/lang/temper/frontend/CleanupTemporariesTest.kt @@ -122,7 +122,7 @@ class CleanupTemporariesTest { | t4#0 = t3#0; | toLogOrNotToLog__0 = t4#0; | if (toLogOrNotToLog__0) { - | do_bind_log(console#0)("k") + | do_call_log(console#0, "k") | } | | ```, @@ -172,7 +172,7 @@ class CleanupTemporariesTest { | let toLogOrNotToLog__0; | toLogOrNotToLog__0 = randomBool(); | if (toLogOrNotToLog__0) { - | do_bind_log(t#0)("k") + | do_call_log(t#0, "k") | } | | ```, @@ -224,10 +224,10 @@ class CleanupTemporariesTest { | var t1#0; | t1#0 = randomBool(); | if (t1#0) { - | do_bind_log(t#0)("j") + | do_call_log(t#0, "j") | }; | if (t1#0) { - | do_bind_log(t#0)("k") + | do_call_log(t#0, "k") | }; | return__0 = void | @@ -272,7 +272,7 @@ class CleanupTemporariesTest { | t#3 = 2 | }; | x__2 = t#3; - | t#6 = do_bind_toString(x__2)(); + | t#6 = do_call_toString(x__2); | t#7 = t#6; | t#8 = t#7; | return__9 = t#8 @@ -354,7 +354,7 @@ class CleanupTemporariesTest { | } else { | x__2 = 2 | }; - | return__9 = do_bind_toString(x__2)(); + | return__9 = do_call_toString(x__2); | | ```, |} @@ -511,14 +511,14 @@ class CleanupTemporariesTest { | return__1 = getConsole(); | }); | @fn let f__0; - | f__0 = fn f /* return__2 */: Void { - | fn__0: do { - | if (randomBool()) { - | do_bind_log(console#0)("Random"); - | }; - | return__2 = void - | } - | }; + | f__0 = (@stay fn f /* return__2 */: Void { + | fn__0: do { + | if (randomBool()) { + | do_call_log(console#0, "Random"); + | }; + | return__2 = void + | } + | }); | f__0(); | return__0 = void | @@ -671,7 +671,7 @@ class CleanupTemporariesTest { | if (randomBool()) { | x__0 = "bar" | }; - | do_bind_log(t#0)(x__0); + | do_call_log(t#0, x__0); | | ```, |} @@ -713,7 +713,7 @@ class CleanupTemporariesTest { | t#1 = "bar" | }; | `test//`.x = t#1; - | do_bind_log(t#0)(`test//`.x); + | do_call_log(t#0, `test//`.x); | return__0 = void | | ```, @@ -743,9 +743,9 @@ class CleanupTemporariesTest { """ |{ | pseudoCodeAfter: ``` - | do_bind_log(doPure(@stay fn /* return__0 */: Console { + | do_call_log(doPure(@stay fn /* return__0 */: Console { | return__0 = getConsole(); - | }))(do_bind_toString(2)()) + | }), do_call_toString(2)) | | ```, |} @@ -792,14 +792,14 @@ class CleanupTemporariesTest { | return__1 = getConsole(); | }); | @fn let f__0; - | f__0 = fn f(a__0 /* aka a */: Int32, b__0 /* aka b */: Int32) /* return__2 */: Void { - | var t#1; - | fn__0: do { - | t#1 = do_bind_toString(a__0 + b__0)(); - | do_bind_log(console#0)(t#1); - | return__2 = void - | } - | }; + | f__0 = (@stay fn f(a__0 /* aka a */: Int32, b__0 /* aka b */: Int32) /* return__2 */: Void { + | var t#1; + | fn__0: do { + | t#1 = do_call_toString(a__0 + b__0); + | do_call_log(console#0, t#1); + | return__2 = void + | } + | }); | @fn let incr__0; | var x__0; | x__0 = 0; @@ -854,9 +854,9 @@ class CleanupTemporariesTest { | var y__0, t#1; | y__0 = 1; | t#1 = 2; - | do_bind_log(t#0)(do_bind_toString(y__0)()); + | do_call_log(t#0, do_call_toString(y__0)); | y__0 = t#1; - | do_bind_log(t#0)(do_bind_toString(y__0)()); + | do_call_log(t#0, do_call_toString(y__0)); | | ```, |} @@ -904,7 +904,7 @@ class CleanupTemporariesTest { | } | }; | x__0 = t#1; - | do_bind_log(t#0)(do_bind_toString(x__0)()); + | do_call_log(t#0, do_call_toString(x__0)); | return__0 = void | | ```, @@ -1329,12 +1329,12 @@ class CleanupTemporariesTest { | if (fail#3) { | break orelse#1; | }; - | t#2 = do_bind_toString(t#1)(); + | t#2 = do_call_toString(t#1); | t#3 = t#2 | } orelse { | t#3 = "Bubble" | }; - | t#4 = do_bind_log(console#0)(t#3); + | t#4 = do_call_log(console#0, t#3); | t#4 | | ```, @@ -1350,12 +1350,12 @@ class CleanupTemporariesTest { | if (fail#3) { | break orelse#1; | }; - | t#2 = do_bind_toString(t#1)(); + | t#2 = do_call_toString(t#1); | t#3 = t#2 | } orelse { | t#3 = "Bubble" | }; - | do_bind_log(t#0)(t#3); + | do_call_log(t#0, t#3); | | ``` |} @@ -1592,9 +1592,9 @@ class CleanupTemporariesTest { |{ | pseudoCodeAfter: ``` | label__0: do { - | do_bind_log(doPure(@stay fn /* return__0 */: Console { + | do_call_log(doPure(@stay fn /* return__0 */: Console { | return__0 = getConsole(); - | }))("foo") + | }), "foo") | } | | ``` @@ -1635,8 +1635,8 @@ class CleanupTemporariesTest { | postfixReturn#0 = i__0; | i__0 = i__0 + 1; | t#1 = postfixReturn#0; - | t#2 = do_bind_toString(t#1)(); - | do_bind_log(console#0)(t#2) + | t#2 = do_call_toString(t#1); + | do_call_log(console#0, t#2) | } | | ```, @@ -1651,7 +1651,7 @@ class CleanupTemporariesTest { | let postfixReturn#0; | postfixReturn#0 = i__0; | i__0 = i__0 + 1; - | do_bind_log(t#0)(do_bind_toString(postfixReturn#0)()) + | do_call_log(t#0, do_call_toString(postfixReturn#0)) | } | | ```, @@ -1694,8 +1694,8 @@ class CleanupTemporariesTest { | postfixReturn#0 = i__0; | i__0 = i__0 + 1; | t#1 = postfixReturn#0; - | t#2 = do_bind_toString(t#1)(); - | do_bind_log(console#0)(t#2) + | t#2 = do_call_toString(t#1); + | do_call_log(console#0, t#2) | }; | return__0 = void | @@ -1712,7 +1712,7 @@ class CleanupTemporariesTest { | let postfixReturn#0; | postfixReturn#0 = i__0; | i__0 = i__0 + 1; - | do_bind_log(t#0)(do_bind_toString(postfixReturn#0)()) + | do_call_log(t#0, do_call_toString(postfixReturn#0)) | }; | return__0 = void | @@ -1814,9 +1814,9 @@ class CleanupTemporariesTest { | @stay @imported(\(`test//other/`.f)) @fn let localName__0; | localName__0 = (fn f); |## This is not a dead store - | do_bind_log(doPure(@stay fn /* return__0 */: Console { + | do_call_log(doPure(@stay fn /* return__0 */: Console { | return__0 = getConsole(); - | }))(do_bind_aString((fn f)())()); + | }), do_call_aString((fn f)())); | | ```, | "consoleOutput": ``` @@ -2081,7 +2081,7 @@ class CleanupTemporariesTest { |if (fail#16) { | bubble() |}; - |do_bind_log(t#18)(getStatic(IntUtil__0, \plusOne)(t#19)); + |do_call_log(t#18, getStatic(IntUtil__0, \plusOne)(t#19)); | """.trimMargin().stripDoubleHashCommentLinesToPutCommentsInlineBelow(), r.pseudoCodeAfter, diff --git a/frontend/src/commonTest/kotlin/lang/temper/frontend/DefineStageTest.kt b/frontend/src/commonTest/kotlin/lang/temper/frontend/DefineStageTest.kt index ca2e3186..7d12aaaa 100644 --- a/frontend/src/commonTest/kotlin/lang/temper/frontend/DefineStageTest.kt +++ b/frontend/src/commonTest/kotlin/lang/temper/frontend/DefineStageTest.kt @@ -50,7 +50,7 @@ class DefineStageTest { |{ | define: { | body: ``` - | do_bind_verb(subject)(arg) + | do_call_verb(subject, arg) | | ```, | } @@ -111,7 +111,7 @@ class DefineStageTest { nym`get.next__13` = fn nym`get.next`(@impliedThis(C__1) this__3: C__1) /* return__14 */{ fn__15: do { do { - return__14 = do_ibind_f(type (C__1), this__3)() + 1; + return__14 = do_icall_f(type (C__1), this__3) + 1; break(\label, fn__15) } } @@ -154,7 +154,7 @@ class DefineStageTest { }); let c__22; c__22 = new C__1(); - do_bind_log(console#0)(do_get_i(c__22), do_get_x(c__22), do_get_x(c__22), do_bind_f(c__22)()); + do_call_log(console#0, do_get_i(c__22), do_get_x(c__22), do_get_x(c__22), do_call_f(c__22)); do { do_set_next(c__22, 42); 42 @@ -879,21 +879,21 @@ class DefineStageTest { | } | } | }); - | `test//`.prodWrap = fn prodWrap(i__1 /* aka i */: Int32, j__1 /* aka j */: List) /* return__1 */: Int32 { - | fn__1: do { - | i__1 * do { - | let subject#0; - | subject#0 = do_bind_get(j__1)(0); - | { - | if (subject#0 != null) { - | subject#0 - | } else { - | 1 + | `test//`.prodWrap = (@stay fn prodWrap(i__1 /* aka i */: Int32, j__1 /* aka j */: List) /* return__1 */: Int32 { + | fn__1: do { + | i__1 * do { + | let subject#0; + | subject#0 = do_call_get(j__1, 0); + | { + | if (subject#0 != null) { + | subject#0 + | } else { + | 1 + | } | } | } | } - | } - | }; + | }); | | ``` | }, @@ -932,8 +932,8 @@ class DefineStageTest { i__1 } }); - do_bind_log(console#0)(cat("f( )=", str(do_bind_toString(42)()))); - do_bind_log(console#0)(cat("f(1)=", str(do_bind_toString(1)()))); + do_call_log(console#0, cat("f( )=", str(do_call_toString(42)))); + do_call_log(console#0, cat("f(1)=", str(do_call_toString(1)))); ``` }, @@ -977,8 +977,8 @@ class DefineStageTest { i__4 } }; - do_bind_log(console#0)(cat("f(true )=", str(f__1(true)))); - do_bind_log(console#0)(cat("f(false)=", str(f__1(false)))); + do_call_log(console#0, cat("f(true )=", str(f__1(true)))); + do_call_log(console#0, cat("f(false)=", str(f__1(false)))); ``` }, @@ -2126,12 +2126,12 @@ class DefineStageTest { | as(2, Mystery); | as(3, type (List)); | error (list("`(Leaf`", "4", "`Leaf)`", "as")); - | do_bind_as(5)(type (Int32)); - | do_bind_as(6)(Mystery); - | do_bind_as(7)(); + | do_call_as(5, type (Int32)); + | do_call_as(6, Mystery); + | do_call_as(7); | do_get_as(8); - | as(do_bind_get(list(9))(0), Int32); - | as(do_bind_toString(10)(), String); + | as(do_call_get(list(9), 0), Int32); + | as(do_call_toString(10), String); | | ``` | }, @@ -2291,19 +2291,19 @@ class DefineStageTest { | Test__0 = type (Test); | @fn @test("- does / this : work?") let doesThisWork__0; | doesThisWork__0 = fn doesThisWork(test#0: Test) /* return__0 */: (Void | Bubble) { - | do_bind_assert(test#0)(true, @stay fn { + | do_call_assert(test#0, true, @stay fn { | "or what?" | }) | }; | @fn @test("does\tthis\nwork") let doesThisWork__1; | doesThisWork__1 = fn doesThisWork(test__0 /* aka test */: Test) /* return__1 */: (Void | Bubble) { - | do_bind_assert(test__0)(false, @stay fn { + | do_call_assert(test__0, false, @stay fn { | "or that" | }) | }; | @fn @test("again") let again__0; | again__0 = fn again(t__0 /* aka t */: Test) /* return__2 */: (Void | Bubble) { - | do_bind_assert(t__0)(true, @stay fn { + | do_call_assert(t__0, true, @stay fn { | "whatever" | }) | }; @@ -2336,8 +2336,8 @@ class DefineStageTest { | actual#0 = 4; | let expected#0; | expected#0 = 3; - | do_bind_assert(test#0)(false, fn { - | cat("expected num == (", do_bind_toString(3)(), ") not (", do_bind_toString(4)(), ")") + | do_call_assert(test#0, false, @stay fn { + | cat("expected num == (", do_call_toString(3), ") not (", do_call_toString(4), ")") | }) | }; | }; @@ -2345,7 +2345,7 @@ class DefineStageTest { | ha__0 = fn ha(test#1: Test) /* return__1 */: (Void | Bubble) { | let condition__0; | condition__0 = false; - | do_bind_assert(test#1)(false, @stay fn { + | do_call_assert(test#1, false, @stay fn { | "expected condition" | }); | }; @@ -2554,9 +2554,9 @@ class DefineStageTest { }@imported(\(`std//regex/`.End)) End__0 = `std//regex/`.End, ${ listOf( // r1 = rgx(list("a.b*"), list()) - """r1 = do_bind_compiled(new Sequence(list(new CodePoints("a"), Dot__0, new Repeat(new CodePoints("b"), 0, null, false))))()""", + """r1 = do_call_compiled(new Sequence(list(new CodePoints("a"), Dot__0, new Repeat(new CodePoints("b"), 0, null, false))))""", // r2 = rgx(list("a.\u{24}{b}*"), list()) - """r2 = do_bind_compiled(new Sequence(list(new CodePoints("a"), Dot__0, End__0, new CodePoints("{b"), new Repeat(new CodePoints("}"), 0, null, false))))()""", + """r2 = do_call_compiled(new Sequence(list(new CodePoints("a"), Dot__0, End__0, new CodePoints("{b"), new Repeat(new CodePoints("}"), 0, null, false))))""", """r3 = rgx(list("(?/g)a.b*"), list())""", """b = r3""", // Here, r4 and r5 interpolate regex objects, but we don't support those yet. @@ -2591,9 +2591,9 @@ class DefineStageTest { | End__0 = `std//regex/`.End; | let r1__0; |## Types have been inlined into `new` operators - | r1__0 = do_bind_compiled(new Sequence(list(new CodePoints("a"), Dot__0, new Repeat(new CodePoints("b"), 0, null, false))))(); + | r1__0 = do_call_compiled(new Sequence(list(new CodePoints("a"), Dot__0, new Repeat(new CodePoints("b"), 0, null, false)))); | let r2__0; - | r2__0 = do_bind_compiled(new Sequence(list(new CodePoints("a"), Dot__0, End__0, new CodePoints("{b"), new Repeat(new CodePoints("}"), 0, null, false))))(); + | r2__0 = do_call_compiled(new Sequence(list(new CodePoints("a"), Dot__0, End__0, new CodePoints("{b"), new Repeat(new CodePoints("}"), 0, null, false)))); | let r3__0; |## (/g) unrecognized in rgx(list("(?/g)a.b*"), list()); | r3__0 = error (UnrecognizedToken); @@ -2605,13 +2605,13 @@ class DefineStageTest { | let r5__0; | r5__0 = error (UnrecognizedToken); | let r6__0; - | r6__0 = do_bind_compiled(new Sequence__0(list(new CodePoints__0("a"), new Repeat__0(new CodePoints__0("."), 0, null, false))))(); + | r6__0 = do_call_compiled(new Sequence__0(list(new CodePoints__0("a"), new Repeat__0(new CodePoints__0("."), 0, null, false)))); | let r7__0; - | r7__0 = do_bind_compiled(new Sequence(list(new CodePoints("a"), Dot, new Repeat(new CodePoints("b"), 0, null))))(); + | r7__0 = do_call_compiled(new Sequence(list(new CodePoints("a"), Dot, new Repeat(new CodePoints("b"), 0, null)))); | let s__0; | s__0 = "[a]"; | let r8__0; - | r8__0 = do_bind_compiled(new Sequence__0(list(Dot__0, new CodePoints__0("[a]"), Dot__0)))(); + | r8__0 = do_call_compiled(new Sequence__0(list(Dot__0, new CodePoints__0("[a]"), Dot__0))); | | ``` | }, @@ -3097,9 +3097,9 @@ class DefineStageTest { |## Here are members for the generated JSON adapter class | PointJsonAdapter__0 extends JsonAdapter; | @visibility(\public) @fn @stay @fromType(PointJsonAdapter__0) let encodeToJson__0; - | encodeToJson__0 = fn (@impliedThis(PointJsonAdapter__0) this__0: PointJsonAdapter__0, x__1: Point__0, p__0: JsonProducer) /* return__0 */: Void { - | do_bind_encodeToJson(x__1)(p__0) - | }; + | encodeToJson__0 = (@stay fn (@impliedThis(PointJsonAdapter__0) this__0: PointJsonAdapter__0, x__1: Point__0, p__0: JsonProducer) /* return__0 */: Void { + | do_call_encodeToJson(x__1, p__0) + | }); | @visibility(\public) @fn @stay @fromType(PointJsonAdapter__0) let decodeFromJson__0; | decodeFromJson__0 = fn (@impliedThis(PointJsonAdapter__0) this__1: PointJsonAdapter__0, t__0: JsonSyntaxTree, ic__0: InterchangeContext) /* return__1 */: (Point__0 | Bubble) { | getStatic(Point__0, \decodeFromJson)(t__0, ic__0) @@ -3153,24 +3153,24 @@ class DefineStageTest { |## Here is the encodeToJson method added to point. | @visibility(\public) @fn @stay @fromType(Point__0) let encodeToJson__1; | encodeToJson__1 = fn (@impliedThis(Point__0) this__7: Point__0, p__1: JsonProducer) /* return__7 */: Void { - | do_bind_startObject(p__1)(); - | do_bind_objectKey(p__1)("x"); + | do_call_startObject(p__1); + | do_call_objectKey(p__1, "x"); |## `this` in the generated expression `this.x` got rewritten to `this__7`, after |## the regular type processing pass adds that implied parameter. - | do_bind_int32Value(p__1)(getp(x__0, this__7)); - | do_bind_objectKey(p__1)("y"); - | do_bind_int32Value(p__1)(getp(y__0, this__7)); - | do_bind_endObject(p__1)(); + | do_call_int32Value(p__1, getp(x__0, this__7)); + | do_call_objectKey(p__1, "y"); + | do_call_int32Value(p__1, getp(y__0, this__7)); + | do_call_endObject(p__1); | }; | @static @visibility(\public) @fn @stay @fromType(Point__0) let decodeFromJson__1; - | decodeFromJson__1 = fn (t__1: JsonSyntaxTree, ic__1: InterchangeContext) /* return__8 */: (Point__0 | Bubble) { - | let obj__0; - | obj__0 = as(t__1, JsonObject); - | let x__3: Int32, y__2: Int32; - | x__3 = do_bind_asInt32(as(do_bind_propertyValueOrBubble(obj__0)("x"), JsonNumeric))(); - | y__2 = do_bind_asInt32(as(do_bind_propertyValueOrBubble(obj__0)("y"), JsonNumeric))(); - | new Point__0(x__3, y__2) - | }; + | decodeFromJson__1 = (@stay fn (t__1: JsonSyntaxTree, ic__1: InterchangeContext) /* return__8 */: (Point__0 | Bubble) { + | let obj__0; + | obj__0 = as(t__1, JsonObject); + | let x__3: Int32, y__2: Int32; + | x__3 = do_call_asInt32(as(do_call_propertyValueOrBubble(obj__0, "x"), JsonNumeric)); + | y__2 = do_call_asInt32(as(do_call_propertyValueOrBubble(obj__0, "y"), JsonNumeric)); + | new Point__0(x__3, y__2) + | }); | @static @visibility(\public) @fn @stay @fromType(Point__0) let jsonAdapter__0; | jsonAdapter__0 = (@stay fn /* return__9 */: (JsonAdapter) { | new PointJsonAdapter__0() @@ -3337,19 +3337,19 @@ class DefineStageTest { | accumulator#0 = new TheCount__0(); |## We inlined the body here. | do { - | do_bind_appendSafe(accumulator#0)("Zero: "); + | do_call_appendSafe(accumulator#0, "Zero: "); |## Unsafe interpolations become regular appends. - | do_bind_append(accumulator#0)(0); - | do_bind_appendSafe(accumulator#0)("\nOne: "); - | do_bind_append(accumulator#0)(1); - | do_bind_appendSafe(accumulator#0)("\n"); + | do_call_append(accumulator#0, 0); + | do_call_appendSafe(accumulator#0, "\nOne: "); + | do_call_append(accumulator#0, 1); + | do_call_appendSafe(accumulator#0, "\n"); |## The loop becomes just a regular forEach application and the content are appends. - | do_bind_forEach(list(2, 3, 4))(fn (n__0) { - | do_bind_appendSafe(accumulator#0)(" "); - | do_bind_append(accumulator#0)(n__0); + | do_call_forEach(list(2, 3, 4), fn (n__0) { + | do_call_appendSafe(accumulator#0, " "); + | do_call_append(accumulator#0, n__0); | }); - | do_bind_appendSafe(accumulator#0)("!\nFive: "); - | do_bind_append(accumulator#0)(5); + | do_call_appendSafe(accumulator#0, "!\nFive: "); + | do_call_append(accumulator#0, 5); | }; |## We inject a `.accumulated` fetch for the block result | do_get_accumulated(accumulator#0) @@ -3358,9 +3358,9 @@ class DefineStageTest { | let accumulator#1; | accumulator#1 = new TheCount__0(); | do { - | do_bind_append(accumulator#1)(6); - | do_bind_appendSafe(accumulator#1)(", "); - | do_bind_append(accumulator#1)(7) + | do_call_append(accumulator#1, 6); + | do_call_appendSafe(accumulator#1, ", "); + | do_call_append(accumulator#1, 7) | }; | do_get_accumulated(accumulator#1) | } @@ -3414,20 +3414,20 @@ class DefineStageTest { | accumulator#0 = new TheCount__0(); |## We inlined the body here. | do { - | do_bind_appendSafe(accumulator#0)("Zero: "); + | do_call_appendSafe(accumulator#0, "Zero: "); |## Unsafe interpolations become regular appends. - | do_bind_append(accumulator#0)(0); - | do_bind_appendSafe(accumulator#0)("\nOne: "); - | do_bind_append(accumulator#0)(1); - | do_bind_appendSafe(accumulator#0)("\nTwo: "); - | do_bind_append(accumulator#0)(2); - | do_bind_appendSafe(accumulator#0)("\nThree: "); - | do_bind_append(accumulator#0)(3); - | do_bind_appendSafe(accumulator#0)("\nF\\our: "); - | do_bind_append(accumulator#0)(4); - | do_bind_appendSafe(accumulator#0)("\nFive: "); - | do_bind_append(accumulator#0)(5); - | do_bind_appendSafe(accumulator#0)("\n") + | do_call_append(accumulator#0, 0); + | do_call_appendSafe(accumulator#0, "\nOne: "); + | do_call_append(accumulator#0, 1); + | do_call_appendSafe(accumulator#0, "\nTwo: "); + | do_call_append(accumulator#0, 2); + | do_call_appendSafe(accumulator#0, "\nThree: "); + | do_call_append(accumulator#0, 3); + | do_call_appendSafe(accumulator#0, "\nF\\our: "); + | do_call_append(accumulator#0, 4); + | do_call_appendSafe(accumulator#0, "\nFive: "); + | do_call_append(accumulator#0, 5); + | do_call_appendSafe(accumulator#0, "\n") | }; |## We inject a `.accumulated` fetch for the block result | do_get_accumulated(accumulator#0) @@ -3436,9 +3436,9 @@ class DefineStageTest { | let accumulator#1; | accumulator#1 = new TheCount__0(); | do { - | do_bind_append(accumulator#1)(6); - | do_bind_appendSafe(accumulator#1)(", "); - | do_bind_append(accumulator#1)(7) + | do_call_append(accumulator#1, 6); + | do_call_appendSafe(accumulator#1, ", "); + | do_call_append(accumulator#1, 7) | }; | do_get_accumulated(accumulator#1) | } @@ -3469,9 +3469,9 @@ class DefineStageTest { | let accumulator#0; | accumulator#0 = new HtmlBuilder(); | do { - | do_bind_appendSafe(accumulator#0)(raw "") + | do_call_appendSafe(accumulator#0, raw "") | }; | do_get_accumulated(accumulator#0) | } diff --git a/frontend/src/commonTest/kotlin/lang/temper/frontend/GenerateCodeStageTest.kt b/frontend/src/commonTest/kotlin/lang/temper/frontend/GenerateCodeStageTest.kt index 368172d0..efc61a11 100644 --- a/frontend/src/commonTest/kotlin/lang/temper/frontend/GenerateCodeStageTest.kt +++ b/frontend/src/commonTest/kotlin/lang/temper/frontend/GenerateCodeStageTest.kt @@ -161,7 +161,7 @@ class GenerateCodeStageTest { | generateCode: { | body: ``` | let return__0; - | do_bind_log(getConsole())("Done once"); + | do_call_log(getConsole(), "Done once"); | return__0 = void | | ``` @@ -207,6 +207,20 @@ class GenerateCodeStageTest { """, ) + @Test + fun simpleMethodCall() = assertModuleAtStage( + input = """ + |1.toString() + """.trimMargin(), + stage = Stage.Run, + moduleResultNeeded = true, + want = """ + |{ + | run: ["1", "String"], + |} + """.trimMargin(), + ) + @Suppress("SpellCheckingInspection") // getprop/setprop @Test fun getterSettersFinal() = assertModuleAtStage( @@ -492,12 +506,12 @@ class GenerateCodeStageTest { | @fn @reach(\none) let f__0; | f__0 = (@stay fn f(s__0 /* aka s */: String) /* return__1 */: Void { | var t#0, t#1, t#2; - | cat(do_bind_toString(0)()); - | t#0 = do_bind_toString(0)(); + | cat(do_call_toString(0)); + | t#0 = do_call_toString(0); | cat(s__0, t#0); - | t#1 = do_bind_toString(0)(); + | t#1 = do_call_toString(0); | cat(s__0, t#1, s__0); - | t#2 = do_bind_toString(0)(); + | t#2 = do_call_toString(0); | cat(s__0, s__0, t#2, s__0); | return__1 = void | }) @@ -527,7 +541,7 @@ class GenerateCodeStageTest { | if (isNull(a__0)) { | t#1 = "null" | } else { - | t#0 = do_bind_toString(notNull(a__0))(); + | t#0 = do_call_toString(notNull(a__0)); | t#1 = t#0 | }; | if (!isNull(a__0)) { @@ -537,7 +551,7 @@ class GenerateCodeStageTest { | } else { | t#3 = -1 | }; - | t#2 = do_bind_toString(t#3)(); + | t#2 = do_call_toString(t#3); | return__1 = cat(s__0, t#1, t#2) | }) | @@ -799,7 +813,7 @@ class GenerateCodeStageTest { | console#0 = getConsole(); | @fn let hi__0; | hi__0 = (@stay fn hi(name__0 /* aka name */: String) /* return__1 */: Void { - | do_bind_log(console#0)(name__0); + | do_call_log(console#0, name__0); | return__1 = void | }); | hi__0(\nom, "Alice") @@ -989,8 +1003,8 @@ class GenerateCodeStageTest { | @method(\p) @setter @visibility(\public) @fn @stay @fromType(C__0) let nym`set.p__1`; | nym`set.p__1` = (@stay fn nym`set.p`(@impliedThis(C__0) this__0: C__0, newValue__0 /* aka newValue */: Int32) /* return__1 */: Void { | var t#0; - | t#0 = do_bind_toString(newValue__0)(10); - | do_bind_log(console#0)(cat("Assigned ", t#0)); + | t#0 = do_call_toString(newValue__0, 10); + | do_call_log(console#0, cat("Assigned ", t#0)); | return__1 = void | }); | @fn @method(\constructor) @visibility(\public) @stay @fromType(C__0) let constructor__0; @@ -1043,7 +1057,7 @@ class GenerateCodeStageTest { body: ``` @fn @reach(\none) let funny__0: (fn (Int32): String); funny__0 = (@stay fn funny(n__0 /* aka n */) /* return__0 */{ - return__0 = do_bind_toString(n__0)() + return__0 = do_call_toString(n__0) }) ``` @@ -1459,7 +1473,7 @@ class GenerateCodeStageTest { | console#0 = getConsole(); | @fn let b__0, @fn c__0; | b__0 = (@stay fn b /* return__0 */: Void { - | do_bind_log(console#0)("hi"); + | do_call_log(console#0, "hi"); | return__0 = void | }); | let a__0; @@ -1470,7 +1484,7 @@ class GenerateCodeStageTest { | return__1 = void | }); | @reach(\none) let e__0; - | do_bind_get(a__0)(0); + | do_call_get(a__0, 0); | c__0(void); | e__0 = void; | c__0(void) @@ -1570,7 +1584,7 @@ class GenerateCodeStageTest { |{ | generateCode: { | body: ``` - | do_bind_log(getConsole())("Logged") + | do_call_log(getConsole(), "Logged") | | ``` | }, @@ -1660,7 +1674,7 @@ class GenerateCodeStageTest { | }; | @visibility(\public) @fn let h__0 = fn h(@impliedThis(C__0) this__1: C__0, i__3 /* aka i */: Int) /* return__3 */: (Int) { | fn__3: do { - | 2 * f__0(i__3) * fp__0(i__3) * do_ibind_g(type (C__0), this(C__0))(i__3) * do_ibind_g(type (C__0), this(C__0))(i__3) + | 2 * f__0(i__3) * fp__0(i__3) * do_icall_g(type (C__0), this(C__0), i__3) * do_icall_g(type (C__0), this(C__0), i__3) | } | }; | @fn @static @visibility(\public) let g2__0 = fn g2(i__4 /* aka i */: Int) /* return__4 */: (Int) { @@ -1687,7 +1701,7 @@ class GenerateCodeStageTest { | }); | g3__0 = fn g3(i__6 /* aka i */: Int) /* return__7 */: (Int) { | fn__6: do { - | 2 * do_bind_f(C__0)(i__6) * do_bind_g(new C__0())(i__6) * do_get_a(C__0) * do_get_ap(C__0) + | 2 * do_call_f(C__0, i__6) * do_call_g(new C__0(), i__6) * do_get_a(C__0) * do_get_ap(C__0) | } | }; | @@ -1726,12 +1740,12 @@ class GenerateCodeStageTest { | } | }; | @visibility(\public) @fn @stay @fromType(C__0) let h__0; - | h__0 = fn h(@impliedThis(C__0) this__1: C__0, i__3 /* aka i */: Int32) /* return__3 */: Int32 { - | void; - | fn__3: do { - | return__3 = 2 *(fn f)(i__3) *(fn fp)(i__3) * do_ibind_g(type (C__0), this__1)(i__3) * do_ibind_g(type (C__0), this__1)(i__3) - | } - | }; + | h__0 = (@stay fn h(@impliedThis(C__0) this__1: C__0, i__3 /* aka i */: Int32) /* return__3 */: Int32 { + | void; + | fn__3: do { + | return__3 = 2 *(fn f)(i__3) *(fn fp)(i__3) * do_icall_g(type (C__0), this__1, i__3) * do_icall_g(type (C__0), this__1, i__3) + | } + | }); | @fn @static @visibility(\public) @stay @fromType(C__0) let g2__0; | g2__0 = fn g2(i__4 /* aka i */: Int32) /* return__4 */: Int32 { | void; @@ -1753,12 +1767,12 @@ class GenerateCodeStageTest { | setp(b__0, this__2, t#1); | return__6 = void | }); - | g3__0 = fn g3(i__6 /* aka i */: Int32) /* return__7 */: Int32 { - | void; - | fn__6: do { - | return__7 = 2 * getStatic(C__0, \f)(i__6) * do_bind_g(new C__0())(i__6) * getStatic(C__0, \a) * getStatic(C__0, \ap) - | } - | } + | g3__0 = (@stay fn g3(i__6 /* aka i */: Int32) /* return__7 */: Int32 { + | void; + | fn__6: do { + | return__7 = 2 * getStatic(C__0, \f)(i__6) * do_call_g(new C__0(), i__6) * getStatic(C__0, \a) * getStatic(C__0, \ap) + | } + | }) | | ``` | }, @@ -1789,7 +1803,7 @@ class GenerateCodeStageTest { | }); | @visibility(\public) @fn @stay @fromType(C__0) let h__0; | h__0 = (@stay fn h(@impliedThis(C__0) this__1: C__0, i__3 /* aka i */: Int32) /* return__3 */: Int32 { - | return__3 = 2 *(fn f)(i__3) *(fn fp)(i__3) * do_ibind_g(type (C__0), this__1)(i__3) * do_ibind_g(type (C__0), this__1)(i__3) + | return__3 = 2 *(fn f)(i__3) *(fn fp)(i__3) * do_icall_g(type (C__0), this__1, i__3) * do_icall_g(type (C__0), this__1, i__3) | }); | @fn @static @visibility(\public) @stay @fromType(C__0) let g2__0; | g2__0 = (@stay fn g2(i__4 /* aka i */: Int32) /* return__4 */: Int32 { @@ -1808,7 +1822,7 @@ class GenerateCodeStageTest { | return__6 = void | }); | g3__0 = (@stay fn g3(i__6 /* aka i */: Int32) /* return__7 */: Int32 { - | return__7 = 2 * getStatic(C__0, \f)(i__6) * do_bind_g(new C__0())(i__6) * getStatic(C__0, \a) * getStatic(C__0, \ap) + | return__7 = 2 * getStatic(C__0, \f)(i__6) * do_call_g(new C__0(), i__6) * getStatic(C__0, \a) * getStatic(C__0, \ap) | }) | | ``` @@ -1910,7 +1924,7 @@ class GenerateCodeStageTest { | @reach(\none) let unreachableInt__0; | unreachableInt__0 = 2; | conditionallyExportReachable__0 = (@stay fn conditionallyExportReachable /* return__0 */: Void { - | do_bind_log(console#0)(""); + | do_call_log(console#0, ""); | return__0 = void | }); | `test//`.exportedFunction = (@stay fn exportedFunction(b__0 /* aka b */: Boolean) /* return__1 */: Void { @@ -1920,24 +1934,24 @@ class GenerateCodeStageTest { | return__1 = void | }); | transitivelyTestReachable__0 = (@stay fn transitivelyTestReachable /* return__2 */: Void { - | do_bind_log(console#0)(""); + | do_call_log(console#0, ""); | return__2 = void | }); | exportAndTestReachable__0 = (@stay fn exportAndTestReachable /* return__3 */: Void { - | do_bind_log(console#0)(""); + | do_call_log(console#0, ""); | return__3 = void | }); | transitivelyInitReachable__0 = (@stay fn transitivelyInitReachable /* return__4 */: Void { - | do_bind_log(console#0)(""); + | do_call_log(console#0, ""); | return__4 = void | }); | initReachable__0 = (@stay fn initReachable /* return__5 */: Void { | transitivelyInitReachable__0(); - | do_bind_log(console#0)(""); + | do_call_log(console#0, ""); | return__5 = void | }); | unreachableFunction__0 = (@stay fn unreachableFunction /* return__6 */: Void { - | do_bind_log(console#0)(""); + | do_call_log(console#0, ""); | return__6 = void | }); | @fn @method(\constructor) @visibility(\public) @stay @fromType(UsedOnlyAsPropertyType__0) let constructor__0; @@ -2074,14 +2088,14 @@ class GenerateCodeStageTest { | fn__0: do { | let generator__0: SafeGenerator; | generator__0 = factory__0();${ - "" // Since it's a SafeGenerator, no error checking around do_bind_next(...)() + "" // Since it's a SafeGenerator, no error checking around do_call_next(...) } - | do_bind_next(generator__0)(); - | do_bind_log(console#0)(","); - | do_bind_next(generator__0)(); - | do_bind_log(console#0)(","); - | do_bind_next(generator__0)(); - | do_bind_log(console#0)("."); + | do_call_next(generator__0); + | do_call_log(console#0, ","); + | do_call_next(generator__0); + | do_call_log(console#0, ","); + | do_call_next(generator__0); + | do_call_log(console#0, "."); | return__1 = void | } | }; @@ -2089,13 +2103,13 @@ class GenerateCodeStageTest { "" // Adapt call specialized to adaptGeneratorFnSafe } | return__2 = adaptGeneratorFnSafe(@wrappedGeneratorFn fn /* return__3 */: (GeneratorResult) implements GeneratorFn { - | do_bind_log(console#0)("First"); + | do_call_log(console#0, "First"); | yield(); - | do_bind_log(console#0)("Second"); + | do_call_log(console#0, "Second"); | yield(); - | do_bind_log(console#0)("Third"); + | do_call_log(console#0, "Third"); | yield(); - | do_bind_log(console#0)("Fourth"); + | do_call_log(console#0, "Fourth"); | return__3 = implicits.doneResult() | }) | }); @@ -2156,14 +2170,14 @@ class GenerateCodeStageTest { | fn__0: do { | let generator__0: SafeGenerator; | generator__0 = factory__0(); - |## Since it's a SafeGenerator, no error checking around do_bind_next(...)() - | do_bind_next(generator__0)(); - | do_bind_log(console#0)("Ran once"); - | do_bind_next(generator__0)(); - | do_bind_log(console#0)("Ran twice"); - | do_bind_next(generator__0)(); - | do_bind_log(console#0)("Ran thrice"); - | do_bind_close(generator__0)(); + |## Since it's a SafeGenerator, no error checking around do_call_next(...) + | do_call_next(generator__0); + | do_call_log(console#0, "Ran once"); + | do_call_next(generator__0); + | do_call_log(console#0, "Ran twice"); + | do_call_next(generator__0); + | do_call_log(console#0, "Ran thrice"); + | do_call_close(generator__0); | return__1 = void | } | }; @@ -2174,9 +2188,9 @@ class GenerateCodeStageTest { |## The interpreter needs to distinguish a legit return result with the result from a yield. | void; | while (true) { - | do_bind_log(console#0)("Pausing"); + | do_call_log(console#0, "Pausing"); | yield(); - | do_bind_log(console#0)("Resuming"); + | do_call_log(console#0, "Resuming"); | } | }) | }); @@ -2290,7 +2304,7 @@ class GenerateCodeStageTest { | fn__0 = (@stay fn /* return__0 */{ | let fn__1; | fn__1 = (@wrappedGeneratorFn fn /* return__1 */: (GeneratorResult) implements GeneratorFn { - | do_bind_complete(pb__0)("Hello, World!"); + | do_call_complete(pb__0, "Hello, World!"); | return__1 = (fn doneResult)() | }); | return__0 = adaptGeneratorFnSafe(fn__1) @@ -2300,7 +2314,7 @@ class GenerateCodeStageTest { | if (fail#0) { | bubble() | }; - | do_bind_log(t#0)(t#1) + | do_call_log(t#0, t#1) | | ``` | } @@ -2977,13 +2991,13 @@ class GenerateCodeStageTest { | bubble() | } | } else if (index__0 == 1) { - | return__1 = hs(fail#1, do_bind_get(nums__0)(index__0)); + | return__1 = hs(fail#1, do_call_get(nums__0, index__0)); | if (fail#1) { | bubble() | } | } else { | orelse#0: { - | t#0 = hs(fail#2, do_bind_get(nums__0)(index__0)); + | t#0 = hs(fail#2, do_call_get(nums__0, index__0)); | if (fail#2) { | break orelse#0; | }; @@ -3046,14 +3060,14 @@ class GenerateCodeStageTest { | var j__0; | j__0 = do_get_end(s__0); | while(i__0 < j__0, fn { - | j__0 = do_bind_prev(s__0)(j__0); - | if(do_bind_get(s__0)(i__0) != do_bind_get(s__0)(j__0), fn { + | j__0 = do_call_prev(s__0, j__0); + | if(do_call_get(s__0, i__0) != do_call_get(s__0, j__0), fn { | do { | return__0 = false; | break(\label, fn__0) | } | }); - | i__0 = do_bind_next(s__0)(i__0); + | i__0 = do_call_next(s__0, i__0); | }); | do { | return__0 = true; @@ -3061,34 +3075,34 @@ class GenerateCodeStageTest { | } | } | }; - | (do_bind_isPalindrome[stringIsPalindrome__0])("step on no pets")() + | (do_call_isPalindrome[stringIsPalindrome__0])("step on no pets") | | ``` | }, | type: { | body: ``` | let return__1, @fn @extension("isPalindrome") stringIsPalindrome__0; - | stringIsPalindrome__0 = fn stringIsPalindrome(s__0 /* aka s */: String) /* return__0 */: Boolean { - | var t#0, t#1, t#2; - | fn__0: do { - | var i__0; - | i__0 = getStatic(String, \begin); - | var j__0; - | t#0 = do_get_end(s__0); - | j__0 = t#0; - | while (i__0 < j__0) { - | t#1 = do_bind_prev(s__0)(j__0); - | j__0 = t#1; - | if (do_bind_get(s__0)(i__0) != do_bind_get(s__0)(j__0)) { - | return__0 = false; - | break fn__0; + | stringIsPalindrome__0 = (@stay fn stringIsPalindrome(s__0 /* aka s */: String) /* return__0 */: Boolean { + | var t#0, t#1, t#2; + | fn__0: do { + | var i__0; + | i__0 = getStatic(String, \begin); + | var j__0; + | t#0 = do_get_end(s__0); + | j__0 = t#0; + | while (i__0 < j__0) { + | t#1 = do_call_prev(s__0, j__0); + | j__0 = t#1; + | if (do_call_get(s__0, i__0) != do_call_get(s__0, j__0)) { + | return__0 = false; + | break fn__0; + | }; + | t#2 = do_call_next(s__0, i__0); + | i__0 = t#2 | }; - | t#2 = do_bind_next(s__0)(i__0); - | i__0 = t#2 - | }; - | return__0 = true - | } - | }; + | return__0 = true + | } + | }); | return__1 = stringIsPalindrome__0("step on no pets");${ "" // The do_call_isPalindrome got rewritten to the direct function reference } @@ -3254,14 +3268,14 @@ class GenerateCodeStageTest { | let accumulator#0: StringBuilder; | accumulator#0 = new StringBuilder (); | do { - | do_bind_append(accumulator#0)("Hello, World"); + | do_call_append(accumulator#0, "Hello, World"); | for((let guest of guests), fn { - | do_bind_append(accumulator#0)(", and "); - | do_bind_append(accumulator#0)(str(guest)); + | do_call_append(accumulator#0, ", and "); + | do_call_append(accumulator#0, str(guest)); | }); - | do_bind_append(accumulator#0)("!"); + | do_call_append(accumulator#0, "!"); | }; - | do_bind_toString(accumulator#0)() + | do_call_toString(accumulator#0) | } | | ``` @@ -3359,7 +3373,8 @@ class GenerateCodeStageTest { input = """ |export let Act = fn (i: Int): Void; |export let hi(i: Int, act: Act?): Void { - | if (i != 0 && act != null) { + | if (i == 0 || act != null) { + | // `||` means act could be null here. | act(i); | } |} @@ -3367,7 +3382,7 @@ class GenerateCodeStageTest { want = """ |{ | run: "void: Void", - | errors: ["Expected function type, but got Invalid!"], + | errors: ["Expected function type, but got (fn (Int32): Void)?!"], |} """.trimMargin(), ) @@ -3401,7 +3416,7 @@ class GenerateCodeStageTest { | d__0 = 4; | @imported(\(`test//nums/`.e)) @reach(\none) let e__0; | e__0 = 5; - | do_bind_log(getConsole())(do_bind_toString(15)()) + | do_call_log(getConsole(), do_call_toString(15)) | | ``` | } @@ -3449,12 +3464,12 @@ class GenerateCodeStageTest { | if (isNull(actual#0)) { | t#2 = "null" | } else { - | t#1 = do_bind_toString(notNull(actual#0))(); + | t#1 = do_call_toString(notNull(actual#0)); | t#2 = t#1 | }; | return__1 = cat("expected c0.optionalString == (", "", ") not (", t#2, ")") | }); - | do_bind_assert(test#0)(t#0, fn__0); + | do_call_assert(test#0, t#0, fn__0); | return__0 = void | }) | @@ -3502,7 +3517,7 @@ class GenerateCodeStageTest { | if (isNull(subject#0)) { | null | } else { - | do_bind_toString(notNull(subject#0))() + | do_call_toString(notNull(subject#0)) | } | } | ?? "NULL" @@ -3531,7 +3546,7 @@ class GenerateCodeStageTest { | if (isNull(t#4)) { | t#5 = null | } else { - | t#2 = do_bind_toString(notNull(t#4))(); + | t#2 = do_call_toString(notNull(t#4)); | t#5 = t#2 | }; | if (!isNull(t#5)) { @@ -3561,15 +3576,14 @@ class GenerateCodeStageTest { | body: ``` | @fn @reach(\none) let maybeLength__0; | maybeLength__0 = (@stay fn maybeLength(a__0 /* aka a */: String?) /* return__0 */: (Int32?) { - | var t#0; + | var t#0, t#1; | if (isNull(a__0)) { | return__0 = null | } else { |## In this branch, a is aliased to a#0 and is known to be not null. - | let a#0; - | a#0 = notNull(a__0); - | t#0 = do_get_end(a#0); - | return__0 = do_bind_countBetween(a#0)(getStatic(String, \begin), t#0) + | t#1 = notNull(a__0); + | t#0 = do_get_end(t#1); + | return__0 = do_call_countBetween(t#1, getStatic(String, \begin), t#0) | } | }) | @@ -3620,25 +3634,25 @@ class GenerateCodeStageTest { | t#0 = getConsole(); | let ib__0; | ib__0 = new IntBox(-1); - | do_bind_log(t#0)(cat("ib.i = ", do_bind_toString(do_get_i(ib__0))())); + | do_call_log(t#0, cat("ib.i = ", do_call_toString(do_get_i(ib__0)))); | let t#1; | t#1 = ib__0; |## set-i of get-i pattern |## TODO: this might be a good test case for improving temporary elimination. | do_set_i(t#1, do_get_i(t#1) + 11); - | do_bind_log(t#0)(cat("ib.i = ", do_bind_toString(do_get_i(ib__0))())); + | do_call_log(t#0, cat("ib.i = ", do_call_toString(do_get_i(ib__0)))); | let t#2; | t#2 = ib__0; | do_set_i(t#2, do_get_i(t#2) * 9); - | do_bind_log(t#0)(cat("ib.i = ", do_bind_toString(do_get_i(ib__0))())); + | do_call_log(t#0, cat("ib.i = ", do_call_toString(do_get_i(ib__0)))); | let t#3; | t#3 = ib__0; | do_set_i(t#3, do_get_i(t#3) - 6); - | do_bind_log(t#0)(cat("ib.i = ", do_bind_toString(do_get_i(ib__0))())); + | do_call_log(t#0, cat("ib.i = ", do_call_toString(do_get_i(ib__0)))); | let t#4; | t#4 = ib__0; | do_set_i(t#4, do_get_i(t#4) / 2); - | do_bind_log(t#0)(cat("ib.i = ", do_bind_toString(do_get_i(ib__0))())) + | do_call_log(t#0, cat("ib.i = ", do_call_toString(do_get_i(ib__0)))) | | ``` | }, @@ -3675,21 +3689,21 @@ class GenerateCodeStageTest { | let console#0 = doPure(fn: Console { | getConsole() | }), ls__0 = new ListBuilder(); - | do_bind_add(ls__0)(0); - | do_bind_add(ls__0)(3); + | do_call_add(ls__0, 0); + | do_call_add(ls__0, 3); | do { | let t#0; | t#0 = ls__0; |## Here's a call to .set of a call to .get - | do_bind_set(t#0)(0, do_bind_get(t#0)(0) + 10) + | do_call_set(t#0, 0, do_call_get(t#0, 0) + 10) | }; | do { | let t#1; | t#1 = ls__0; - | do_bind_set(t#1)(1, do_bind_get(t#1)(1) * 2) + | do_call_set(t#1, 1, do_call_get(t#1, 1) * 2) | }; - | do_bind_log(console#0)(cat("ls = [", str(do_bind_join(do_bind_toList(ls__0)())(", ", fn (i__0 /* aka i */: Int) /* return__1 */: (String) { - | do_bind_toString(i__0)(10) + | do_call_log(console#0, cat("ls = [", str(do_call_join(do_call_toList(ls__0), ", ", fn (i__0 /* aka i */: Int) /* return__1 */: (String) { + | do_call_toString(i__0, 10) | })), "]")); | | ``` @@ -3851,14 +3865,14 @@ class GenerateCodeStageTest { | sbNow__0 = sbOrNull__0; | let sb__0; | sb__0 = sbNow__0; - | do_bind_append(sb__0)(cat(do_bind_toString(i__0)())); + | do_call_append(sb__0, cat(do_call_toString(i__0))); | sbOrNull__0 = sb__0 | }; | let finalSb__0; | finalSb__0 = sbOrNull__0; - | return__1 = do_bind_toString(finalSb__0)() + | return__1 = do_call_toString(finalSb__0) | }); - | return__0 = f__0(4) + | return__0 = (fn f)(4) | | ``` | }, @@ -3886,9 +3900,9 @@ class GenerateCodeStageTest { | f__0 = (@stay fn f(i__0 /* aka i */: StringIndexOption) /* return__0 */: Void { | var t#0, t#1; |## str has erased to a .toString() call here - | t#0 = do_bind_toString(is(i__0, StringIndex))(); - | t#1 = do_bind_toString(is(i__0, NoStringIndex))(); - | do_bind_log(console#0)(cat("Yes ", t#0, ", no ", t#1)); + | t#0 = do_call_toString(is(i__0, StringIndex)); + | t#1 = do_call_toString(is(i__0, NoStringIndex)); + | do_call_log(console#0, cat("Yes ", t#0, ", no ", t#1)); | return__0 = void | }); | f__0(getStatic(String, \begin)) diff --git a/frontend/src/commonTest/kotlin/lang/temper/frontend/SyntaxMacroStageTest.kt b/frontend/src/commonTest/kotlin/lang/temper/frontend/SyntaxMacroStageTest.kt index 6c0f2503..055a6f25 100644 --- a/frontend/src/commonTest/kotlin/lang/temper/frontend/SyntaxMacroStageTest.kt +++ b/frontend/src/commonTest/kotlin/lang/temper/frontend/SyntaxMacroStageTest.kt @@ -839,6 +839,7 @@ class SyntaxMacroStageTest { |} |x """.trimMargin(), + stagingFlags = setOf(StagingFlags.skipImportImplicits), stage = Stage.SyntaxMacro, want = """ |{ @@ -846,7 +847,7 @@ class SyntaxMacroStageTest { | body: | ``` | let x__0 = f(); - | do_bind_forEach(x__0)(fn (x__1) { + | do_call_forEach(x__0, fn (x__1) { | x__1 | }); | x__0 @@ -1775,7 +1776,7 @@ class SyntaxMacroStageTest { |## magnitude has its doc string | @fn let magnitude__0 = (@docString((["magnitude is the distance of this point from the origin.", "magnitude is the distance of this point from the origin.\n\nIt is always >= 0.", "test/test.temper"])) fn magnitude(@impliedThis(Point__0) this__0: Point__0) /* return__0 */: (Float64) { | fn__0: do { - | do_bind_sqrt(do_iget_x(type (Point__0), this(Point__0)) * do_iget_x(type (Point__0), this(Point__0)) + do_iget_y(type (Point__0), this(Point__0)) * do_iget_y(type (Point__0), this(Point__0)))() + | do_call_sqrt(do_iget_x(type (Point__0), this(Point__0)) * do_iget_x(type (Point__0), this(Point__0)) + do_iget_y(type (Point__0), this(Point__0)) * do_iget_y(type (Point__0), this(Point__0))) | } | }); | @visibility(\public) let constructor__0 = fn constructor(@impliedThis(Point__0) this__1: Point__0, x__1 /* aka x */: Float64, y__1 /* aka y */: Float64) /* return__1 */: Void { @@ -1927,8 +1928,8 @@ class SyntaxMacroStageTest { | let console#0 = doPure(fn: Console { | getConsole() | }), console__0 = getConsole("myConsole"); - | do_bind_log(console__0)("Hi!"); - | do_bind_log(console#0)("Bye!"); + | do_call_log(console__0, "Hi!"); + | do_call_log(console#0, "Bye!"); | | ``` | } @@ -1980,13 +1981,13 @@ class SyntaxMacroStageTest { | if (isNull(subject#1)) { | null | } else { - | do_bind_countBetween(notNull(subject#1))(do_get_begin(String), do_get_end(do_get_string(a__0))) + | do_call_countBetween(notNull(subject#1), do_get_begin(String), do_get_end(do_get_string(a__0))) | } | }; | if (isNull(subject#0)) { | null | } else { - | do_bind_max(notNull(subject#0))(min__0) + | do_call_max(notNull(subject#0), min__0) | } | } | } @@ -2051,7 +2052,7 @@ class SyntaxMacroStageTest { | if (isNull(c__0)) { | null | } else { - | do_bind_method(notNull(c__0))() + | do_call_method(notNull(c__0)) | } | }); | g__0({ @@ -2060,7 +2061,7 @@ class SyntaxMacroStageTest { | if (isNull(subject#1)) { | null | } else { - | do_bind_method(notNull(subject#1))() + | do_call_method(notNull(subject#1)) | } | }); | } @@ -2090,8 +2091,8 @@ class SyntaxMacroStageTest { | do (fn { | let console__0 = getConsole("myConsole"); | }); - | do_bind_log(console#0)("Hi!"); - | do_bind_log(console#0)("Bye!"); + | do_call_log(console#0, "Hi!"); + | do_call_log(console#0, "Bye!"); | | ``` | } diff --git a/frontend/src/commonTest/kotlin/lang/temper/frontend/TypeStageTest.kt b/frontend/src/commonTest/kotlin/lang/temper/frontend/TypeStageTest.kt index ced16d90..da189ed4 100644 --- a/frontend/src/commonTest/kotlin/lang/temper/frontend/TypeStageTest.kt +++ b/frontend/src/commonTest/kotlin/lang/temper/frontend/TypeStageTest.kt @@ -407,8 +407,8 @@ class TypeStageTest { | console#0 = doPure(@stay fn: Console { | getConsole() | }); - | do_bind_forEach(list("foo"))(fn (x__0) { - | do_bind_log(console#0)(x__0) + | do_call_forEach(list("foo"), @stay fn (x__0) { + | do_call_log(console#0, x__0) | }) | | ``` @@ -429,12 +429,12 @@ class TypeStageTest { | i__0 = 0; | while (i__0 < n__0) { | let el__0: String; - | el__0 = do_bind_get(this__0)(i__0); + | el__0 = do_call_get(this__0, i__0); | i__0 = i__0 + 1; |## Inlined block lambda | let x__0; | x__0 = el__0; - | do_bind_log(console#0)(x__0); + | do_call_log(console#0, x__0); |## End of inlined block lambda | }; | @@ -476,7 +476,7 @@ class TypeStageTest { | i__0 = 0; | while (i__0 < n__0) { | let el__0: String; - | el__0 = do_bind_get(this__0)(i__0); + | el__0 = do_call_get(this__0, i__0); | i__0 = i__0 + 1;${ "" // Here we start the inlined block lambda parameters. } @@ -488,7 +488,7 @@ class TypeStageTest { | if (x__0 == "c") { | break; | }; - | do_bind_log(console#0)(x__0);${ + | do_call_log(console#0, x__0);${ "" // Did not inline `return__0 = void`. Not ok for local vars. // Here's the end of the inlined block lambda. } @@ -534,9 +534,9 @@ class TypeStageTest { | outer__0: do { | body#0: do {} | }; - | do_bind_log(doPure(@stay fn /* return__0 */: Console { + | do_call_log(doPure(@stay fn /* return__0 */: Console { | return__0 = getConsole(); - | }))("yes"); + | }), "yes"); | | ``` | }, @@ -915,10 +915,10 @@ class TypeStageTest { type: { body: ``` - fn (i__0 /* aka i */: Int32) /* return__0 */: String { + @stay fn (i__0 /* aka i */: Int32) /* return__0 */: String { void; fn__0: do { - return__0 = do_bind_toString(do_bind_toString(do_bind_toString(i__0)())())(); + return__0 = do_call_toString(do_call_toString(do_call_toString(i__0))); } } @@ -1188,7 +1188,7 @@ class TypeStageTest { |## body's semantics would result in "0" if x could be bound. | (fn i)("0") | } orelse { - | do_bind_log(t#0)("bad"); + | do_call_log(t#0, "bad"); | }; | return__0 = void | @@ -1674,17 +1674,17 @@ class TypeStageTest { | return__0 = getConsole(); | }); | @fn let f__0; - | f__0 = fn f(returnEarly__0 /* aka returnEarly */: Boolean) /* return__1 */: Void { - | void; - | fn__0: do { - | if (returnEarly__0) { - | return__1 = void; - | break fn__0; - | }; - | do_bind_log(console#0)("Did not return early"); - | return__1 = void - | } - | } + | f__0 = (@stay fn f(returnEarly__0 /* aka returnEarly */: Boolean) /* return__1 */: Void { + | void; + | fn__0: do { + | if (returnEarly__0) { + | return__1 = void; + | break fn__0; + | }; + | do_call_log(console#0, "Did not return early"); + | return__1 = void + | } + | }) | | ``` | } @@ -1737,20 +1737,20 @@ class TypeStageTest { | type: { | body: ``` | @fn let a__0; - | a__0 = fn a(i__0 /* aka i */: List) /* return__1 */: (List) { - | void; - | fn__0: do { - | if (do_get_length(i__0) == 0) { - | return__1 = list(); - | break fn__0; - | }; - | let n__0; - | n__0 = new ListBuilder(); - | return__1 = do_bind_map(n__0)(@stay fn (it__0 /* aka it */) /* return__2 */: Int32 { - | return__2 = 2 * it__0 - | }); - | } - | } + | a__0 = (@stay fn a(i__0 /* aka i */: List) /* return__1 */: (List) { + | void; + | fn__0: do { + | if (do_get_length(i__0) == 0) { + | return__1 = list(); + | break fn__0; + | }; + | let n__0; + | n__0 = new ListBuilder(); + | return__1 = do_call_map(n__0, @stay fn (it__0 /* aka it */) /* return__2 */: Int32 { + | return__2 = 2 * it__0 + | }); + | } + | }) | | ```, | } @@ -1795,7 +1795,7 @@ class TypeStageTest { | return__3 = void | }); | if (isZero__0(0)) { - | return__0 = do_bind_isZero(new Zero__0())(); + | return__0 = do_call_isZero(new Zero__0()); | } else { | return__0 = false | }; @@ -1829,7 +1829,7 @@ class TypeStageTest { | x__0 == 0 | } | }); - | if((do_bind_isZero[static isZero__0])(type (Int32))(0), @stay fn { + | if((do_call_isZero[static isZero__0])(type (Int32), 0), @stay fn { | true | }, \else, fn (f#0) { | f#0(@stay fn { @@ -1880,16 +1880,16 @@ class TypeStageTest { | return__0 = getConsole(); | }); | @fn let f__0; - | f__0 = fn f(hi__0 /* aka hi */: String) /* return__1 */: Void { - | void; - | fn__0: do { - | let s__0: String; - | s__0 = cat("Hello, ", str(hi__0), "!"); - | do_bind_log(console#0)(s__0); - | do_bind_log(console#0)(s__0); - | return__1 = void - | } - | } + | f__0 = (@stay fn f(hi__0 /* aka hi */: String) /* return__1 */: Void { + | void; + | fn__0: do { + | let s__0: String; + | s__0 = cat("Hello, ", str(hi__0), "!"); + | do_call_log(console#0, s__0); + | do_call_log(console#0, s__0); + | return__1 = void + | } + | }) | | ``` | } @@ -1906,10 +1906,10 @@ class TypeStageTest { | body: ``` | @stay @imported(\(`half//`.intHalf)) @fn @extension("half") let intHalf__0; | intHalf__0 = (fn intHalf); - | do_bind_log(doPure(@stay fn /* return__0 */: Console { + | do_call_log(doPure(@stay fn /* return__0 */: Console { | return__0 = getConsole(); - | }))(do_bind_toString(intHalf__0(84))()); - |## ^^^^^^^^^^ extension resolved across module boundaries + | }), do_call_toString(intHalf__0(84))); + |## ^^^^^^^^^^ extension resolved across module boundaries | | ``` | } @@ -2076,16 +2076,15 @@ class TypeStageTest { | body: | ``` | @fn let f__0 ⦂(fn (List, List): String), @fn `test//`.g ⦂(fn (String): String); - | f__0 = fn f(literals__0 /* aka literals */: List, values__0 /* aka values */: List) /* return__0 */: String { - | void; - | fn__0: do { - | return__0 = do_bind_get(literals__0)(0); - | } - | }; - | `test//`.g = (@stay fn g(there__0 /* aka there */: String) /* return__1 */: String { + | f__0 = (@stay fn f(literals__0 /* aka literals */: List, values__0 /* aka values */: List) /* return__0 */: String { | void; + | fn__0: do { + | return__0 = do_call_get(literals__0, 0); + | } + | }); + | `test//`.g = (@stay fn g(there__0 /* aka there */: String) /* return__1 */: String { | fn__1: do { - | return__1 = f__0(list ⋖ String ⋗("hi", ""), list ⋖ String ⋗(there__0)); + | return__1 = (fn f)(list ⋖ String ⋗("hi", ""), list ⋖ String ⋗(there__0)) | } | }) | @@ -2128,27 +2127,27 @@ class TypeStageTest { | @fn let `test//`.crazySum ⦂(fn (IntMaker, Int64, String): Int32 | Bubble); | @constructorProperty @visibility(\public) @stay @fromType(IntMaker) let radix__0: Int32; | @visibility(\public) @overload("toInt") @fn @stay @fromType(IntMaker) let int64ToInt__0 ⦂(fn (IntMaker, Int64): Int32 | Bubble); - | int64ToInt__0 = fn int64ToInt(@impliedThis(IntMaker) this__0: IntMaker, int__0 /* aka int */: Int64) /* return__0 */: (Int32 | Bubble) { - | void; - | fn__0: do { - | var fail#0 ⦂ Boolean; - | return__0 = hs ⋖ Int32 ⋗(fail#0, do_bind_toInt32(int__0)()); - | if (fail#0) { - | bubble ⋖ Int32 ⋗() - | }; - | } - | }; + | int64ToInt__0 = (@stay fn int64ToInt(@impliedThis(IntMaker) this__0: IntMaker, int__0 /* aka int */: Int64) /* return__0 */: (Int32 | Bubble) { + | void; + | fn__0: do { + | var fail#0 ⦂ Boolean; + | return__0 = hs ⋖ Int32 ⋗(fail#0, do_call_toInt32(int__0)); + | if (fail#0) { + | bubble ⋖ Int32 ⋗() + | }; + | } + | }); | @visibility(\public) @overload("toInt") @fn @stay @fromType(IntMaker) let stringToInt__0 ⦂(fn (IntMaker, String): Int32 | Bubble); - | stringToInt__0 = fn stringToInt(@impliedThis(IntMaker) this__1: IntMaker, string__0 /* aka string */: String) /* return__1 */: (Int32 | Bubble) { - | void; - | fn__1: do { - | var fail#1 ⦂ Boolean; - | return__1 = hs ⋖ Int32 ⋗(fail#1, do_bind_toInt32(string__0)(getp(radix__0, this__1))); - | if (fail#1) { - | bubble ⋖ Int32 ⋗() - | }; - | } - | }; + | stringToInt__0 = (@stay fn stringToInt(@impliedThis(IntMaker) this__1: IntMaker, string__0 /* aka string */: String) /* return__1 */: (Int32 | Bubble) { + | void; + | fn__1: do { + | var fail#1 ⦂ Boolean; + | return__1 = hs ⋖ Int32 ⋗(fail#1, do_call_toInt32(string__0, getp(radix__0, this__1))); + | if (fail#1) { + | bubble ⋖ Int32 ⋗() + | }; + | } + | }); | @visibility(\public) @overload("justMe") @fn @stay @fromType(IntMaker) let int32ToInt__0 ⦂(fn (IntMaker, Int32): Int32); | int32ToInt__0 = (@stay fn int32ToInt(@impliedThis(IntMaker) this__2: IntMaker, int__1 /* aka int */: Int32) /* return__2 */: Int32 { | fn__2: do { @@ -2164,23 +2163,23 @@ class TypeStageTest { | getradix__0 = (@stay fn (@impliedThis(IntMaker) this__4: IntMaker) /* return__4 */: Int32 { | return__4 = getp(radix__0, this__4) | }); - | `test//`.crazySum = fn crazySum(intMaker__0 /* aka intMaker */: IntMaker, int__2 /* aka int */: Int64, string__1 /* aka string */: String) /* return__5 */: (Int32 | Bubble) { - | void; - | fn__3: do { - | var fail#2 ⦂ Boolean, fail#3 ⦂ Boolean; - | let intInt__0 ⦂ Int32; - | intInt__0 = hs ⋖ Int32 ⋗(fail#2, do_bind_int64ToInt(intMaker__0)(int__2)); - | if (fail#2) { - | bubble ⋖ Int32 ⋗() - | }; - | let stringInt__0 ⦂ Int32; - | stringInt__0 = hs ⋖ Int32 ⋗(fail#3, do_bind_stringToInt(intMaker__0)(string__1)); - | if (fail#3) { - | bubble ⋖ Int32 ⋗() - | }; - | return__5 = do_bind_int32ToInt(intMaker__0)(intInt__0 + stringInt__0); - | } - | } + | `test//`.crazySum = (@stay fn crazySum(intMaker__0 /* aka intMaker */: IntMaker, int__2 /* aka int */: Int64, string__1 /* aka string */: String) /* return__5 */: (Int32 | Bubble) { + | void; + | fn__3: do { + | var fail#2 ⦂ Boolean, fail#3 ⦂ Boolean; + | let intInt__0 ⦂ Int32; + | intInt__0 = hs ⋖ Int32 ⋗(fail#2, do_call_int64ToInt(intMaker__0, int__2)); + | if (fail#2) { + | bubble ⋖ Int32 ⋗() + | }; + | let stringInt__0 ⦂ Int32; + | stringInt__0 = hs ⋖ Int32 ⋗(fail#3, do_call_stringToInt(intMaker__0, string__1)); + | if (fail#3) { + | bubble ⋖ Int32 ⋗() + | }; + | return__5 = do_call_int32ToInt(intMaker__0, intInt__0 + stringInt__0); + | } + | }) | | ``` | } @@ -2235,15 +2234,15 @@ class TypeStageTest { | @stay @imported(\(`test//c/`.C)) let C__0 ⦂ Type; | C__0 = type (C); | @fn let `test//`.useC ⦂(fn (C): Void); - | `test//`.useC = fn useC(c__0 /* aka c */: C) /* return__0 */: Void { - | void; - | fn__0: do { - | do_bind_fooInt32(c__0)(1); - | do_bind_foolean(c__0)(true); - | do_bind_fooString(c__0)(""); - | return__0 = void - | } - | } + | `test//`.useC = (@stay fn useC(c__0 /* aka c */: C) /* return__0 */: Void { + | void; + | fn__0: do { + | do_call_fooInt32(c__0, 1); + | do_call_foolean(c__0, true); + | do_call_fooString(c__0, ""); + | return__0 = void + | } + | }) | | ``` | } @@ -2298,12 +2297,12 @@ class TypeStageTest { | pureVirtual ⋖ String ⋗() | } | }; - | `test//`.stringifyLists = fn stringifyLists(stringer__0 /* aka stringer */: Stringer, int__1 /* aka int */: Int32, ints__1 /* aka ints */: List, strings__0 /* aka strings */: List) /* return__3 */: String { - | void; - | fn__3: do { - | return__3 = cat(str(do_bind_stringifyInt32(stringer__0)(int__1)), ", ", str(do_bind_stringifyInt32List(stringer__0)(ints__1)), ", ", str(do_bind_stringifyStringList(stringer__0)(strings__0))) - | } - | } + | `test//`.stringifyLists = (@stay fn stringifyLists(stringer__0 /* aka stringer */: Stringer, int__1 /* aka int */: Int32, ints__1 /* aka ints */: List, strings__0 /* aka strings */: List) /* return__3 */: String { + | void; + | fn__3: do { + | return__3 = cat(str(do_call_stringifyInt32(stringer__0, int__1)), ", ", str(do_call_stringifyInt32List(stringer__0, ints__1)), ", ", str(do_call_stringifyStringList(stringer__0, strings__0))) + | } + | }) | | ``` | } @@ -2341,27 +2340,27 @@ class TypeStageTest { | @fn let `test//`.crazySum ⦂(fn (IntMaker, Int64, String): Int32 | Bubble); | @constructorProperty @visibility(\public) @stay @fromType(IntMaker) let radix__0: Int32; | @visibility(\public) @fn @stay @fromType(IntMaker) let toInt__0 ⦂(fn (IntMaker, Int64): Int32 | Bubble); - | toInt__0 = fn toInt(@impliedThis(IntMaker) this__0: IntMaker, int__0 /* aka int */: Int64) /* return__0 */: (Int32 | Bubble) { - | void; - | fn__0: do { - | var fail#0 ⦂ Boolean; - | return__0 = hs ⋖ Int32 ⋗(fail#0, do_bind_toInt32(int__0)()); - | if (fail#0) { - | bubble ⋖ Int32 ⋗() - | }; - | } - | }; + | toInt__0 = (@stay fn toInt(@impliedThis(IntMaker) this__0: IntMaker, int__0 /* aka int */: Int64) /* return__0 */: (Int32 | Bubble) { + | void; + | fn__0: do { + | var fail#0 ⦂ Boolean; + | return__0 = hs ⋖ Int32 ⋗(fail#0, do_call_toInt32(int__0)); + | if (fail#0) { + | bubble ⋖ Int32 ⋗() + | }; + | } + | }); | @visibility(\public) @fn @stay @fromType(IntMaker) let toInt__1 ⦂(fn (IntMaker, String): Int32 | Bubble); - | toInt__1 = fn toInt(@impliedThis(IntMaker) this__1: IntMaker, string__0 /* aka string */: String) /* return__1 */: (Int32 | Bubble) { - | void; - | fn__1: do { - | var fail#1 ⦂ Boolean; - | return__1 = hs ⋖ Int32 ⋗(fail#1, do_bind_toInt32(string__0)(getp(radix__0, this__1))); - | if (fail#1) { - | bubble ⋖ Int32 ⋗() - | }; - | } - | }; + | toInt__1 = (@stay fn toInt(@impliedThis(IntMaker) this__1: IntMaker, string__0 /* aka string */: String) /* return__1 */: (Int32 | Bubble) { + | void; + | fn__1: do { + | var fail#1 ⦂ Boolean; + | return__1 = hs ⋖ Int32 ⋗(fail#1, do_call_toInt32(string__0, getp(radix__0, this__1))); + | if (fail#1) { + | bubble ⋖ Int32 ⋗() + | }; + | } + | }); | @fn @visibility(\public) @stay @fromType(IntMaker) let constructor__0 ⦂(fn (IntMaker, Int32): Void); | constructor__0 = (@stay fn constructor(@impliedThis(IntMaker) this__2: IntMaker, radix__1 /* aka radix */: Int32) /* return__2 */: Void { | setp(radix__0, this__2, radix__1); @@ -2371,23 +2370,23 @@ class TypeStageTest { | getradix__0 = (@stay fn (@impliedThis(IntMaker) this__3: IntMaker) /* return__3 */: Int32 { | return__3 = getp(radix__0, this__3) | }); - | `test//`.crazySum = fn crazySum(intMaker__0 /* aka intMaker */: IntMaker, int__1 /* aka int */: Int64, string__1 /* aka string */: String) /* return__4 */: (Int32 | Bubble) { - | void; - | fn__2: do { - | var fail#2 ⦂ Boolean, fail#3 ⦂ Boolean; - | let intInt__0 ⦂ Int32; - | intInt__0 = hs ⋖ Int32 ⋗(fail#2, do_bind_toInt(intMaker__0)(int__1)); - | if (fail#2) { - | bubble ⋖ Int32 ⋗() - | }; - | let stringInt__0 ⦂ Int32; - | stringInt__0 = hs ⋖ Int32 ⋗(fail#3, do_bind_toInt(intMaker__0)(string__1)); - | if (fail#3) { - | bubble ⋖ Int32 ⋗() - | }; - | return__4 = intInt__0 + stringInt__0 - | } - | } + | `test//`.crazySum = (@stay fn crazySum(intMaker__0 /* aka intMaker */: IntMaker, int__1 /* aka int */: Int64, string__1 /* aka string */: String) /* return__4 */: (Int32 | Bubble) { + | void; + | fn__2: do { + | var fail#2 ⦂ Boolean, fail#3 ⦂ Boolean; + | let intInt__0 ⦂ Int32; + | intInt__0 = hs ⋖ Int32 ⋗(fail#2, do_call_toInt(intMaker__0, int__1)); + | if (fail#2) { + | bubble ⋖ Int32 ⋗() + | }; + | let stringInt__0 ⦂ Int32; + | stringInt__0 = hs ⋖ Int32 ⋗(fail#3, do_call_toInt(intMaker__0, string__1)); + | if (fail#3) { + | bubble ⋖ Int32 ⋗() + | }; + | return__4 = intInt__0 + stringInt__0 + | } + | }) | | ``` | } diff --git a/frontend/src/commonTest/kotlin/lang/temper/frontend/typestage/TyperPlanTest.kt b/frontend/src/commonTest/kotlin/lang/temper/frontend/typestage/TyperPlanTest.kt index 16bad5b7..fa62d248 100644 --- a/frontend/src/commonTest/kotlin/lang/temper/frontend/typestage/TyperPlanTest.kt +++ b/frontend/src/commonTest/kotlin/lang/temper/frontend/typestage/TyperPlanTest.kt @@ -652,27 +652,27 @@ class TyperPlanTest { assertStringsEqual( """ |let return__9, @fn f__1; - |f__1 = fn f(s__2 /* aka s */: String) /* return__0 */: Boolean { - | var t#7, t#8; - | fn__3: do { - | var fail#6; - | let x__4; - | orelse#5: { - | t#7 = hs(fail#6, do_bind_toFloat64(s__2)()); - | if (fail#6) { - | break orelse#5; - | }; - | t#8 = t#7 - | } orelse { + |f__1 = (@stay fn f(s__2 /* aka s */: String) /* return__0 */: Boolean { + | var t#7, t#8; + | fn__3: do { + | var fail#6; + | let x__4; + | orelse#5: { + | t#7 = hs(fail#6, do_call_toFloat64(s__2)); + | if (fail#6) { + | break orelse#5; + | }; + | t#8 = t#7 + | } orelse { |## This is not an initializer for t#8 |## And this assignment needs to be typed after t#8's initializer |## so that its context can be used to compute Never as a type. - | t#8 = panic() - | }; - | x__4 = t#8; - | return__0 = x__4 > 0.0 - | } - |}; + | t#8 = panic() + | }; + | x__4 = t#8; + | return__0 = x__4 > 0.0 + | } + |}); |return__9 = void | """.trimMargin().stripDoubleHashCommentLinesToPutCommentsInlineBelow(), @@ -682,10 +682,10 @@ class TyperPlanTest { """ |{ | "f__1": [ - | "fn f(s__2 /* aka s */: String) /* return__0 */: Boolean {var t#7, t#8; fn__3: do {var fail#6; let x__4; orelse#5: {t#7 = hs(fail#6, do_bind_toFloat64(s__2)()); if (fail#6) {break orelse#5;}; t#8 = t#7} orelse {t#8 = panic()}; x__4 = t#8; return__0 = x__4 \u003e 0.0}}" + | "@stay fn f(s__2 /* aka s */: String) /* return__0 */: Boolean {var t#7, t#8; fn__3: do {var fail#6; let x__4; orelse#5: {t#7 = hs(fail#6, do_call_toFloat64(s__2)); if (fail#6) {break orelse#5;}; t#8 = t#7} orelse {t#8 = panic()}; x__4 = t#8; return__0 = x__4 \u003e 0.0}}" | ], | "t#7": [ - | "hs(fail#6, do_bind_toFloat64(s__2)())" + | "hs(fail#6, do_call_toFloat64(s__2))" | ], | "t#8": [ |## No panic() @@ -695,7 +695,7 @@ class TyperPlanTest { | "t#8" | ], | "return__0": [ - | "x__4 \u003e 0.0" + | "x__4 > 0.0" | ], | "return__9": [ | "void" @@ -707,10 +707,9 @@ class TyperPlanTest { assertStructure( """ |[ - | "do_bind_toFloat64(s)", - | "do_bind_toFloat64(s)()", - | "hs(fail, do_bind_toFloat64(s)())", - | "x \u003e 0.0", + | "do_call_toFloat64(s)", + | "hs(fail, do_call_toFloat64(s))", + | "x > 0.0", | "fn ...", | "panic()", |] diff --git a/frontend/src/commonTest/kotlin/lang/temper/frontend/typestage/TyperTest.kt b/frontend/src/commonTest/kotlin/lang/temper/frontend/typestage/TyperTest.kt index 61c7dfa1..56761a01 100644 --- a/frontend/src/commonTest/kotlin/lang/temper/frontend/typestage/TyperTest.kt +++ b/frontend/src/commonTest/kotlin/lang/temper/frontend/typestage/TyperTest.kt @@ -374,6 +374,7 @@ class TyperTest { "22+28-31: Type Invalid mentions Invalid", "22+28-33: Type Invalid mentions Invalid", ), + skipImplicits = true, ) @Test @@ -638,6 +639,7 @@ class TyperTest { |/// ┣━━━━━┓ ┃ : C | new C().method() """.trimMargin(), + skipImplicits = true, ) @Test @@ -748,9 +750,8 @@ class TyperTest { | void """.trimMargin(), wantErrors = listOf( - "3+4-15: Member b defined in Something incompatible with usage!", + "3+4-17: Member b defined in Something incompatible with usage!", "3+4: Type Invalid mentions Invalid", - "3+4-15: Type Invalid mentions Invalid", "3+4-17: Type Invalid mentions Invalid", "3+14-15: Type Invalid mentions Invalid", ), @@ -1190,6 +1191,21 @@ class TyperTest { """.trimMargin(), ) + @Test + fun functionLikePropertyCalled() = assertTypes( + $$""" + | class C( + | private map: fn (t: T): T + | ) { + | public f(t: T): T { map(t) } + | } + | + | export let result = new C(fn (str: String) { "Hello, ${str}!" }).f("World"); + | result + |/// ┗━━━━┛ : String + """.trimMargin(), + ) + @Test fun genericRestParametersWithoutContext() = assertTypes( """ @@ -1446,8 +1462,7 @@ class TyperTest { | } """.trimMargin(), wantErrors = listOf( - "2+23-33: No member toString in Imu | AnyValue | Equatable!", - "2+23-33: Type Invalid mentions Invalid", + "2+23-35: No member toString in Imu | AnyValue | Equatable!", "2+23-35: Type Invalid mentions Invalid", "2+25-33: Type Invalid mentions Invalid", ), @@ -1609,6 +1624,7 @@ class TyperTest { "2+27-32: Expected value of type Type not ❎!", "2+16-23: No declaration for avocado!", "2+27-32: No declaration for Thing!", + "2+24-26: Expected function type, but got Function!", "4+15-24: No declaration for Commander!", "2+16-23: Type Invalid mentions Invalid", "2+27-32: Type Invalid mentions Invalid", @@ -1710,11 +1726,32 @@ class TyperTest { | | inst.f(); |/// ┗━━━━━━┛ : String - | I.f() + | I.f(); |/// ┗━━━┛ : Int32 """.trimMargin(), ) + @Test + fun genericExtension() = assertTypes( + $$""" + | @staticExtension(Boolean, "sayHi") + | let boolSaysHi(): String { + | "Hi, Booleans can be true or false! What's your name?" + | } + | + | @staticExtension(List, "sayHi") + | let listSaysHi(ls: List): String { + | "Hi! Fun fact, a list can have ${ls.length} things!" + | } + | + | Boolean.sayHi(); + |/// ┗━━━━━━━━━━━━━┛ : String + | + | List.sayHi([1, 2, 3]); + |/// ┗━━━━━━━━━━━━━━━━━━━┛ : String + """.trimMargin(), + ) + @Test fun unionOfSealed() = assertTypes( """ @@ -1896,6 +1933,112 @@ class TyperTest { """.trimMargin(), ) + @Test + fun multipleInheritedMethodImplementations() = assertTypes( + // Here we've got a complex inheritance tree like the below. + // A + // / + // B C + // \ / + // D + // + // A and C both implement a() but since C is reachable first, + // its implementation unambiguously wins. + """ + | interface A { a(): String { "A wins!" } } + | interface B extends A {} + | interface C { a(): String { "C wins!" } } + | class D extends B & C {} + | new D().a() + |/// ┗━━━━━━━━━┛ : String + """.trimMargin(), + skipImplicits = true, + ) + + /** + * Like [multipleInheritedMethodImplementations] but the methods are + * only equivalent post-contextualization. + */ + @Test + fun multipleInheritedMethodsWithParameterization() = assertTypes( + // Here we've got a complex inheritance tree like the below. + // A + // / + // B C + // \ / + // D + // + // A and C both implement a() but since C is reachable first, + // and A's .a method is equivalent, in the context of D + // to C's .a method, C's implementation unambiguously wins. + $$""" + | interface A { a(x: T): T { x } } + | interface B extends A {} + | interface C { a(x: String): String { "C: ${x}" } } + | class D extends B & C {} + | new D().a("hi") + |/// ┗━━━━━━━━━━━━━┛ : String + """.trimMargin(), + skipImplicits = true, + ) + + /** + * Like [multipleInheritedMethodImplementations] but the methods are + * only equivalent assuming corresponding type formals are equivalent. + */ + @Test + fun multipleInheritedMethodsWithFormalsAssumedEquivalent() = assertTypes( + // Here we've got a complex inheritance tree like the below. + // A + // / + // B C + // \ / + // D + $$""" + | interface A { a(x: T): T { x } } + | interface B extends A {} + | interface C { a(x: T): T { x } } + | class D extends B & C {} + | new D().a("hi") + |/// ┗━━━━━━━━━━━━━┛ : String + """.trimMargin(), + skipImplicits = true, + // We resolved types just fine, but parameterized methods in interfaces are still suss. + wantErrors = listOf( + "1+20: Illegal type parameter T. Overridable methods don't allow generics!", + "3+20: Illegal type parameter T. Overridable methods don't allow generics!", + ), + ) + + @Test + fun multipleInheritedMethodsWithConflict() = assertTypes( + // Here we've got a complex inheritance tree like the below. + // + // A + // / + // B C + // \ / + // D + // + // A and C both implement a() but their methods have irreconcilable + // signatures (Void vs String return type), so an error is reported. + """ + | interface A { a(): Void {} } + | interface B extends A {} + | interface C { a(): String { "C wins!" } } + | class D extends B & C {} + | new D().a() + |/// ┗━━━━━━━━━┛ : Invalid + """.trimMargin(), + skipImplicits = true, + wantErrors = listOf( + "5+4-15: No callee matches inputs [D] among [(A) -> Void, (C) -> String]!", + "1+4: Type Invalid mentions Invalid", + "5+4: Type Invalid mentions Invalid", + "5+4-15: Type Invalid mentions Invalid", + ), + ) + /** Text at a position in a source code snippet in a unit test */ data class Chunk( val text: String, @@ -2204,25 +2347,34 @@ class TyperTest { childOrder.forEach { i -> findContradictionsAndInvalidType(t.child(i)) } val typeInferences = t.typeInferences + var hasProblem = false if (typeInferences != null) { if (typeInferences.type.mentionsInvalid) { invalidPositions.putMultiSet(t.pos, "Type ${typeInferences.type}") + hasProblem = true } else if (typeInferences is CallTypeInferences) { typeInferences.bindings2.forEach { (typeFormal, binding) -> val name = typeFormal.name if (binding is StaticType && binding.mentionsInvalid) { invalidPositions.putMultiSet(t.pos, "Binding from $name to $binding") + hasProblem = true } } val variant = typeInferences.variant if (variant.mentionsInvalid) { invalidPositions.putMultiSet(t.pos, "Invalid variant: $variant") + hasProblem = true } } typeInferences.explanations.forEach { it.logTo(projectLogSink) } } + if (hasProblem && verbose) { + console.group("${t.pos}: invalid type") { + t.toPseudoCode(console.textOutput) + } + } } findContradictionsAndInvalidType(root) diff --git a/fundamentals/src/commonMain/kotlin/lang/temper/type/DotHelper.kt b/fundamentals/src/commonMain/kotlin/lang/temper/type/DotHelper.kt index 8d294ec3..2125702f 100644 --- a/fundamentals/src/commonMain/kotlin/lang/temper/type/DotHelper.kt +++ b/fundamentals/src/commonMain/kotlin/lang/temper/type/DotHelper.kt @@ -4,8 +4,6 @@ import lang.temper.common.console import lang.temper.env.BindingNamingContext import lang.temper.env.InterpMode import lang.temper.format.OutToks -import lang.temper.format.OutputToken -import lang.temper.format.OutputTokenType import lang.temper.format.TokenSerializable import lang.temper.format.TokenSink import lang.temper.log.MessageTemplate @@ -18,18 +16,15 @@ import lang.temper.value.BuiltinStatelessMacroValue import lang.temper.value.CallableValue import lang.temper.value.Fail import lang.temper.value.InternalFeatureKeys -import lang.temper.value.InterpreterCallback import lang.temper.value.MacroEnvironment import lang.temper.value.NamedBuiltinFun import lang.temper.value.NotYet import lang.temper.value.PartialResult import lang.temper.value.SpecialFunction -import lang.temper.value.StaySink import lang.temper.value.TClass import lang.temper.value.TFunction import lang.temper.value.Value import lang.temper.value.ValueLeaf -import lang.temper.value.and import lang.temper.value.staticBuiltinName import lang.temper.value.typeShapeAtLeafOrNull @@ -61,10 +56,10 @@ data class StaticExtensionResolution( * * - `subject.adjective = expr` : property set via [ExternalSet] and [InternalSet] * - `subject.adjective` : property read via [ExternalGet] and [InternalGet] - * - `subject.verb(args)` : method call via [ExternalBind] and [InternalBind] + * - `subject.verb(args)` : method call via [ExternalCall] and [InternalCall] * - `subject.verb` : read of a bound method via [ExternalGet] and [InternalGet] - * - `subject.adjective(args)` : call to a function stored in a property via [ExternalBind] and - * [InternalBind] + * - `subject.adjective(args)` : call to a function stored in a property via [ExternalCall] and + * [InternalCall] but rewritten statically by [lang.temper.frontend.maybeAdjustDotHelper] * * It also allows */ @@ -124,16 +119,16 @@ class DotHelper( } val args = macroEnv.args val sizeWanted = when (memberAccessor) { - ExternalGet -> 1 // (this) - InternalGet -> 2 // (containingTypeShape, this) - ExternalSet -> 2 // (this, newValue) + ExternalGet -> arityOne // (this) + InternalGet -> arityTwo // (containingTypeShape, this) + ExternalSet -> arityTwo // (this, newValue) InternalSet -> @Suppress("MagicNumber") // arity - 3 // (containingTypeShape, this, newValue) - ExternalBind -> 1 // (this) - InternalBind -> 2 // (containingTypeShape, this) + arityThree // (containingTypeShape, this, newValue) + ExternalCall -> arityOneOrMore // (this, arg0, arg1, ...) + InternalCall -> arityTwoOrMore // (containingTypeShape, this, arg0, arg1) } - if (args.size != sizeWanted) { + if (args.size !in sizeWanted) { return macroEnv.fail(MessageTemplate.ArityMismatch, values = listOf(sizeWanted)) } val subjectIndex = memberAccessor.firstArgumentIndex @@ -261,36 +256,17 @@ class DotHelper( dispatchCallTo(helperShape) } - ExternalBind, InternalBind -> + ExternalCall, InternalCall -> when (val method = accessibleMembers.firstOrNull()) { null -> inaccessible() - is MethodShape -> { - if (method.methodKind == MethodKind.Normal) { - lookupMemberDefinition(method).and { methodValue -> - if (methodValue.typeTag != TFunction) { - macroEnv.fail( - MessageTemplate.ExpectedValueOfType, - values = listOf(TFunction, methodValue), - ) - } else { - val callable = TFunction.unpack(methodValue) // typeTag checked above - if (callable is CallableValue) { - Value(BoundMethod(method, callable, subject)) - } else { - macroEnv.fail( - MessageTemplate.CannotInvokeMacroAsFunction, - ) - } - } - } - } else { - // TODO: handle call of functions from getter as in - // obj.f() - // where (obj.f) is a use of a getter than gets a function. - Fail - } - } - else -> TODO("Call of function stored in property") + is MethodShape if (method.methodKind == MethodKind.Normal) -> + dispatchCallTo(method) + else -> + // TODO: handle call of functions from getter as in + // obj.f() + // where (obj.f) is a use of a getter than gets a function. + // Currently, maybeAdjustDotHelper handles this but perhaps late. + Fail } } } @@ -302,12 +278,7 @@ class DotHelper( AccessibleFilter(accessingTypeShape.membersMatching(member, member is OperatorMember), null) override val callMayFailPerSe: Boolean - get() = when (memberAccessor) { - is BindMemberAccessor -> false // Just a curry operator - // getters and setters can bubble - is GetMemberAccessor -> true - is SetMemberAccessor -> true - } + get() = true override fun toString() = "DotHelper(${this.memberAccessor.prefix}, ${this.member})" } @@ -359,34 +330,8 @@ private fun lookupMemberDefinition( return methodDefinition?.value ?: NotYet } -private class BoundMethod( - private val methodShape: MethodShape, - private val method: CallableValue, - private val subject: Value<*>, -) : CallableValue, TokenSerializable { - override fun invoke(args: ActualValues, cb: InterpreterCallback, interpMode: InterpMode): PartialResult { - val allArgs = ActualValues.cat(ActualValues.from(subject), args) - return method.invoke(allArgs, cb, interpMode) - } - - override val sigs: List? get() = method.sigs?.map { sig -> - sig.copy(requiredInputTypes = sig.requiredInputTypes.drop(1), hasThisFormal = false) - } - - override fun addStays(s: StaySink) { - // Not stable - } - - override fun toString(): String = "BoundMethod(${subject}.${methodShape.name})" - - override fun renderTo(tokenSink: TokenSink) { - tokenSink.emit(OutputToken("ƒ", OutputTokenType.Word)) - tokenSink.emit(OutToks.dot) - tokenSink.emit(OutputToken("bind", OutputTokenType.Name)) - tokenSink.emit(OutToks.leftParen) - tokenSink.emit(methodShape.enclosingType.name.toToken(inOperatorPosition = false)) - tokenSink.emit(OutToks.dot) - tokenSink.emit(methodShape.name.toToken(inOperatorPosition = false)) - tokenSink.emit(OutToks.rightParen) - } -} +private val arityOne = 1..1 +private val arityTwo = 2..2 +private val arityThree = 3..3 +private val arityOneOrMore = 1..Int.MAX_VALUE +private val arityTwoOrMore = 2..Int.MAX_VALUE diff --git a/fundamentals/src/commonMain/kotlin/lang/temper/type/MemberShape.kt b/fundamentals/src/commonMain/kotlin/lang/temper/type/MemberShape.kt index 147edf82..105c6279 100644 --- a/fundamentals/src/commonMain/kotlin/lang/temper/type/MemberShape.kt +++ b/fundamentals/src/commonMain/kotlin/lang/temper/type/MemberShape.kt @@ -344,12 +344,14 @@ sealed class MemberAccessor( override fun toString(): String = prefix } +/** Access to a member from within its definition via a `this` subject. */ sealed class InternalMemberAccessor( prefix: String, ) : MemberAccessor(prefix, enclosingTypeIndexOrNegativeOne = 0) { override fun prefix(member: VisibleMemberShape) = prefix(DotMember(member.symbol)) } +/** Access to a member from outside the type's definition. */ sealed class ExternalMemberAccessor( prefix: String, ) : MemberAccessor(prefix, enclosingTypeIndexOrNegativeOne = -1) { @@ -359,13 +361,19 @@ sealed class ExternalMemberAccessor( } } +/** A member accessor that reads a property value. */ sealed interface GetMemberAccessor + +/** A member accessor that sets a property value. */ sealed interface SetMemberAccessor -sealed interface BindMemberAccessor +/** A member accessor that calls a method. */ +sealed interface CallMemberAccessor + +/** Reading a property internally. */ object InternalGet : InternalMemberAccessor("iget"), GetMemberAccessor object InternalSet : InternalMemberAccessor("iset"), SetMemberAccessor -object InternalBind : InternalMemberAccessor("ibind"), BindMemberAccessor +object InternalCall : InternalMemberAccessor("icall"), CallMemberAccessor object ExternalGet : ExternalMemberAccessor("get"), GetMemberAccessor object ExternalSet : ExternalMemberAccessor("set"), SetMemberAccessor -object ExternalBind : ExternalMemberAccessor("bind"), BindMemberAccessor +object ExternalCall : ExternalMemberAccessor("call"), CallMemberAccessor diff --git a/fundamentals/src/commonMain/kotlin/lang/temper/type/Types.kt b/fundamentals/src/commonMain/kotlin/lang/temper/type/Types.kt index df3ccec9..f734ebd9 100644 --- a/fundamentals/src/commonMain/kotlin/lang/temper/type/Types.kt +++ b/fundamentals/src/commonMain/kotlin/lang/temper/type/Types.kt @@ -29,6 +29,7 @@ import lang.temper.value.Stayless enum class TypeOpPrecedence { Or, // Binds loosest And, + Fn, Postfixed, SelfContained, // Binds tightest } @@ -369,6 +370,10 @@ class FunctionType private constructor( returnTypeOpPrecedence = returnType.precedence, ) + override val precedence: TypeOpPrecedence + // So `?` suffix doesn't spuriously attach to return type. + get() = TypeOpPrecedence.Fn + companion object { internal fun renderFunctionType( tokenSink: TokenSink, @@ -886,6 +891,27 @@ val StaticType.isBubbly: Boolean get() = when (this) { else -> false } +val StaticType.nullity: Nullity get() = when (this) { + is FunctionType, + is BubbleType, + is InvalidType, + -> Nullity.NonNull + is NominalType -> if (definition == WellKnownTypes.nullTypeDefinition) { + Nullity.OrNull + } else { + Nullity.NonNull + } + is AndType, + is OrType, + -> { + val members = if (this is AndType) { members } else { (this as OrType).members } + if (members.any { it.nullity == Nullity.OrNull }) { Nullity.OrNull } else { + Nullity.NonNull + } + } + is TopType -> Nullity.OrNull +} + /** * Simplifies a type by removing annotation types like `Null` or `Bubble`, if present. See [SimplifyStaticType] for * details. diff --git a/fundamentals/src/commonMain/kotlin/lang/temper/type2/TypeBindingMapper.kt b/fundamentals/src/commonMain/kotlin/lang/temper/type2/TypeBindingMapper.kt index bb92f77e..e21fa4e7 100644 --- a/fundamentals/src/commonMain/kotlin/lang/temper/type2/TypeBindingMapper.kt +++ b/fundamentals/src/commonMain/kotlin/lang/temper/type2/TypeBindingMapper.kt @@ -45,6 +45,23 @@ fun TypeLike.mapType( defnMapper: (TypeFormal) -> TypeLike?, ): TypeLike = TypeBindingMapper(varMapper, defnMapper).mapTypeLike(this) +fun Descriptor.mapType(m: Map): Descriptor = when (this) { + is Type2 -> mapType(m) + is Signature2 -> mapType(m) +} + +val DefinedType.bindingMap: Map get() = if (bindings.isEmpty()) { + emptyMap() +} else { + buildMap { + val formals = definition.formals + for ((i, binding) in bindings.withIndex()) { + val tf = formals.getOrNull(i) ?: break + put(tf, binding) + } + } +} + private class TypeBindingMapper( private val varMapper: (TypeVar) -> TypeLike?, private val defnMapper: (TypeFormal) -> TypeLike?, diff --git a/fundamentals/src/commonMain/kotlin/lang/temper/type2/UntypedCall.kt b/fundamentals/src/commonMain/kotlin/lang/temper/type2/UntypedCall.kt index 23b901b1..9fc49f45 100644 --- a/fundamentals/src/commonMain/kotlin/lang/temper/type2/UntypedCall.kt +++ b/fundamentals/src/commonMain/kotlin/lang/temper/type2/UntypedCall.kt @@ -30,8 +30,7 @@ data class UntypedCall( */ val destination: CallTree, /** - * The trees from which the input bounds were arrived. - * If we've unwrapped a do_bind_... call, then the inputTrees are uncurried from multiple calls + * The trees from which the input bounds were derived. */ val inputTrees: List, ) { diff --git a/fundamentals/src/commonMain/kotlin/lang/temper/value/PseudoCode.kt b/fundamentals/src/commonMain/kotlin/lang/temper/value/PseudoCode.kt index c21c6bfd..fc193bfc 100644 --- a/fundamentals/src/commonMain/kotlin/lang/temper/value/PseudoCode.kt +++ b/fundamentals/src/commonMain/kotlin/lang/temper/value/PseudoCode.kt @@ -9,6 +9,7 @@ import lang.temper.common.TriState import lang.temper.common.abbreviate import lang.temper.common.allMapToSameElseNull import lang.temper.common.mapInterleaving +import lang.temper.common.subListToEnd import lang.temper.common.temperEscaper import lang.temper.common.toStringViaTextOutput import lang.temper.cst.InnerOperatorStackElement @@ -46,8 +47,8 @@ import lang.temper.name.Symbol import lang.temper.name.TemperName import lang.temper.stage.Stage import lang.temper.type.AndType -import lang.temper.type.BindMemberAccessor import lang.temper.type.BubbleType +import lang.temper.type.CallMemberAccessor import lang.temper.type.DotHelper import lang.temper.type.DotMember import lang.temper.type.FunctionType @@ -201,15 +202,76 @@ internal class PseudoTreeBuilder( } } + is CallTree if tree.size == 0 -> PseudoError(pos) is CallTree -> { - if (tree.size == 0) { - PseudoError(pos) - } else if (detail.resugarDotHelpers && isDotHelperCall(tree)) { - val calleePos = tree.child(0).pos - val dotHelper = extractDotHelperFromCall(tree)!! + var calleeTree = tree.child(0) + + val typeArgs = mutableListOf() + var typeArgsInferred = false + var argListStart = 1 // Index into children where arguments start. + + if (calleeTree is CallTree && calleeTree.size > 1) { + val nestedCallee = calleeTree.child(0) + // (nym`<>` actualCallee typeArg0 typeArg1 ...) + if (nestedCallee isProbablyBuiltinFunNamed NameConstants.Angle) { + for (typeArg in calleeTree.children.subListToEnd(2)) { + typeArgs.add(buildPseudoTree(typeArg)) + } + calleeTree = calleeTree.child(1) + } + } + + fun findTypeArgs() { + if (typeArgs.isEmpty()) { + // Consume type arguments into + while (argListStart + 1 < tree.size) { + val childAtArgListStart = tree.child(argListStart) + if (childAtArgListStart.symbolContained != typeArgSymbol) { + break + } + typeArgs.add(buildPseudoTree(tree.child(argListStart + 1))) + argListStart += 2 + } + } + // Show inferred type arguments if we don't have explicit ones and the details + // ask for them. + if (typeArgs.isEmpty() && detail.showInferredTypes) { + val calleeType = calleeTree.typeInferences?.type + val variant = calleeType as? FunctionType + ?: tree.typeInferences?.variant as? FunctionType + val bindings = tree.typeInferences?.bindings2 + if (variant != null && variant.typeFormals.isNotEmpty() && bindings != null) { + val inferredTypeArgs = variant.typeFormals.map { + bindings[it] + } + if (null !in inferredTypeArgs) { + inferredTypeArgs.mapTo(typeArgs) { + PseudoType(calleeTree.pos.rightEdge, it!!) + } + typeArgsInferred = true + } + } + } + } + + fun buildValueArgs() = (argListStart until tree.size).map { + buildPseudoTree(tree.child(it)) + } + + val dotHelper = if (detail.resugarDotHelpers) { + calleeTree.functionContained as? DotHelper + } else { + null + } + + if (dotHelper != null) { + val calleePos = calleeTree.pos val memberAccessor = dotHelper.memberAccessor - val firstArgumentChildIndex = memberAccessor.firstArgumentIndex + 1 // skip over callee - val subject = tree.childOrNull(firstArgumentChildIndex) ?: return PseudoError(pos) + + argListStart = memberAccessor.firstArgumentIndex + 1 // skip over callee + val subject = tree.childOrNull(argListStart) ?: return PseudoError(pos) + argListStart += 1 + val operation = when (val member = dotHelper.member) { is DotMember -> PseudoCall( pos = tree.pos, @@ -230,18 +292,24 @@ internal class PseudoTreeBuilder( emptyList(), listOf( operation, - buildPseudoTree(tree.child(firstArgumentChildIndex + 1)), + buildPseudoTree(tree.child(argListStart)), ), ) - is BindMemberAccessor -> operation + is CallMemberAccessor -> { + findTypeArgs() + PseudoCall( + pos = pos, + callee = operation, + typeArgs = typeArgs, + args = buildValueArgs(), + typeArgsInferred = typeArgsInferred, + ) + } } } else { - var callee = buildPseudoTree(tree.child(0)) - val typeArgs = mutableListOf() - var typeArgsInferred = false + var callee = buildPseudoTree(calleeTree) var args: List? = null - var argListStart = 1 // Index into children where arguments start. // Special case `.` operator since its right operand is a symbol but // should render as a bare name. if ( @@ -284,44 +352,12 @@ internal class PseudoTreeBuilder( } argListStart = 2 } - // Consume type arguments into - while (argListStart + 1 < tree.size) { - val childAtArgListStart = tree.child(argListStart) - if (childAtArgListStart.symbolContained != typeArgSymbol) { - break - } - typeArgs.add(buildPseudoTree(tree.child(argListStart + 1))) - argListStart += 2 - } - // Show inferred type arguments if we don't have explicit ones and the details - // ask for them. - if (typeArgs.isEmpty() && detail.showInferredTypes) { - val calleeType = tree.childOrNull(0)?.typeInferences?.type - val variant = calleeType as? FunctionType - ?: tree.typeInferences?.variant as? FunctionType - val bindings = tree.typeInferences?.bindings2 - if (variant != null && variant.typeFormals.isNotEmpty() && bindings != null) { - val inferredTypeArgs = variant.typeFormals.map { - bindings[it] - } - if (null !in inferredTypeArgs) { - inferredTypeArgs.mapTo(typeArgs) { - PseudoType(callee.pos.rightEdge, it!!) - } - typeArgsInferred = true - } - } - } - if (args == null) { - args = (argListStart until tree.size).map { - buildPseudoTree(tree.child(it)) - } - } + findTypeArgs() PseudoCall( pos = pos, callee = callee, typeArgs = typeArgs.toList(), - args = args, + args = args ?: buildValueArgs(), typeArgsInferred = typeArgsInferred, ) } @@ -1441,12 +1477,11 @@ internal class PseudoDecl( reduce(emitDeclKeyword = TriState.TRUE) fun reduce(emitDeclKeyword: TriState): OpTree { - val symbolToken = if (word == null) { - null - } else if (word is PseudoValueLeaf && word.value.typeTag == TSymbol) { - OutputToken.makeSlashStarComment("aka ${TSymbol.unpack(word.value).text}") - } else { - OutputToken.makeSlashStarComment("aka ???") + val symbolToken = when (word) { + null -> null + is PseudoValueLeaf if word.value.typeTag == TSymbol -> + OutputToken.makeSlashStarComment("aka ${TSymbol.unpack(word.value).text}") + else -> OutputToken.makeSlashStarComment("aka ???") } val keyword = when { // Assume caller knows what they're doing @@ -2325,6 +2360,10 @@ object TemperFormattingHints : FormattingHints { override fun shouldBreakBefore(token: OutputToken): Boolean { if (isInlineSentinel(token)) { return false } + // The Temper lexer treats `<` ss an angle bracket when there is no ignorable + // token between it and the preceding token, but as an infix less-than + // token otherwise. + if (token == OutToks.leftAngle) { return false } return super.shouldBreakBefore(token) } @@ -2332,6 +2371,7 @@ object TemperFormattingHints : FormattingHints { if (isInlineSentinel(preceding) || isInlineSentinel(following)) { return TriState.FALSE } + if (following == OutToks.leftAngle) { return TriState.FALSE } // See above return super.shouldBreakBetween(preceding, following) } @@ -2420,6 +2460,14 @@ private fun isStandaloneDecl(metadata: MetadataMultimap): Boolean = // Class and interface members should not be grouped into a comma list. typeDeclSymbol in metadata || typeMemberMetadataSymbols.any { it in metadata } +private infix fun (Tree).isProbablyBuiltinFunNamed(desiredName: String): Boolean = + when (this) { + is NameLeaf -> content.builtinKey == desiredName + is ValueLeaf -> + (TFunction.unpackOrNull(content) as? NamedBuiltinFun)?.name == desiredName + else -> false + } + private infix fun (PseudoTree).isProbablyBuiltinFunNamed(desiredName: String): Boolean = when (this) { is PseudoNameLeaf -> name.builtinKey == desiredName @@ -2538,19 +2586,6 @@ private fun maybeTagStringValue(stringValue: String): Pair { return null to escapedString } -private fun isDotHelperCall(t: CallTree) = extractDotHelperFromCall(t) != null - -private fun extractDotHelperFromCall(t: CallTree): DotHelper? { - val callee = t.child(0) - if (callee is ValueLeaf) { - val fn = TFunction.unpackOrNull(callee.content) - if (fn is DotHelper) { - return fn - } - } - return null -} - internal enum class ReturnKind { ReturnType, ReturnDecl, diff --git a/fundamentals/src/commonTest/kotlin/lang/temper/type/StaticTypeTest.kt b/fundamentals/src/commonTest/kotlin/lang/temper/type/StaticTypeTest.kt index 84ded103..e0c52b54 100644 --- a/fundamentals/src/commonTest/kotlin/lang/temper/type/StaticTypeTest.kt +++ b/fundamentals/src/commonTest/kotlin/lang/temper/type/StaticTypeTest.kt @@ -3,6 +3,8 @@ package lang.temper.type import lang.temper.common.assertStringsEqual import lang.temper.common.assertStructure import lang.temper.format.toStringViaTokenSink +import lang.temper.type2.hackMapNewStyleToOld +import lang.temper.type2.makeTypeFormalsForTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals @@ -27,6 +29,26 @@ class StaticTypeTest { "Bubble", toStringViaTokenSink { BubbleType.renderTo(it) }, ) + val strToVoid = MkType.fn(listOf(), listOf(WellKnownTypes.stringType), null, WellKnownTypes.voidType) + assertStringsEqual( + "fn (String): Void", + toStringViaTokenSink { strToVoid.renderTo(it) }, + ) + val strToVoidOrNull = MkType.nullable(strToVoid) + assertStringsEqual( + "(fn (String): Void)?", + toStringViaTokenSink { strToVoidOrNull.renderTo(it) }, + ) + val (typeParamRefT) = makeTypeFormalsForTest("T" to WellKnownTypes.anyValueType) + val genericFnType = MkType.fn( + listOf(typeParamRefT.definition), + listOf(hackMapNewStyleToOld(typeParamRefT)), null, WellKnownTypes.voidType, + ) + assertStringsEqual( + "fn(T): Void", + toStringViaTokenSink { genericFnType.renderTo(it) }, + ) + TypeTestHarness( """ |interface Foo; diff --git a/fundamentals/src/commonTest/kotlin/lang/temper/value/PseudoCodeTest.kt b/fundamentals/src/commonTest/kotlin/lang/temper/value/PseudoCodeTest.kt index e815125b..401919cf 100644 --- a/fundamentals/src/commonTest/kotlin/lang/temper/value/PseudoCodeTest.kt +++ b/fundamentals/src/commonTest/kotlin/lang/temper/value/PseudoCodeTest.kt @@ -13,6 +13,7 @@ import lang.temper.common.assertStringsEqual import lang.temper.common.console import lang.temper.common.toStringViaBuilder import lang.temper.cst.CstComment +import lang.temper.cst.NameConstants import lang.temper.interp.emptyValue import lang.temper.lexer.Lexer import lang.temper.log.FilePositions @@ -28,10 +29,12 @@ import lang.temper.name.Symbol import lang.temper.parser.parse import lang.temper.type.DotHelper import lang.temper.type.DotMember -import lang.temper.type.ExternalBind +import lang.temper.type.ExternalCall import lang.temper.type.ExternalGet import lang.temper.type.ExternalSet +import lang.temper.type.FunctionType import lang.temper.type.MkType +import lang.temper.type.TypeTestHarness import lang.temper.type.WellKnownTypes import lang.temper.type2.MkType2 import kotlin.test.Test @@ -963,10 +966,8 @@ class PseudoCodeTest { Call(DotHelper(ExternalGet, DotMember(Symbol("i")), emptyList())) { Rn(ParsedName("x")) } - Call { - Call(DotHelper(ExternalBind, DotMember(Symbol("f")), emptyList())) { - Rn(ParsedName("x")) - } + Call(DotHelper(ExternalCall, DotMember(Symbol("f")), emptyList())) { + Rn(ParsedName("x")) V(Value(1, TInt)) } } @@ -975,6 +976,52 @@ class PseudoCodeTest { }, ) + @Test + fun explicitAndNonExplicitTypeParameters() = assertPseudoCode( + want = """ + |o.foo(str); + |o.bar ⋖ String ⋗(str) + | + """.trimMargin(), + detail = PseudoCodeDetail(resugarDotHelpers = true, showInferredTypes = true), + makeInput = { doc, pos -> + val unboundCalleeType: FunctionType + val tiWithBindings: CallTypeInferences + TypeTestHarness("").run { + unboundCalleeType = type("fn(T): Void") as FunctionType + tiWithBindings = CallTypeInferences( + WellKnownTypes.voidType, + variant = type("fn (String): Void"), + bindings2 = mapOf(unboundCalleeType.typeFormals[0] to WellKnownTypes.stringType), + explanations = listOf(), + ) + } + + doc.treeFarm.grow(pos) { + Block { + Call { + Call { + Rn(BuiltinName(NameConstants.Angle)) + V(Value(DotHelper(ExternalCall, DotMember(Symbol("foo")), emptyList()))) + V(Value(Types.string)) + } + Rn(ParsedName("o")) + Rn(ParsedName("str")) + } + + Call(tiWithBindings) { + V( + Value(DotHelper(ExternalCall, DotMember(Symbol("bar")), emptyList())), + type = unboundCalleeType, + ) + Rn(ParsedName("o")) + Rn(ParsedName("str")) + } + } + } + }, + ) + @Test fun postfixOperators() = assertPseudoCode( input = "C?", diff --git a/interp/src/commonTest/kotlin/lang/temper/interp/InterpreterTest.kt b/interp/src/commonTest/kotlin/lang/temper/interp/InterpreterTest.kt index 479ea7f9..6cd2e8f3 100644 --- a/interp/src/commonTest/kotlin/lang/temper/interp/InterpreterTest.kt +++ b/interp/src/commonTest/kotlin/lang/temper/interp/InterpreterTest.kt @@ -776,34 +776,6 @@ class InterpreterTest { } } - private fun assertResult( - expectedJson: String, - input: String, - expectedFailLog: String? = null, - ) = assertResult( - expectedJson, - inputText = input, - expectedFailLog = expectedFailLog, - ) { logSink, context -> - // Lex the input - val lexer = Lexer(context.loc, logSink, input) - - // Build a parse tree - val comments = mutableListOf() - val cst = parse(lexer, logSink, comments) - - // Build an AST. - val ast = buildTree( - cstParts = flatten(cst), - storedCommentTokens = StoredCommentTokens(comments), - logSink = logSink, - documentContext = context, - ) - - // Pre-process the AST to do just enough to get basic language features working. - preprocess(ast) - } - @Test fun declMetadataVisitedDuringPartialInterp() { val logSink = ListBackedLogSink() @@ -862,6 +834,34 @@ class InterpreterTest { assertEquals(emptyList(), untagged) } + private fun assertResult( + expectedJson: String, + input: String, + expectedFailLog: String? = null, + ) = assertResult( + expectedJson, + inputText = input, + expectedFailLog = expectedFailLog, + ) { logSink, context -> + // Lex the input + val lexer = Lexer(context.loc, logSink, input) + + // Build a parse tree + val comments = mutableListOf() + val cst = parse(lexer, logSink, comments) + + // Build an AST. + val ast = buildTree( + cstParts = flatten(cst), + storedCommentTokens = StoredCommentTokens(comments), + logSink = logSink, + documentContext = context, + ) + + // Pre-process the AST to do just enough to get basic language features working. + preprocess(ast) + } + private fun assertResult( expectedJson: String, inputText: String, From b3748364f887eaa58182ac6b9069e448f7561717 Mon Sep 17 00:00:00 2001 From: Mike Samuel Date: Fri, 26 Jun 2026 14:26:58 -0600 Subject: [PATCH 2/6] cleanup some missing tests Signed-off-by: Mike Samuel --- .../commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt | 7 ++++--- .../src/commonMain/kotlin/lang/temper/type/DotHelper.kt | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt index 35ce058f..050e0bc3 100644 --- a/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt +++ b/be-js/src/commonTest/kotlin/lang/temper/be/js/JsBackendTest.kt @@ -1897,12 +1897,13 @@ class JsBackendTest { | export function maybeLength(a_0) { | let return_0; | let t_3; + | let t_4; | if (a_0 == null) { | return_0 = null; | } else { - | const a_1 = a_0; - | t_3 = a_1.length; - | return_0 = stringCountBetween_0(a_1, 0, t_3); + | t_4 = a_0; + | t_3 = t_4.length; + | return_0 = stringCountBetween_0(t_4, 0, t_3); | } | return return_0; | }; diff --git a/fundamentals/src/commonMain/kotlin/lang/temper/type/DotHelper.kt b/fundamentals/src/commonMain/kotlin/lang/temper/type/DotHelper.kt index 2125702f..c3395b60 100644 --- a/fundamentals/src/commonMain/kotlin/lang/temper/type/DotHelper.kt +++ b/fundamentals/src/commonMain/kotlin/lang/temper/type/DotHelper.kt @@ -332,6 +332,8 @@ private fun lookupMemberDefinition( private val arityOne = 1..1 private val arityTwo = 2..2 + +@Suppress("MagicNumber") // Three is what's written on the tin. (The magic tin) private val arityThree = 3..3 private val arityOneOrMore = 1..Int.MAX_VALUE private val arityTwoOrMore = 2..Int.MAX_VALUE From f041b40dbb20de111f354e6a85fb2d22fa90f9a0 Mon Sep 17 00:00:00 2001 From: Mike Samuel Date: Wed, 1 Jul 2026 14:52:48 -0600 Subject: [PATCH 3/6] minimal repro for be-rust problem Signed-off-by: Mike Samuel --- .../lang/temper/be/rust/RustBackendTest.kt | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/be-rust/src/commonTest/kotlin/lang/temper/be/rust/RustBackendTest.kt b/be-rust/src/commonTest/kotlin/lang/temper/be/rust/RustBackendTest.kt index 81c95873..1f264f22 100644 --- a/be-rust/src/commonTest/kotlin/lang/temper/be/rust/RustBackendTest.kt +++ b/be-rust/src/commonTest/kotlin/lang/temper/be/rust/RustBackendTest.kt @@ -2719,6 +2719,115 @@ class RustBackendTest { """.trimMargin(), ) } + + @Test + fun decodeHexTest() = assertGenerateWanted( + temper = """ + |let decodeHexUnsigned( + | sourceText: String, start: StringIndex, limit: StringIndex + |): Int { + | var n = 0; + | var i = start; + | while (i.compareTo(limit) < 0) { + | let cp = sourceText[i]; + | let digit = if (char'0' <= cp && cp <= char'0') { + | cp - char'0' + | } else if (char'A' <= cp && cp <= char'F') { + | cp - char'A' + 10 + | } else if (char'a' <= cp && cp <= char'f') { + | cp - char'a' + 10 + | } else { + | return -1; + | } + | n = (n * 16) + digit; + | i = sourceText.next(i); + | } + | n + |} + """.trimMargin(), + rust = """ + |pub (crate) fn init() -> temper_core::Result<()> { + | static INIT_ONCE: std::sync::OnceLock> = std::sync::OnceLock::new(); + | INIT_ONCE.get_or_init(| |{ + | Ok(()) + | }).clone() + |} + |fn decodeHexUnsigned__0(sourceText__0: impl temper_core::ToArcString, start__0: usize, limit__0: usize) -> i32 { + | let sourceText__0 = sourceText__0.to_arc_string(); + | let return__0: i32; + | let mut t___0: usize; + | let mut t___1: bool; + | let mut t___2: bool; + | let mut t___3: bool; + | let mut t___4: i32; + | 'fn__0: { + | let mut n__0: i32 = 0; + | let mut i__0: usize = start__0; + | 'loop___0: loop { + | if ! (Some(Some(i__0).cmp( & Some(limit__0)) as i32) < Some(0)) { + | break; + | } + | let cp__0: i32 = temper_core::string::get( & sourceText__0, i__0); + | if Some(48) <= Some(cp__0) { + | t___1 = Some(cp__0) <= Some(48); + | } else { + | t___1 = false; + | } + | if t___1 { + | t___4 = cp__0.wrapping_sub(48); + | } else { + | if Some(65) <= Some(cp__0) { + | t___2 = Some(cp__0) <= Some(70); + | } else { + | t___2 = false; + | } + | if t___2 { + | t___4 = cp__0.wrapping_sub(65).wrapping_add(10); + | } else { + | if Some(97) <= Some(cp__0) { + | t___3 = Some(cp__0) <= Some(102); + | } else { + | t___3 = false; + | } + | if t___3 { + | t___4 = cp__0.wrapping_sub(97).wrapping_add(10); + | } else { + | return__0 = -1; + | break 'fn__0; + | } + | } + | } + | let digit__0: i32 = t___4; + | n__0 = n__0.wrapping_mul(16).wrapping_add(digit__0); + | t___0 = temper_core::string::next( & sourceText__0, i__0); + | i__0 = t___0; + | } + | return__0 = n__0; + | } + | return return__0; + |} + """.trimMargin(), + ) + + @Test + fun stringIndexOptionCompareToStringIndexMinimal() = assertGenerateWanted( + temper = """ + |export let notEmpty(start: StringIndex, limit: StringIndex): Boolean { + | start.compareTo(limit) < 0 + |} + """.trimMargin(), + rust = """ + |pub (crate) fn init() -> temper_core::Result<()> { + | static INIT_ONCE: std::sync::OnceLock> = std::sync::OnceLock::new(); + | INIT_ONCE.get_or_init(| |{ + | Ok(()) + | }).clone() + |} + |pub fn not_empty(start__0: usize, limit__0: usize) -> bool { + | return Some(Some(start__0).cmp( & Some(limit__0)) as i32) < Some(0); + |} + """.trimMargin(), + ) } private fun assertGenerateWanted( From a561e9f49b1523ac8eaf818edcc7f8adf44777ff Mon Sep 17 00:00:00 2001 From: Mike Samuel Date: Wed, 1 Jul 2026 15:47:38 -0600 Subject: [PATCH 4/6] fix test golden Signed-off-by: Mike Samuel --- .../kotlin/lang/temper/be/csharp/CSharpBackendTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be-csharp/src/commonTest/kotlin/lang/temper/be/csharp/CSharpBackendTest.kt b/be-csharp/src/commonTest/kotlin/lang/temper/be/csharp/CSharpBackendTest.kt index 4f9beff1..b3b4655d 100644 --- a/be-csharp/src/commonTest/kotlin/lang/temper/be/csharp/CSharpBackendTest.kt +++ b/be-csharp/src/commonTest/kotlin/lang/temper/be/csharp/CSharpBackendTest.kt @@ -476,7 +476,7 @@ class CSharpBackendTest { |static TestGlobal() |{ | builder__0 = new C::OrderedDictionary(); - | Map1 = C::Mapped.ToMap(builder__0); + | Map1 = C::Mapped.ToMap(C::Mapped.AsReadOnly(builder__0)); | mapped__0 = C::Mapped.AsReadOnly(builder__0); | Map2 = C::Mapped.ToMap(mapped__0); |} From 9ca4f13b33607dd0f116e49a6b5aa89b142234d5 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 1 Jul 2026 15:37:24 -0700 Subject: [PATCH 5/6] Remove obsolete workaround for StringIndexOption Signed-off-by: Tom --- .../lang/temper/be/rust/RustSupportNetwork.kt | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustSupportNetwork.kt b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustSupportNetwork.kt index 5d3a7f1e..024ead5e 100644 --- a/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustSupportNetwork.kt +++ b/be-rust/src/commonMain/kotlin/lang/temper/be/rust/RustSupportNetwork.kt @@ -24,9 +24,7 @@ import lang.temper.name.OutName import lang.temper.name.ParsedName import lang.temper.name.name import lang.temper.type.WellKnownTypes -import lang.temper.type2.DefinedNonNullType import lang.temper.type2.DefinedType -import lang.temper.type2.MkType2 import lang.temper.type2.Signature2 import lang.temper.type2.Type2 import lang.temper.type2.withType @@ -678,19 +676,8 @@ private object CmpGeneric : MethodCall( returnType: Type2, translator: RustTranslator, ): Rust.Expr { - // Work around core.type StringIndexOption.compareTo() typing if needed. - val self = arguments.getOrNull(0) - val effectiveArgs = when ((self?.type as? DefinedNonNullType)?.definition) { - WellKnownTypes.stringIndexTypeDefinition -> buildList { - // Left should be an option, not a string index, but we don't get the function type that way. - val optionType = MkType2(WellKnownTypes.stringIndexOptionTypeDefinition).get() - add(TypedArg((self.expr as Rust.Expr).wrapSome(), optionType)) - addAll(arguments.subListToEnd(1)) - } - else -> arguments - } - // And cast result as i32 for be-rust int expectations. - return super.inlineToTree(pos, effectiveArgs, returnType, translator).infix(RustOperator.As, "i32".toId(pos)) + // Cast result as i32 for temper Int type expectations. + return super.inlineToTree(pos, arguments, returnType, translator).infix(RustOperator.As, "i32".toId(pos)) } } From b9171e5f52869431417e71a2037f211270217930 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 1 Jul 2026 15:50:39 -0700 Subject: [PATCH 6/6] Expect more inlining Signed-off-by: Tom --- .../lang/temper/be/rust/RustBackendTest.kt | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/be-rust/src/commonTest/kotlin/lang/temper/be/rust/RustBackendTest.kt b/be-rust/src/commonTest/kotlin/lang/temper/be/rust/RustBackendTest.kt index 5514523a..cf411117 100644 --- a/be-rust/src/commonTest/kotlin/lang/temper/be/rust/RustBackendTest.kt +++ b/be-rust/src/commonTest/kotlin/lang/temper/be/rust/RustBackendTest.kt @@ -96,28 +96,25 @@ class RustBackendTest { | pub (crate) fn init() -> temper_core::Result<()> { | static INIT_ONCE: std::sync::OnceLock> = std::sync::OnceLock::new(); | INIT_ONCE.get_or_init(| |{ + | let stringifyValue__0: std::sync::Arc std::sync::Arc + std::marker::Send + std::marker::Sync> = std::sync::Arc::new(crate::bar::stringify.clone()); | println!("{}", "Foo"); - | STRINGIFY_VALUE_HERE.set(std::sync::Arc::new(stringifyHere__0.clone())).unwrap_or_else(| _ | panic!()); + | let stringifyValueHere__0: std::sync::Arc std::sync::Arc + std::marker::Send + std::marker::Sync> = std::sync::Arc::new(stringifyHere__0.clone()); | Ok(()) | }).clone() | } - | static STRINGIFY_VALUE_HERE: std::sync::OnceLock std::sync::Arc + std::marker::Send + std::marker::Sync>> = std::sync::OnceLock::new(); - | fn stringify_value_here() -> std::sync::Arc std::sync::Arc + std::marker::Send + std::marker::Sync> { - | ( * STRINGIFY_VALUE_HERE.get().unwrap()).clone() - | } | fn stringifyHere__0(i__0: i32) -> std::sync::Arc { | return temper_core::int_to_string(i__0, None); | } | pub fn hi(nums__0: impl temper_core::ToListed) -> std::sync::Arc { | let nums__0 = nums__0.to_listed(); | let a__0: std::sync::Arc = temper_core::listed::join( & ( * nums__0), std::sync::Arc::new("".to_string()), & crate::bar::stringify.clone()); - | let b__0: std::sync::Arc = temper_core::listed::join( & ( * nums__0), std::sync::Arc::new("".to_string()), & ( * crate::bar::stringify_value().clone())); + | let b__0: std::sync::Arc = temper_core::listed::join( & ( * nums__0), std::sync::Arc::new("".to_string()), & crate::bar::stringify.clone()); | let c__0: std::sync::Arc = temper_core::listed::join( & ( * nums__0), std::sync::Arc::new("".to_string()), & stringifyHere__0.clone()); - | let d__0: std::sync::Arc = temper_core::listed::join( & ( * nums__0), std::sync::Arc::new("".to_string()), & ( * stringify_value_here().clone())); + | let d__0: std::sync::Arc = temper_core::listed::join( & ( * nums__0), std::sync::Arc::new("".to_string()), & stringifyHere__0.clone()); | return std::sync::Arc::new(format!("{}{}{}{}", a__0, b__0.clone(), c__0.clone(), d__0.clone())); | } - | pub fn make_talk_here(talker__1: crate::bar::Talker) { - | talker__1.talk(); + | pub fn make_talk_here(talker__0: crate::bar::Talker) { + | talker__0.talk(); | } | | ``` @@ -173,8 +170,8 @@ class RustBackendTest { | pub fn stringify(i__1: i32) -> std::sync::Arc { | return temper_core::int_to_string(i__1, None); | } - | pub fn make_talk(talker__0: Talker) { - | talker__0.talk(); + | pub fn make_talk(talker__1: Talker) { + | talker__1.talk(); | } | | ```