Preserve static return types and initialize singleton static methods
This commit is contained in:
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())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user