Optimize member access via slots

This commit is contained in:
Sergey Chernov 2026-01-25 03:11:40 +03:00
parent 9b580bafb6
commit 74d73540c6
5 changed files with 1820 additions and 598 deletions

File diff suppressed because it is too large Load Diff

View File

@ -392,6 +392,24 @@ open class Scope(
nameToSlot[name]?.let { slots[it] = record }
}
/**
* Apply a precomputed slot plan (name -> slot index) for this scope.
* This enables direct slot references to bypass name-based lookup.
*/
fun applySlotPlan(plan: Map<String, Int>) {
if (plan.isEmpty()) return
val maxIndex = plan.values.maxOrNull() ?: return
if (slots.size <= maxIndex) {
val targetSize = maxIndex + 1
while (slots.size < targetSize) {
slots.add(ObjRecord(ObjUnset, isMutable = true))
}
}
for ((name, idx) in plan) {
nameToSlot[name] = idx
}
}
/**
* Clear all references and maps to prevent memory leaks when pooled.
*/
@ -503,6 +521,7 @@ open class Scope(
if (this is ClosureScope) {
callScope.localBindings[name] = it
}
bumpClassLayoutIfNeeded(name, value, recordType)
it
} ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride)
@ -529,6 +548,24 @@ open class Scope(
isTransient = isTransient
)
objects[name] = rec
bumpClassLayoutIfNeeded(name, value, recordType)
if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) {
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
if (inst != null) {
val slot = inst.objClass.fieldSlotForKey(name)
if (slot != null) inst.setFieldSlotRecord(slot.slot, rec)
}
}
if (value is Statement ||
recordType == ObjRecord.Type.Fun ||
recordType == ObjRecord.Type.Delegated ||
recordType == ObjRecord.Type.Property) {
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
if (inst != null) {
val slot = inst.objClass.methodSlotForKey(name)
if (slot != null) inst.setMethodSlotRecord(slot.slot, rec)
}
}
// Index this binding within the current frame to help resolve locals across suspension
localBindings[name] = rec
// If we are a ClosureScope, mirror binding into the caller frame to keep it discoverable
@ -558,6 +595,14 @@ open class Scope(
return rec
}
private fun bumpClassLayoutIfNeeded(name: String, value: Obj, recordType: ObjRecord.Type) {
val cls = thisObj as? net.sergeych.lyng.obj.ObjClass ?: return
if (cls.classScope !== this) return
if (!(value is Statement || recordType == ObjRecord.Type.Fun || recordType == ObjRecord.Type.Delegated)) return
if (cls.members.containsKey(name)) return
cls.layoutVersion += 1
}
fun getOrCreateNamespace(name: String): ObjClass {
val ns = objects.getOrPut(name) { ObjRecord(ObjNamespace(name), isMutable = false) }.value
return ns.objClass

View File

@ -114,8 +114,7 @@ open class ObjClass(
val classId: Long = ClassIdGen.nextId()
var layoutVersion: Int = 0
private val mangledNameCache = mutableMapOf<String, String>()
fun mangledName(name: String): String = mangledNameCache.getOrPut(name) { "$className::$name" }
fun mangledName(name: String): String = "$className::$name"
/**
* Map of public member names to their effective storage keys in instanceScope.objects.
@ -128,7 +127,7 @@ open class ObjClass(
if (cls.className == "Obj") continue
for ((name, rec) in cls.members) {
if (rec.visibility == Visibility.Public) {
val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
res[name] = key
}
}
@ -267,6 +266,119 @@ open class ObjClass(
*/
internal val members = mutableMapOf<String, ObjRecord>()
internal data class FieldSlot(val slot: Int, val record: ObjRecord)
internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass)
internal data class MethodSlot(val slot: Int, val record: ObjRecord)
private var fieldSlotLayoutVersion: Int = -1
private var fieldSlotMap: Map<String, FieldSlot> = emptyMap()
private var fieldSlotCount: Int = 0
private var instanceMemberLayoutVersion: Int = -1
private var instanceMemberCache: Map<String, ResolvedMember> = emptyMap()
private var methodSlotLayoutVersion: Int = -1
private var methodSlotMap: Map<String, MethodSlot> = emptyMap()
private var methodSlotCount: Int = 0
private fun ensureFieldSlots(): Map<String, FieldSlot> {
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
val res = mutableMapOf<String, FieldSlot>()
var idx = 0
for (cls in mro) {
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
val key = cls.mangledName(name)
if (res.containsKey(key)) continue
res[key] = FieldSlot(idx, rec)
idx += 1
}
}
fieldSlotMap = res
fieldSlotCount = idx
fieldSlotLayoutVersion = layoutVersion
return fieldSlotMap
}
private fun ensureInstanceMemberCache(): Map<String, ResolvedMember> {
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
val res = mutableMapOf<String, ResolvedMember>()
for (cls in mro) {
if (cls.className == "Obj") break
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (res.containsKey(name)) continue
val decl = rec.declaringClass ?: cls
res[name] = ResolvedMember(rec, decl)
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach
if (res.containsKey(name)) return@forEach
val decl = rec.declaringClass ?: cls
res[name] = ResolvedMember(rec, decl)
}
}
instanceMemberCache = res
instanceMemberLayoutVersion = layoutVersion
return instanceMemberCache
}
private fun ensureMethodSlots(): Map<String, MethodSlot> {
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
val res = mutableMapOf<String, MethodSlot>()
var idx = 0
for (cls in mro) {
if (cls.className == "Obj") break
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (rec.value !is Statement &&
rec.type != ObjRecord.Type.Delegated &&
rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property) {
continue
}
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (res.containsKey(key)) continue
res[key] = MethodSlot(idx, rec)
idx += 1
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach
if (rec.value !is Statement &&
rec.type != ObjRecord.Type.Delegated &&
rec.type != ObjRecord.Type.Property) return@forEach
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (res.containsKey(key)) return@forEach
res[key] = MethodSlot(idx, rec)
idx += 1
}
}
methodSlotMap = res
methodSlotCount = idx
methodSlotLayoutVersion = layoutVersion
return methodSlotMap
}
internal fun fieldSlotCount(): Int {
ensureFieldSlots()
return fieldSlotCount
}
internal fun fieldSlotForKey(key: String): FieldSlot? {
ensureFieldSlots()
return fieldSlotMap[key]
}
internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots()
internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name]
internal fun methodSlotCount(): Int {
ensureMethodSlots()
return methodSlotCount
}
internal fun methodSlotForKey(key: String): MethodSlot? {
ensureMethodSlots()
return methodSlotMap[key]
}
internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots()
override fun toString(): String = className
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
@ -284,8 +396,8 @@ open class ObjClass(
for (cls in mro) {
// 1) members-defined methods and fields
for ((k, v) in cls.members) {
if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field)) {
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField)) {
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
if (!res.containsKey(key)) {
res[key] = v
}
@ -327,12 +439,47 @@ open class ObjClass(
val stableParent = classScope ?: scope.parent
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
instance.instanceScope.currentClassCtx = null
val fieldSlots = fieldSlotMap()
if (fieldSlots.isNotEmpty()) {
instance.initFieldSlots(fieldSlotCount())
}
val methodSlots = methodSlotMap()
if (methodSlots.isNotEmpty()) {
instance.initMethodSlots(methodSlotCount())
}
// Expose instance methods (and other callable members) directly in the instance scope for fast lookup
// This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust
instance.instanceScope.objects.putAll(templateMethods)
if (methodSlots.isNotEmpty()) {
for ((key, rec) in templateMethods) {
val slot = methodSlots[key]
if (slot != null) {
instance.setMethodSlotRecord(slot.slot, rec)
}
}
}
for (p in templateOthers) {
instance.instanceScope.objects[p.first] = p.second.copy()
val rec = p.second.copy()
instance.instanceScope.objects[p.first] = rec
val slot = fieldSlots[p.first]
if (slot != null) {
instance.setFieldSlotRecord(slot.slot, rec)
}
if (methodSlots.isNotEmpty()) {
val mSlot = methodSlots[p.first]
if (mSlot != null) {
instance.setMethodSlotRecord(mSlot.slot, rec)
}
}
}
if (methodSlots.isNotEmpty()) {
for ((_, mSlot) in methodSlots) {
val idx = mSlot.slot
if (idx >= 0 && idx < instance.methodSlots.size && instance.methodSlots[idx] == null) {
instance.setMethodSlotRecord(idx, mSlot.record)
}
}
}
return instance
}
@ -417,6 +564,10 @@ open class ObjClass(
if (rec != null) {
val mangled = c.mangledName(p.name)
instance.instanceScope.objects[mangled] = rec
val slot = instance.objClass.fieldSlotForKey(mangled)
if (slot != null) {
instance.setFieldSlotRecord(slot.slot, rec)
}
}
}
}
@ -738,5 +889,3 @@ open class ObjClass(
scope.raiseNotImplemented()
}

