Step 18: delegated member access in bytecode
This commit is contained in:
parent
b2d5897aa8
commit
5305ced89f
@ -59,9 +59,9 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
|
||||
- [x] Step 17: Callable property calls in bytecode.
|
||||
- [x] Support `CallRef` where the target is a `FieldRef` (e.g., `(obj.fn)()`), keeping compile-time resolution.
|
||||
- [x] Add a JVM test for a callable property call compiled to bytecode.
|
||||
- [ ] Step 18: Delegated member access in bytecode.
|
||||
- [ ] Remove `containsDelegatedRefs` guard once bytecode emits delegated get/set/call correctly.
|
||||
- [ ] Add JVM coverage for delegated member get/set/call in bytecode.
|
||||
- [x] Step 18: Delegated member access in bytecode.
|
||||
- [x] Remove `containsDelegatedRefs` guard once bytecode emits delegated get/set/call correctly.
|
||||
- [x] Add JVM coverage for delegated member get/set/call in bytecode.
|
||||
- [ ] Step 19: Unknown receiver member access in bytecode.
|
||||
- [ ] Decide on allowed fallback behavior for unknown receiver types without runtime name resolution.
|
||||
- [ ] Add JVM tests for member access on unresolved receiver types and keep compile-time-only resolution.
|
||||
|
||||
@ -2077,18 +2077,6 @@ class Compiler(
|
||||
private fun containsDelegatedRefs(ref: ObjRef): Boolean {
|
||||
return when (ref) {
|
||||
is LocalSlotRef -> ref.isDelegated
|
||||
is ImplicitThisMemberRef -> {
|
||||
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
|
||||
val targetClass = typeName?.let { resolveClassByName(it) }
|
||||
val member = targetClass?.findFirstConcreteMember(ref.name)
|
||||
member?.type == ObjRecord.Type.Delegated
|
||||
}
|
||||
is ImplicitThisMethodCallRef -> {
|
||||
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
|
||||
val targetClass = typeName?.let { resolveClassByName(it) }
|
||||
val member = targetClass?.findFirstConcreteMember(ref.methodName())
|
||||
member?.type == ObjRecord.Type.Delegated
|
||||
}
|
||||
is BinaryOpRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right)
|
||||
is UnaryOpRef -> containsDelegatedRefs(ref.a)
|
||||
is CastRef -> containsDelegatedRefs(ref.castValueRef()) || containsDelegatedRefs(ref.castTypeRef())
|
||||
@ -2103,14 +2091,7 @@ class Compiler(
|
||||
is ConditionalRef ->
|
||||
containsDelegatedRefs(ref.condition) || containsDelegatedRefs(ref.ifTrue) || containsDelegatedRefs(ref.ifFalse)
|
||||
is ElvisRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right)
|
||||
is FieldRef -> {
|
||||
val receiverClass = resolveReceiverClassForMember(ref.target)
|
||||
if (receiverClass != null) {
|
||||
val member = receiverClass.findFirstConcreteMember(ref.name)
|
||||
if (member?.type == ObjRecord.Type.Delegated) return true
|
||||
}
|
||||
containsDelegatedRefs(ref.target)
|
||||
}
|
||||
is FieldRef -> containsDelegatedRefs(ref.target)
|
||||
is IndexRef -> containsDelegatedRefs(ref.targetRef) || containsDelegatedRefs(ref.indexRef)
|
||||
is ListLiteralRef -> ref.entries().any {
|
||||
when (it) {
|
||||
@ -2125,14 +2106,7 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
is CallRef -> containsDelegatedRefs(ref.target) || ref.args.any { containsDelegatedRefs(it.value) }
|
||||
is MethodCallRef -> {
|
||||
val receiverClass = resolveReceiverClassForMember(ref.receiver)
|
||||
if (receiverClass != null) {
|
||||
val member = receiverClass.findFirstConcreteMember(ref.name)
|
||||
if (member?.type == ObjRecord.Type.Delegated) return true
|
||||
}
|
||||
containsDelegatedRefs(ref.receiver) || ref.args.any { containsDelegatedRefs(it.value) }
|
||||
}
|
||||
is MethodCallRef -> containsDelegatedRefs(ref.receiver) || ref.args.any { containsDelegatedRefs(it.value) }
|
||||
is StatementRef -> containsDelegatedRefs(ref.statement)
|
||||
else -> false
|
||||
}
|
||||
|
||||
@ -1673,7 +1673,12 @@ class CmdCallMemberSlot(
|
||||
}
|
||||
?: frame.ensureScope().raiseError("member id $methodId not found on ${receiver.objClass.className}")
|
||||
val callArgs = frame.buildArguments(argBase, argCount)
|
||||
val name = rec.memberName ?: "<member>"
|
||||
val rawName = rec.memberName ?: "<member>"
|
||||
val name = if (receiver is ObjInstance && rawName.contains("::")) {
|
||||
rawName.substringAfterLast("::")
|
||||
} else {
|
||||
rawName
|
||||
}
|
||||
if (receiver is ObjQualifiedView) {
|
||||
val result = receiver.invokeInstanceMethod(frame.ensureScope(), name, callArgs)
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
@ -1688,10 +1693,33 @@ class CmdCallMemberSlot(
|
||||
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.ensureScope(), receiver, decl)
|
||||
else frame.ensureScope().raiseError("property $name cannot be called with arguments")
|
||||
}
|
||||
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> {
|
||||
ObjRecord.Type.Fun -> {
|
||||
val callScope = inst?.instanceScope ?: frame.ensureScope()
|
||||
rec.value.invoke(callScope, receiver, callArgs, decl)
|
||||
}
|
||||
ObjRecord.Type.Delegated -> {
|
||||
val scope = frame.ensureScope()
|
||||
val delegate = when (receiver) {
|
||||
is ObjInstance -> {
|
||||
val storageName = decl.mangledName(name)
|
||||
var del = receiver.instanceScope[storageName]?.delegate ?: rec.delegate
|
||||
if (del == null) {
|
||||
for (c in receiver.objClass.mro) {
|
||||
del = receiver.instanceScope[c.mangledName(name)]?.delegate
|
||||
if (del != null) break
|
||||
}
|
||||
}
|
||||
del ?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
|
||||
}
|
||||
is ObjClass -> rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate")
|
||||
else -> rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate")
|
||||
}
|
||||
val allArgs = (listOf(receiver, ObjString(name)) + callArgs.list).toTypedArray()
|
||||
delegate.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
|
||||
val propVal = delegate.invokeInstanceMethod(scope, "getValue", Arguments(receiver, ObjString(name)))
|
||||
propVal.invoke(scope, receiver, callArgs, decl)
|
||||
})
|
||||
}
|
||||
else -> frame.ensureScope().raiseError("member $name is not callable")
|
||||
}
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
|
||||
@ -171,6 +171,30 @@ class BytecodeRecentOpsTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun delegatedMemberAccessAndCall() = runTest {
|
||||
eval(
|
||||
"""
|
||||
class ConstDelegate(val v) : Delegate {
|
||||
override fun getValue(thisRef: Object, name: String): Object = v
|
||||
}
|
||||
class ActionDelegate : Delegate {
|
||||
override fun invoke(thisRef: Object, name: String, args...) {
|
||||
val list: List = args as List
|
||||
"Called %s with %d args: %s"(name, list.size, list.toString())
|
||||
}
|
||||
}
|
||||
class C {
|
||||
val a by ConstDelegate(7)
|
||||
fun greet by ActionDelegate()
|
||||
}
|
||||
val c = C()
|
||||
assertEquals(7, c.a)
|
||||
assertEquals("Called greet with 2 args: [hi,world]", c.greet("hi", "world"))
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun qualifiedThisValueRef() = runTest {
|
||||
eval(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user