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] 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] 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.
|
- [x] Add a JVM test for a callable property call compiled to bytecode.
|
||||||
- [ ] Step 18: Delegated member access in bytecode.
|
- [x] Step 18: Delegated member access in bytecode.
|
||||||
- [ ] Remove `containsDelegatedRefs` guard once bytecode emits delegated get/set/call correctly.
|
- [x] Remove `containsDelegatedRefs` guard once bytecode emits delegated get/set/call correctly.
|
||||||
- [ ] Add JVM coverage for delegated member get/set/call in bytecode.
|
- [x] Add JVM coverage for delegated member get/set/call in bytecode.
|
||||||
- [ ] Step 19: Unknown receiver member access in bytecode.
|
- [ ] Step 19: Unknown receiver member access in bytecode.
|
||||||
- [ ] Decide on allowed fallback behavior for unknown receiver types without runtime name resolution.
|
- [ ] 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.
|
- [ ] 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 {
|
private fun containsDelegatedRefs(ref: ObjRef): Boolean {
|
||||||
return when (ref) {
|
return when (ref) {
|
||||||
is LocalSlotRef -> ref.isDelegated
|
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 BinaryOpRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right)
|
||||||
is UnaryOpRef -> containsDelegatedRefs(ref.a)
|
is UnaryOpRef -> containsDelegatedRefs(ref.a)
|
||||||
is CastRef -> containsDelegatedRefs(ref.castValueRef()) || containsDelegatedRefs(ref.castTypeRef())
|
is CastRef -> containsDelegatedRefs(ref.castValueRef()) || containsDelegatedRefs(ref.castTypeRef())
|
||||||
@ -2103,14 +2091,7 @@ class Compiler(
|
|||||||
is ConditionalRef ->
|
is ConditionalRef ->
|
||||||
containsDelegatedRefs(ref.condition) || containsDelegatedRefs(ref.ifTrue) || containsDelegatedRefs(ref.ifFalse)
|
containsDelegatedRefs(ref.condition) || containsDelegatedRefs(ref.ifTrue) || containsDelegatedRefs(ref.ifFalse)
|
||||||
is ElvisRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right)
|
is ElvisRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right)
|
||||||
is FieldRef -> {
|
is FieldRef -> containsDelegatedRefs(ref.target)
|
||||||
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 IndexRef -> containsDelegatedRefs(ref.targetRef) || containsDelegatedRefs(ref.indexRef)
|
is IndexRef -> containsDelegatedRefs(ref.targetRef) || containsDelegatedRefs(ref.indexRef)
|
||||||
is ListLiteralRef -> ref.entries().any {
|
is ListLiteralRef -> ref.entries().any {
|
||||||
when (it) {
|
when (it) {
|
||||||
@ -2125,14 +2106,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CallRef -> containsDelegatedRefs(ref.target) || ref.args.any { containsDelegatedRefs(it.value) }
|
is CallRef -> containsDelegatedRefs(ref.target) || ref.args.any { containsDelegatedRefs(it.value) }
|
||||||
is MethodCallRef -> {
|
is MethodCallRef -> containsDelegatedRefs(ref.receiver) || ref.args.any { containsDelegatedRefs(it.value) }
|
||||||
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 StatementRef -> containsDelegatedRefs(ref.statement)
|
is StatementRef -> containsDelegatedRefs(ref.statement)
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1673,7 +1673,12 @@ class CmdCallMemberSlot(
|
|||||||
}
|
}
|
||||||
?: frame.ensureScope().raiseError("member id $methodId not found on ${receiver.objClass.className}")
|
?: frame.ensureScope().raiseError("member id $methodId not found on ${receiver.objClass.className}")
|
||||||
val callArgs = frame.buildArguments(argBase, argCount)
|
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) {
|
if (receiver is ObjQualifiedView) {
|
||||||
val result = receiver.invokeInstanceMethod(frame.ensureScope(), name, callArgs)
|
val result = receiver.invokeInstanceMethod(frame.ensureScope(), name, callArgs)
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||||
@ -1688,10 +1693,33 @@ class CmdCallMemberSlot(
|
|||||||
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.ensureScope(), receiver, decl)
|
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.ensureScope(), receiver, decl)
|
||||||
else frame.ensureScope().raiseError("property $name cannot be called with arguments")
|
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()
|
val callScope = inst?.instanceScope ?: frame.ensureScope()
|
||||||
rec.value.invoke(callScope, receiver, callArgs, decl)
|
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")
|
else -> frame.ensureScope().raiseError("member $name is not callable")
|
||||||
}
|
}
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
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
|
@Test
|
||||||
fun qualifiedThisValueRef() = runTest {
|
fun qualifiedThisValueRef() = runTest {
|
||||||
eval(
|
eval(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user