Preserve static return types and initialize singleton static methods

This commit is contained in:
sergeych committed 2026-10-21 00:01:11 +04:00
1 parent 04c9343a9d
commit c34371b784
7 files changed
+162 -10

No files matched your search

@@ -88,6 +88,9 @@ internal suspend fun executeClassDecl(
classScope.addConst("object", newClass)
spec.bodyInit?.let { executeBytecodeWithSeed(classScope, it, "object body init") }
for (statement in spec.initScope) {
executeBytecodeWithSeed(classScope, statement, "object static init")
}
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
if (spec.declaredName != null) {
@@ -3121,7 +3121,7 @@ class Compiler(
val field = FieldRef(left, next.value, isOptional)
operand = CallRef(field, args, tailBlock, isOptional, memberCallTypeArgs)
} else {
operand = MethodCallRef(left, next.value, args, tailBlock, isOptional, memberCallTypeArgs)
operand = MethodCallRef(left, next.value, args, tailBlock, isOptional, memberCallTypeArgs, classScopeCall = true)
}
} else {
// instance method call
@@ -3238,14 +3238,14 @@ class Compiler(
resolutionSink?.referenceReflection(name.value, next.pos)
}
}
MethodCallRef(left, next.value, args, true, isOptional)
MethodCallRef(left, next.value, args, true, isOptional, classScopeCall = shouldTreatAsClassScopeCall(left, next.value))
} else {
val unionCall = buildUnionMethodCall(left, next.value, next.pos, isOptional, args, true)
if (unionCall != null) {
unionCall
} else {
enforceReceiverTypeForMember(left, next.value, next.pos)
MethodCallRef(left, next.value, args, true, isOptional)
MethodCallRef(left, next.value, args, true, isOptional, classScopeCall = shouldTreatAsClassScopeCall(left, next.value))
}
}
is QualifiedThisRef ->
@@ -3265,7 +3265,7 @@ class Compiler(
unionCall
} else {
enforceReceiverTypeForMember(left, next.value, next.pos)
MethodCallRef(left, next.value, args, true, isOptional)
MethodCallRef(left, next.value, args, true, isOptional, classScopeCall = shouldTreatAsClassScopeCall(left, next.value))
}
}
}
@@ -8686,6 +8686,7 @@ class Compiler(
val initScope = popInitScope()
val wrappedBodyInit = bodyInit?.let { wrapClassBodyBytecode(it, "object@$className") }
val wrappedInitScope = initScope.map { wrapClassBodyBytecode(it, "objectInit@$className") }
val spec = ClassDeclSpec(
declaredName = declaredName,
@@ -8701,7 +8702,7 @@ class Compiler(
constructorArgs = null,
constructorFieldIds = null,
bodyInit = wrappedBodyInit,
initScope = emptyList()
initScope = wrappedInitScope
)
return ClassDeclStatement(spec)
}
@@ -10071,7 +10072,7 @@ class Compiler(
pendingTypeParamStack.removeLast()
}
if (parentContext is CodeContext.ClassBody && !isStatic && extTypeName == null) {
if (parentContext is CodeContext.ClassBody && extTypeName == null) {
classMemberTypeDeclByName.getOrPut(parentContext.name) { mutableMapOf() }[name] = TypeDecl.Function(
receiver = receiverTypeDecl,
contextReceivers = contextReceiverTypeDecls,
@@ -10245,7 +10246,7 @@ class Compiler(
val inferredReturnDecl = returnTypeDecl ?: inferReturnTypeDeclFromStatement(rawFnStatements)
val inferredReturnClass = inferredReturnDecl?.let { resolveTypeDeclObjClass(it) }
?: inferReturnClassFromStatement(rawFnStatements)
if (parentContext is CodeContext.ClassBody && !isStatic && extTypeName == null) {
if (parentContext is CodeContext.ClassBody && extTypeName == null) {
val ownerClassName = parentContext.name
run {
val memberTypeDecl = TypeDecl.Function(
@@ -4876,11 +4876,16 @@ class BytecodeCompiler(
val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
val dst = allocSlot()
if (shouldUseClassScopeReceiver(ref.receiver, receiver, receiverClass)) {
val classReceiver = shouldUseClassScopeReceiver(ref.receiver, receiver, receiverClass)
if (ref.classScopeCall || classReceiver) {
// Singleton objects carry their static members on the instance's class.
val classSlot = if (classReceiver) receiver.slot else allocSlot().also {
builder.emit(Opcode.GET_OBJ_CLASS, receiver.slot, it)
}
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
val memberSlot = allocSlot()
if (!ref.isOptional) {
builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, memberSlot)
builder.emit(Opcode.GET_CLASS_SCOPE, classSlot, nameId, memberSlot)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
@@ -4892,7 +4897,7 @@ class BytecodeCompiler(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, memberSlot)
builder.emit(Opcode.GET_CLASS_SCOPE, classSlot, nameId, memberSlot)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, memberSlot)
@@ -484,6 +484,7 @@ class MethodCallRef(
internal val tailBlock: Boolean,
internal val isOptional: Boolean,
internal val explicitTypeArgs: List<TypeDecl>? = null,
internal val classScopeCall: Boolean = false,
) : ObjRef {
// 4-entry PIC for method invocations (guarded by PerfFlags.METHOD_PIC)
private var mKey1: Long = 0L; private var mVer1: Int = -1; private var mInvoker1: (suspend (Obj, Scope, Arguments) -> Obj)? = null
@@ -0,0 +1,76 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Test
import kotlin.test.assertEquals
class StaticReturnTypeRegressionTest {
@Test
fun declaredFactoryReturnSupportsInferredVariablesAndChains() = runTest {
assertEquals("hi hi hi", eval("""
class Factory(x) {
static fun make(y): Factory = Factory(y)
fun hi(): String = "hi"
}
val a: Factory = Factory.make(1)
val b = Factory.make(2)
a.hi() + " " + b.hi() + " " + Factory.make(3).hi()
""".trimIndent()).toString())
}
@Test
fun declaredScalarReturnsSupportMembersAndArithmetic() = runTest {
assertEquals("4", eval("""
class Scalars {
static fun text(y): String = "abc"
static fun num(y: Int): Int = y
}
val s = Scalars.text(1)
val n = Scalars.num(1)
s.length + n
""".trimIndent()).toString())
}
@Test
fun singletonStaticReturnSupportsMembers() = runTest {
assertEquals("3", eval("""
object Singleton { static fun text(): String = "abc" }
val text = Singleton.text()
text.length
""".trimIndent()).toString())
}
@Test
fun singletonStaticTailBlockAndInstanceMethodsUseTheirOwnDispatch() = runTest {
assertEquals("6", eval("""
object Singleton {
static fun text(block: ()->String): String = block()
fun instanceText(): String = "abc"
}
Singleton.text { "abc" }.length + Singleton.instanceText().length
""".trimIndent()).toString())
}
@Test
fun inferredStaticReturnMatchesInstanceAndTopLevelFunctions() = runTest {
assertEquals("9", eval("""
class Factory {
static fun text() = "abc"
fun instanceText() = "abc"
}
fun topText() = "abc"
Factory.text().length + Factory().instanceText().length + topText().length
""".trimIndent()).toString())
}
@Test
fun genericStaticReturnSubstitutesArgumentType() = runTest {
assertEquals("7", eval("""
class Box<T>(val value: T) {
static fun of<T>(v: T): Box<T> = Box(v)
}
val box = Box.of("abc")
val explicit = Box.of<String>("abcd")
box.value.length + explicit.value.length
""".trimIndent()).toString())
}
}