View File

@ -30,6 +30,36 @@ import net.sergeych.lynon.LynonType
class ObjInstance(override val objClass: ObjClass) : Obj() {
internal lateinit var instanceScope: Scope
internal var fieldSlots: Array<ObjRecord?> = emptyArray()
internal var methodSlots: Array<ObjRecord?> = emptyArray()
internal fun initFieldSlots(size: Int) {
fieldSlots = arrayOfNulls(size)
}
internal fun setFieldSlotRecord(slot: Int, rec: ObjRecord) {
if (slot >= 0 && slot < fieldSlots.size) fieldSlots[slot] = rec
}
internal fun initMethodSlots(size: Int) {
methodSlots = arrayOfNulls(size)
}
internal fun setMethodSlotRecord(slot: Int, rec: ObjRecord) {
if (slot >= 0 && slot < methodSlots.size) methodSlots[slot] = rec
}
internal fun fieldRecordForKey(key: String): ObjRecord? {
val slot = objClass.fieldSlotForKey(key) ?: return null
val idx = slot.slot
return if (idx >= 0 && idx < fieldSlots.size) fieldSlots[idx] else null
}
internal fun methodRecordForKey(key: String): ObjRecord? {
val slot = objClass.methodSlotForKey(key) ?: return null
val idx = slot.slot
return if (idx >= 0 && idx < methodSlots.size) methodSlots[idx] else null
}
override suspend fun readField(scope: Scope, name: String): ObjRecord {
val caller = scope.currentClassCtx
@ -37,6 +67,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// Fast path for public members when outside any class context
if (caller == null) {
objClass.publicMemberResolution[name]?.let { key ->
fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) ?: objClass
return resolveRecord(scope, rec, name, decl)
}
}
instanceScope.objects[key]?.let { rec ->
// Directly return fields to bypass resolveRecord overhead
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
@ -56,6 +96,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
}
// Check for private fields (stored in instanceScope)
val mangled = c.mangledName(name)
fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return resolveRecord(scope, rec, name, c)
}
}
methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return resolveRecord(scope, rec, name, c)
}
}
instanceScope.objects[mangled]?.let { rec ->
if (rec.visibility == Visibility.Private) {
return resolveRecord(scope, rec, name, c)
@ -67,6 +117,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
for (cls in objClass.mro) {
if (cls.className == "Obj") break
val mangled = cls.mangledName(name)
fieldRecordForKey(mangled)?.let { rec ->
if (canAccessMember(rec.visibility, cls, caller, name)) {
return resolveRecord(scope, rec, name, cls)
}
}
methodRecordForKey(mangled)?.let { rec ->
if (canAccessMember(rec.visibility, cls, caller, name)) {
return resolveRecord(scope, rec, name, cls)
}
}
instanceScope.objects[mangled]?.let { rec ->
if (canAccessMember(rec.visibility, cls, caller, name)) {
return resolveRecord(scope, rec, name, cls)
@ -81,6 +141,12 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return resolveRecord(scope, rec, name, decl)
}
}
methodRecordForKey(name)?.let { rec ->
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.visibility, decl, caller, name)) {
return resolveRecord(scope, rec, name, decl)
}
}
// 3. Fall back to super (handles class members and extensions)
return super.readField(scope, name)
@ -109,8 +175,13 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
val d = decl ?: obj.declaringClass
if (d != null) {
val mangled = d.mangledName(name)
instanceScope.objects[mangled]?.let {
targetRec = it
fieldRecordForKey(mangled)?.let {
targetRec = it
}
if (targetRec === obj) {
instanceScope.objects[mangled]?.let {
targetRec = it
}
}
}
if (targetRec === obj) {
@ -134,10 +205,29 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// Fast path for public members when outside any class context
if (caller == null) {
objClass.publicMemberResolution[name]?.let { key ->
fieldRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public) {
// Skip property/delegated overhead if it's a plain mutable field
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && rec.isMutable && !rec.isAbstract) {
if (rec.value.assign(scope, newValue) == null)
rec.value = newValue
return
}
updateRecord(scope, rec, name, newValue, rec.declaringClass)
return
}
}
methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
updateRecord(scope, rec, name, newValue, rec.declaringClass)
return
}
}
instanceScope.objects[key]?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public) {
// Skip property/delegated overhead if it's a plain mutable field
if (rec.type == ObjRecord.Type.Field && rec.isMutable && !rec.isAbstract) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && rec.isMutable && !rec.isAbstract) {
if (rec.value.assign(scope, newValue) == null)
rec.value = newValue
return
@ -160,6 +250,19 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
}
// Check for private fields (stored in instanceScope)
val mangled = c.mangledName(name)
fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
updateRecord(scope, rec, name, newValue, c)
return
}
}
methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
updateRecord(scope, rec, name, newValue, c)
return
}
}
instanceScope.objects[mangled]?.let { rec ->
if (rec.visibility == Visibility.Private) {
updateRecord(scope, rec, name, newValue, c)
@ -172,6 +275,19 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
for (cls in objClass.mro) {
if (cls.className == "Obj") break
val mangled = cls.mangledName(name)
fieldRecordForKey(mangled)?.let { rec ->
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) {
updateRecord(scope, rec, name, newValue, cls)
return
}
}
methodRecordForKey(mangled)?.let { rec ->
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name) &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
updateRecord(scope, rec, name, newValue, cls)
return
}
}
instanceScope.objects[mangled]?.let { rec ->
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) {
updateRecord(scope, rec, name, newValue, cls)
@ -188,6 +304,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return
}
}
methodRecordForKey(name)?.let { rec ->
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name) &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
updateRecord(scope, rec, name, newValue, decl)
return
}
}
super.writeField(scope, name, newValue)
}
@ -225,6 +349,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// Fast path for public members when outside any class context
if (caller == null) {
objClass.publicMemberResolution[name]?.let { key ->
methodRecordForKey(key)?.let { rec ->
if (rec.visibility == Visibility.Public && !rec.isAbstract) {
val decl = rec.declaringClass
if (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(instanceScope, this, args, decl)
}
}
}
instanceScope.objects[key]?.let { rec ->
if (rec.visibility == Visibility.Public && !rec.isAbstract) {
val decl = rec.declaringClass
@ -241,6 +375,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// 0. Prefer private member of current class context
caller?.let { c ->
val mangled = c.mangledName(name)
methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(instanceScope, this, args, c)
}
}
}
instanceScope.objects[mangled]?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
@ -261,50 +404,58 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
}
}
// 1. Walk MRO to find member, handling delegation
for (cls in objClass.mro) {
if (cls.className == "Obj") break
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
if (rec != null && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Delegated) {
val storageName = cls.mangledName(name)
val del = instanceScope[storageName]?.delegate ?: rec.delegate
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
// For delegated member, try 'invoke' first if it's a function-like call
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
// Fallback: property delegation (getValue then call result)
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
propVal.invoke(scope, this, args, rec.declaringClass ?: cls)
})
}
val decl = rec.declaringClass ?: cls
// Fast path for non-delegated instance methods in class context
methodRecordForKey(name)?.let { rec ->
if (!rec.isAbstract && rec.type == ObjRecord.Type.Fun) {
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) ?: objClass
val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(
ObjIllegalAccessException(
scope,
"can't invoke method $name (declared in ${decl.className})"
)
)
if (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(
instanceScope,
this,
args,
decl
)
} else if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Argument) {
val resolved = readField(scope, name)
return resolved.value.invoke(scope, this, args, resolved.declaringClass)
if (canAccessMember(rec.visibility, decl, effectiveCaller, name)) {
return rec.value.invoke(instanceScope, this, args, decl)
}
}
}
// 1. Resolve instance member via cached MRO lookup, handling delegation
objClass.resolveInstanceMember(name)?.let { resolvedMember ->
val rec = resolvedMember.record
val decl = resolvedMember.declaringClass
if (rec.type == ObjRecord.Type.Delegated) {
val storageName = decl.mangledName(name)
val del = instanceScope[storageName]?.delegate ?: rec.delegate
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
// For delegated member, try 'invoke' first if it's a function-like call
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
// Fallback: property delegation (getValue then call result)
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
propVal.invoke(scope, this, args, rec.declaringClass ?: decl)
})
}
val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(
ObjIllegalAccessException(
scope,
"can't invoke method $name (declared in ${decl.className})"
)
)
if (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(
instanceScope,
this,
args,
decl
)
} else if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Argument) {
val resolved = readField(scope, name)
return resolved.value.invoke(scope, this, args, resolved.declaringClass)
}
}
// 2. Fall back to super (handles extensions and root fallback)
return super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
}
@ -431,6 +582,14 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
override suspend fun readField(scope: Scope, name: String): ObjRecord {
// Qualified field access: prefer mangled storage for the qualified ancestor
val mangled = "${startClass.className}::$name"
instance.fieldRecordForKey(mangled)?.let { rec ->
// Visibility: declaring class is the qualified ancestor for mangled storage
val decl = rec.declaringClass ?: startClass
val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
return instance.resolveRecord(scope, rec, name, decl)
}
instance.instanceScope.objects[mangled]?.let { rec ->
// Visibility: declaring class is the qualified ancestor for mangled storage
val decl = rec.declaringClass ?: startClass
@ -467,6 +626,18 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
// Qualified write: target mangled storage for the ancestor
val mangled = "${startClass.className}::$name"
instance.fieldRecordForKey(mangled)?.let { f ->
val decl = f.declaringClass ?: startClass
val caller = scope.currentClassCtx
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
ObjIllegalAccessException(
scope,
"can't assign to field $name (declared in ${decl.className})"
).raise()
if (!f.isMutable && f.value !== ObjUnset) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
if (f.value.assign(scope, newValue) == null) f.value = newValue
return
}
instance.instanceScope.objects[mangled]?.let { f ->
val decl = f.declaringClass ?: startClass
val caller = scope.currentClassCtx
@ -548,4 +719,4 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
}
override fun toString(): String = instance.toString()
}
}

