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); |} 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 692c73c4..65b9e014 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/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)) } } 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 398ef4f4..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(); | } | | ``` @@ -2768,6 +2765,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( 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 c0fa0a08..31d480f3 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 @@ -1753,15 +1752,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 { @@ -2771,20 +2761,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 8fba74ea..fd811b47 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/tmpl/TranslateDotHelper.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/tmpl/TranslateDotHelper.kt @@ -2,17 +2,16 @@ package lang.temper.be.tmpl import lang.temper.common.Either 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 @@ -35,7 +34,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 @@ -74,9 +72,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) { @@ -157,12 +154,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 @@ -174,7 +169,7 @@ internal object TranslateDotHelper { ) } - val callType = (outerCallTree ?: callTree).typeOrInvalid + val callType = callTree.typeOrInvalid val pool = translator.pool val subjectIndexInCallTree = 1 + dotHelper.memberAccessor.firstArgumentIndex @@ -187,21 +182,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, @@ -215,24 +198,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) @@ -250,17 +225,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( @@ -281,8 +250,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, @@ -391,7 +360,7 @@ internal object TranslateDotHelper { ), ) } - is BindMemberAccessor -> if (outerCallTree != null) { + is CallMemberAccessor -> { val method = firstMember ?: return garbageTranslatedHelper( pos, @@ -413,7 +382,7 @@ internal object TranslateDotHelper { } return translatedExpr( translation - ?: translator.untranslatableExpr(pos, (outerCallTree ?: callTree).toLispy()), + ?: translator.untranslatableExpr(pos, callTree.toLispy()), ) } @@ -458,7 +427,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 bb4f8a82..1cc9cfa6 100644 --- a/docs/for-users/.snippet-hashes.json +++ b/docs/for-users/.snippet-hashes.json @@ -648,7 +648,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", @@ -667,7 +667,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", @@ -697,7 +697,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 0b5999b7..7df361c5 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 c59b1bd3..2864f22c 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 @@ -883,21 +883,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 + | } | } | } | } - | } - | }; + | }); | | ``` | }, @@ -936,8 +936,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)))); ``` }, @@ -981,8 +981,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)))); ``` }, @@ -2134,12 +2134,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); | | ``` | }, @@ -2299,19 +2299,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" | }) | }; @@ -2344,8 +2344,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), ")") | }) | }; | }; @@ -2353,7 +2353,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" | }); | }; @@ -2562,9 +2562,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. @@ -2599,9 +2599,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); @@ -2613,13 +2613,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))); | | ``` | }, @@ -3105,9 +3105,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) @@ -3161,24 +3161,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() @@ -3345,19 +3345,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) @@ -3366,9 +3366,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) | } @@ -3422,20 +3422,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) @@ -3444,9 +3444,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) | } @@ -3477,9 +3477,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 d1b19fd8..8739af7b 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) }) ``` @@ -1460,7 +1474,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; @@ -1471,7 +1485,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) @@ -1571,7 +1585,7 @@ class GenerateCodeStageTest { |{ | generateCode: { | body: ``` - | do_bind_log(getConsole())("Logged") + | do_call_log(getConsole(), "Logged") | | ``` | }, @@ -1661,7 +1675,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) { @@ -1688,7 +1702,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) | } | }; | @@ -1727,12 +1741,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; @@ -1754,12 +1768,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) + | } + | }) | | ``` | }, @@ -1790,7 +1804,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 { @@ -1809,7 +1823,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) | }) | | ``` @@ -1911,7 +1925,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 { @@ -1921,24 +1935,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; @@ -2075,14 +2089,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 | } | }; @@ -2090,13 +2104,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 = core.doneResult() | }) | }); @@ -2157,14 +2171,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 | } | }; @@ -2175,9 +2189,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"); | } | }) | }); @@ -2291,7 +2305,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) @@ -2301,7 +2315,7 @@ class GenerateCodeStageTest { | if (fail#0) { | bubble() | }; - | do_bind_log(t#0)(t#1) + | do_call_log(t#0, t#1) | | ``` | } @@ -2978,13 +2992,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; | }; @@ -3047,14 +3061,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; @@ -3062,34 +3076,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 } @@ -3255,14 +3269,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) | } | | ``` @@ -3360,7 +3374,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); | } |} @@ -3368,7 +3383,7 @@ class GenerateCodeStageTest { want = """ |{ | run: "void: Void", - | errors: ["Expected function type, but got Invalid!"], + | errors: ["Expected function type, but got (fn (Int32): Void)?!"], |} """.trimMargin(), ) @@ -3402,7 +3417,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)) | | ``` | } @@ -3450,12 +3465,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 | }) | @@ -3503,7 +3518,7 @@ class GenerateCodeStageTest { | if (isNull(subject#0)) { | null | } else { - | do_bind_toString(notNull(subject#0))() + | do_call_toString(notNull(subject#0)) | } | } | ?? "NULL" @@ -3532,7 +3547,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)) { @@ -3562,15 +3577,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) | } | }) | @@ -3621,25 +3635,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)))) | | ``` | }, @@ -3676,21 +3690,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) | })), "]")); | | ``` @@ -3852,14 +3866,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) | | ``` | }, @@ -3887,9 +3901,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 1bfa0804..e5d739e5 100644 --- a/frontend/src/commonTest/kotlin/lang/temper/frontend/SyntaxMacroStageTest.kt +++ b/frontend/src/commonTest/kotlin/lang/temper/frontend/SyntaxMacroStageTest.kt @@ -875,6 +875,7 @@ class SyntaxMacroStageTest { |} |x """.trimMargin(), + stagingFlags = setOf(StagingFlags.skipImportImplicits), stage = Stage.SyntaxMacro, want = """ |{ @@ -882,7 +883,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 @@ -1811,7 +1812,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 { @@ -1963,8 +1964,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!"); | | ``` | } @@ -2016,13 +2017,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) | } | } | } @@ -2087,7 +2088,7 @@ class SyntaxMacroStageTest { | if (isNull(c__0)) { | null | } else { - | do_bind_method(notNull(c__0))() + | do_call_method(notNull(c__0)) | } | }); | g__0({ @@ -2096,7 +2097,7 @@ class SyntaxMacroStageTest { | if (isNull(subject#1)) { | null | } else { - | do_bind_method(notNull(subject#1))() + | do_call_method(notNull(subject#1)) | } | }); | } @@ -2126,8 +2127,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 41f878a5..181ef15a 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 cd7cc66d..a6c4f017 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 Equatable | AnyValue!", - "2+23-33: Type Invalid mentions Invalid", + "2+23-35: No member toString in Equatable | AnyValue!", "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..c3395b60 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,10 @@ 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})" +private val arityOne = 1..1 +private val arityTwo = 2..2 - 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) - } -} +@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 diff --git a/fundamentals/src/commonMain/kotlin/lang/temper/type/MemberShape.kt b/fundamentals/src/commonMain/kotlin/lang/temper/type/MemberShape.kt index 3b5d20ae..d9ddf6dd 100644 --- a/fundamentals/src/commonMain/kotlin/lang/temper/type/MemberShape.kt +++ b/fundamentals/src/commonMain/kotlin/lang/temper/type/MemberShape.kt @@ -352,12 +352,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) { @@ -367,13 +369,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 39668c1c..e6907dbe 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, ) } @@ -1442,12 +1478,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 @@ -2326,6 +2361,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) } @@ -2333,6 +2372,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) } @@ -2421,6 +2461,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 @@ -2539,19 +2587,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,