View File

@ -381,7 +381,7 @@ class CastRef(
}
/** Qualified `this@Type`: resolves to a view of current `this` starting dispatch from the ancestor Type. */
class QualifiedThisRef(private val typeName: String, private val atPos: Pos) : ObjRef {
class QualifiedThisRef(val typeName: String, private val atPos: Pos) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
val t = scope[typeName]?.value as? ObjClass
?: scope.raiseError("unknown type $typeName")
@ -403,6 +403,188 @@ class QualifiedThisRef(private val typeName: String, private val atPos: Pos) : O
}
}
private suspend fun resolveQualifiedThisInstance(scope: Scope, typeName: String): Pair<ObjInstance, ObjClass> {
val t = scope[typeName]?.value as? ObjClass
?: scope.raiseError("unknown type $typeName")
var s: Scope? = scope
while (s != null) {
val inst = s.thisObj as? ObjInstance
if (inst != null && (inst.objClass === t || inst.objClass.allParentsSet.contains(t))) {
return inst to t
}
s = s.parent
}
scope.raiseClassCastError(
"No instance of type ${t.className} found in the scope chain"
)
}
/**
* Fast path for direct `this@Type.name` access using slot maps when possible.
*/
class QualifiedThisFieldSlotRef(
private val typeName: String,
val name: String,
private val isOptional: Boolean
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
if (isOptional && inst == ObjNull) return ObjNull.asMutable
if (startClass !== inst.objClass) {
return ObjQualifiedView(inst, startClass).readField(scope, name)
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return inst.resolveRecord(scope, rec, name, caller)
}
}
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return inst.resolveRecord(scope, rec, name, caller)
}
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
inst.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
return inst.resolveRecord(scope, rec, name, decl)
}
}
return inst.readField(scope, name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
if (isOptional && inst == ObjNull) return
if (startClass !== inst.objClass) {
ObjQualifiedView(inst, startClass).writeField(scope, name, newValue)
return
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
writeDirectOrFallback(scope, inst, rec, name, newValue, caller)
return
}
}
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
inst.writeField(scope, name, newValue)
return
}
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
writeDirectOrFallback(scope, inst, rec, name, newValue, decl)
return
}
}
inst.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
inst.writeField(scope, name, newValue)
return
}
}
inst.writeField(scope, name, newValue)
}
private suspend fun writeDirectOrFallback(
scope: Scope,
inst: ObjInstance,
rec: ObjRecord,
name: String,
newValue: Obj,
decl: ObjClass?
) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
inst.writeField(scope, name, newValue)
}
}
}
/**
* Fast path for direct `this@Type.method(...)` calls using slots when the qualifier is the
* dynamic class. Otherwise falls back to a qualified view dispatch.
*/
class QualifiedThisMethodSlotCallRef(
private val typeName: String,
private val name: String,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
override suspend fun evalValue(scope: Scope): Obj {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
if (isOptional && inst == ObjNull) return ObjNull
val callArgs = args.toArguments(scope, tailBlock)
if (startClass !== inst.objClass) {
return ObjQualifiedView(inst, startClass).invokeInstanceMethod(scope, name, callArgs, null)
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, caller)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(inst.instanceScope, inst, callArgs, caller)
}
}
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
val effectiveCaller = caller ?: if (scope.thisObj === inst) inst.objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
}
}
}
return inst.invokeInstanceMethod(scope, name, callArgs)
}
}
/** Assignment compound op: target op= value */
class AssignOpRef(
private val op: BinOp,
@ -691,9 +873,20 @@ class FieldRef(
if (effectiveKey != null) {
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc ->
if (obj is ObjInstance && obj.objClass === cls) {
val rec = obj.instanceScope.objects[effectiveKey]
if (rec != null && rec.type != ObjRecord.Type.Delegated) rec
else obj.readField(sc, name)
val slot = cls.fieldSlotForKey(effectiveKey)
if (slot != null) {
val idx = slot.slot
val rec = if (idx >= 0 && idx < obj.fieldSlots.size) obj.fieldSlots[idx] else null
if (rec != null &&
(rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) &&
!rec.isAbstract) {
rec
} else obj.readField(sc, name)
} else {
val rec = obj.fieldRecordForKey(effectiveKey) ?: obj.instanceScope.objects[effectiveKey]
if (rec != null && rec.type != ObjRecord.Type.Delegated) rec
else obj.readField(sc, name)
}
} else obj.readField(sc, name)
}
} else {
@ -809,10 +1002,24 @@ class FieldRef(
if (effectiveKey != null) {
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, nv ->
if (obj is ObjInstance && obj.objClass === cls) {
val rec = obj.instanceScope.objects[effectiveKey]
if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && rec.type == ObjRecord.Type.Field) {
if (rec.value.assign(sc, nv) == null) rec.value = nv
} else obj.writeField(sc, name, nv)
val slot = cls.fieldSlotForKey(effectiveKey)
if (slot != null) {
val idx = slot.slot
val rec = if (idx >= 0 && idx < obj.fieldSlots.size) obj.fieldSlots[idx] else null
if (rec != null &&
rec.effectiveWriteVisibility == Visibility.Public &&
rec.isMutable &&
(rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) &&
!rec.isAbstract) {
if (rec.value.assign(sc, nv) == null) rec.value = nv
} else obj.writeField(sc, name, nv)
} else {
val rec = obj.fieldRecordForKey(effectiveKey) ?: obj.instanceScope.objects[effectiveKey]
if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable &&
(rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField)) {
if (rec.value.assign(sc, nv) == null) rec.value = nv
} else obj.writeField(sc, name, nv)
}
} else obj.writeField(sc, name, nv)
}
} else {
@ -890,6 +1097,113 @@ class FieldRef(
}
}
/**
* Fast path for direct `this.name` access using slot maps.
* Falls back to normal member resolution when needed.
*/
class ThisFieldSlotRef(
val name: String,
private val isOptional: Boolean
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
val th = scope.thisObj
if (th == ObjNull && isOptional) return ObjNull.asMutable
if (th !is ObjInstance) return th.readField(scope, name)
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, caller)
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, caller)
}
}
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
th.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
return th.resolveRecord(scope, rec, name, decl)
}
}
return th.readField(scope, name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val th = scope.thisObj
if (th == ObjNull && isOptional) return
if (th !is ObjInstance) {
th.writeField(scope, name, newValue)
return
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
writeDirectOrFallback(scope, th, rec, name, newValue, caller)
return
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
writeDirectOrFallback(scope, th, rec, name, newValue, decl)
return
}
}
th.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
th.writeField(scope, name, newValue)
}
private suspend fun writeDirectOrFallback(
scope: Scope,
inst: ObjInstance,
rec: ObjRecord,
name: String,
newValue: Obj,
decl: ObjClass?
) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
inst.writeField(scope, name, newValue)
}
}
}
/**
* Reference to index access (a[i]) with optional chaining.
*/
@ -1336,36 +1650,50 @@ class MethodCallRef(
is ObjInstance -> {
// Prefer resolved class member to avoid per-call lookup on hit
// BUT only if it's NOT a root object member (which can be shadowed by extensions)
var hierarchyMember: ObjRecord? = null
val cls0 = base.objClass
val keyInScope = cls0.publicMemberResolution[name]
if (keyInScope != null) {
val rec = base.instanceScope.objects[keyInScope]
if (rec != null && rec.type == ObjRecord.Type.Fun) {
hierarchyMember = rec
}
}
val methodSlot = if (keyInScope != null) cls0.methodSlotForKey(keyInScope) else null
val fastRec = if (methodSlot != null) {
val idx = methodSlot.slot
if (idx >= 0 && idx < base.methodSlots.size) base.methodSlots[idx] else null
} else if (keyInScope != null) {
base.methodRecordForKey(keyInScope) ?: base.instanceScope.objects[keyInScope]
} else null
val resolved = if (fastRec != null) null else cls0.resolveInstanceMember(name)
if (hierarchyMember == null) {
for (cls in base.objClass.mro) {
if (cls.className == "Obj") break
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
if (rec != null && !rec.isAbstract && rec.type != ObjRecord.Type.Field) {
hierarchyMember = rec
break
val targetRec = when {
fastRec != null && fastRec.type == ObjRecord.Type.Fun -> fastRec
resolved != null && resolved.record.type == ObjRecord.Type.Fun && !resolved.record.isAbstract -> resolved.record
else -> null
}
if (targetRec != null) {
val visibility = targetRec.visibility
val decl = targetRec.declaringClass ?: (resolved?.declaringClass ?: cls0)
if (methodSlot != null && targetRec.type == ObjRecord.Type.Fun) {
val slotIndex = methodSlot.slot
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
val inst = obj as ObjInstance
if (inst.objClass === cls0) {
val rec = if (slotIndex >= 0 && slotIndex < inst.methodSlots.size) inst.methodSlots[slotIndex] else null
if (rec != null && rec.type == ObjRecord.Type.Fun && !rec.isAbstract) {
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
rec.value.invoke(inst.instanceScope, inst, a, decl)
} else {
obj.invokeInstanceMethod(sc, name, a)
}
} else {
obj.invokeInstanceMethod(sc, name, a)
}
}
} else {
val callable = targetRec.value
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
val inst = obj as ObjInstance
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
callable.invoke(inst.instanceScope, inst, a)
}
}
}
if (hierarchyMember != null) {
val visibility = hierarchyMember.visibility
val callable = hierarchyMember.value
val decl = hierarchyMember.declaringClass ?: base.objClass
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
val inst = obj as ObjInstance
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
callable.invoke(inst.instanceScope, inst, a)
}
} else {
// Fallback to name-based lookup per call (handles extensions and root members)
@ -1399,6 +1727,57 @@ class MethodCallRef(
}
}
/**
* Fast path for direct `this.method(...)` calls using slot maps.
* Falls back to normal invoke semantics when needed.
*/
class ThisMethodSlotCallRef(
private val name: String,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
override suspend fun evalValue(scope: Scope): Obj {
val base = scope.thisObj
if (base == ObjNull && isOptional) return ObjNull
val callArgs = args.toArguments(scope, tailBlock)
if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs)
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
base.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, caller)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(base.instanceScope, base, callArgs, caller)
}
}
}
}
val key = base.objClass.publicMemberResolution[name] ?: name
base.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: base.objClass.findDeclaringClassOf(name) ?: base.objClass
val effectiveCaller = caller ?: if (scope.thisObj === base) base.objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(base.instanceScope, base, callArgs, decl)
}
}
}
return base.invokeInstanceMethod(scope, name, callArgs)
}
}
/**
* Reference to a local/visible variable by name (Phase A: scope lookup).
*/
@ -1729,6 +2108,240 @@ class FastLocalVarRef(
}
}
/**
* Identifier reference in class context that prefers member slots on `this` after local lookup.
* Falls back to normal scope lookup for globals/outer scopes.
*/
class ImplicitThisMemberRef(
val name: String,
val atPos: Pos
) : ObjRef {
override fun forEachVariable(block: (String) -> Unit) {
block(name)
}
override fun forEachVariableWithPos(block: (String, Pos) -> Unit) {
block(name, atPos)
}
override suspend fun get(scope: Scope): ObjRecord {
scope.pos = atPos
val caller = scope.currentClassCtx
val th = scope.thisObj
// 1) locals in the same `this` chain
var s: Scope? = scope
while (s != null && s.thisObj === th) {
scope.tryGetLocalRecord(s, name, caller)?.let { return it }
s = s.parent
}
// 2) member slots on this instance
if (th is ObjInstance) {
// private member access for current class context
caller?.let { c ->
val mangled = c.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, c)
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, c)
}
}
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
th.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
return th.resolveRecord(scope, rec, name, decl)
}
}
}
// 3) fallback to normal scope resolution (globals/outer scopes)
scope[name]?.let { return it }
try {
return th.readField(scope, name)
} catch (e: ExecutionError) {
if ((e.message ?: "").contains("no such field: $name")) scope.raiseSymbolNotFound(name)
throw e
}
}
override suspend fun evalValue(scope: Scope): Obj {
val rec = get(scope)
return scope.resolve(rec, name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
scope.pos = atPos
val caller = scope.currentClassCtx
val th = scope.thisObj
// 1) locals in the same `this` chain
var s: Scope? = scope
while (s != null && s.thisObj === th) {
val rec = scope.tryGetLocalRecord(s, name, caller)
if (rec != null) {
scope.assign(rec, name, newValue)
return
}
s = s.parent
}
// 2) member slots on this instance
if (th is ObjInstance) {
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
th.writeField(scope, name, newValue)
}
return
}
}
th.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
}
// 3) fallback to normal scope resolution
scope[name]?.let { stored ->
scope.assign(stored, name, newValue)
return
}
th.writeField(scope, name, newValue)
}
}
/**
* Fast path for implicit member calls in class bodies: `foo(...)` resolves locals first,
* then falls back to member lookup on `this`.
*/
class ImplicitThisMethodCallRef(
private val name: String,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean,
private val atPos: Pos
) : ObjRef {
private val memberRef = ImplicitThisMemberRef(name, atPos)
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
override suspend fun evalValue(scope: Scope): Obj {
scope.pos = atPos
val callee = memberRef.evalValue(scope)
if (callee == ObjNull && isOptional) return ObjNull
val callArgs = args.toArguments(scope, tailBlock)
val usePool = PerfFlags.SCOPE_POOL
return if (usePool) {
scope.withChildFrame(callArgs) { child ->
callee.callOn(child)
}
} else {
callee.callOn(scope.createChildScope(scope.pos, callArgs))
}
}
}
/**
* Direct local slot reference with known slot index and lexical depth.
* Depth=0 means current scope, depth=1 means parent scope, etc.
*/
class LocalSlotRef(
val name: String,
private val slot: Int,
private val depth: Int,
private val atPos: Pos,
) : ObjRef {
override fun forEachVariable(block: (String) -> Unit) {
block(name)
}
private val fallbackRef = LocalVarRef(name, atPos)
private var cachedFrameId: Long = 0L
private var cachedOwner: Scope? = null
private var cachedOwnerVerified: Boolean = false
private fun resolveOwner(scope: Scope): Scope? {
if (cachedOwner != null && cachedFrameId == scope.frameId && cachedOwnerVerified) return cachedOwner
var s: Scope? = scope
var remaining = depth
while (s != null && remaining > 0) {
s = s.parent
remaining--
}
if (s == null || s.getSlotIndexOf(name) != slot) {
cachedOwner = null
cachedOwnerVerified = false
cachedFrameId = scope.frameId
return null
}
cachedOwner = s
cachedOwnerVerified = true
cachedFrameId = scope.frameId
return s
}
override suspend fun get(scope: Scope): ObjRecord {
scope.pos = atPos
val owner = resolveOwner(scope) ?: return fallbackRef.get(scope)
if (slot < 0 || slot >= owner.slotCount()) return fallbackRef.get(scope)
val rec = owner.getSlotRecord(slot)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
}
return rec
}
override suspend fun evalValue(scope: Scope): Obj {
scope.pos = atPos
val owner = resolveOwner(scope) ?: return fallbackRef.evalValue(scope)
if (slot < 0 || slot >= owner.slotCount()) return fallbackRef.evalValue(scope)
val rec = owner.getSlotRecord(slot)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
}
return scope.resolve(rec, name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
scope.pos = atPos
val owner = resolveOwner(scope) ?: run {
fallbackRef.setAt(pos, scope, newValue)
return
}
if (slot < 0 || slot >= owner.slotCount()) {
fallbackRef.setAt(pos, scope, newValue)
return
}
val rec = owner.getSlotRecord(slot)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
}
scope.assign(rec, name, newValue)
}
}
class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
override fun forEachVariable(block: (String) -> Unit) {
for (e in entries) {
@ -1910,7 +2523,15 @@ class AssignRef(
val v = value.evalValue(scope)
// For properties, we should not call get() on target because it invokes the getter.
// Instead, we call setAt directly.
if (target is FieldRef || target is IndexRef || target is LocalVarRef || target is FastLocalVarRef || target is BoundLocalVarRef) {
if (target is FieldRef ||
target is IndexRef ||
target is LocalVarRef ||
target is FastLocalVarRef ||
target is BoundLocalVarRef ||
target is LocalSlotRef ||
target is ThisFieldSlotRef ||
target is QualifiedThisFieldSlotRef ||
target is ImplicitThisMemberRef) {
target.setAt(atPos, scope, v)
} else {
val rec = target.get(scope)
@ -1923,4 +2544,4 @@ class AssignRef(
}
}
// (duplicate LocalVarRef removed; the canonical implementation is defined earlier in this file)
// (duplicate LocalVarRef removed; the canonical implementation is defined earlier in this file)