Use canonical class identity for delegate checks

This commit is contained in:
Sergey Chernov 2026-03-27 19:19:38 +03:00
parent 7c059b4741
commit 4269310beb
4 changed files with 40 additions and 7 deletions

View File

@ -583,7 +583,7 @@ class Compiler(
val baseNames = cls.directParents.map { it.className }
val nextFieldId = (fieldIds.values.maxOrNull() ?: -1) + 1
val nextMethodId = (methodIds.values.maxOrNull() ?: -1) + 1
return CompileClassInfo(name, fieldIds, methodIds, nextFieldId, nextMethodId, baseNames)
return CompileClassInfo(name, cls.logicalPackageName, fieldIds, methodIds, nextFieldId, nextMethodId, baseNames)
}
private data class BaseMemberIds(
@ -1553,6 +1553,7 @@ class Compiler(
private data class CompileClassInfo(
val name: String,
val packageName: String?,
val fieldIds: Map<String, Int>,
val methodIds: Map<String, Int>,
val nextFieldId: Int,
@ -6710,6 +6711,7 @@ class Compiler(
)
compileClassInfos[qualifiedName] = CompileClassInfo(
name = qualifiedName,
packageName = packageName,
fieldIds = fieldIds,
methodIds = methodIds,
nextFieldId = fieldIds.size,
@ -6832,6 +6834,7 @@ class Compiler(
classCtx?.let { ctx ->
compileClassInfos[className] = CompileClassInfo(
name = className,
packageName = packageName,
fieldIds = ctx.memberFieldIds.toMap(),
methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId,
@ -6873,6 +6876,7 @@ class Compiler(
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
compileClassInfos[className] = CompileClassInfo(
name = className,
packageName = packageName,
fieldIds = baseIds.fieldIds,
methodIds = baseIds.methodIds,
nextFieldId = baseIds.nextFieldId,
@ -7121,6 +7125,7 @@ class Compiler(
}
compileClassInfos[qualifiedName] = CompileClassInfo(
name = qualifiedName,
packageName = packageName,
fieldIds = ctx.memberFieldIds.toMap(),
methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId,
@ -7136,6 +7141,7 @@ class Compiler(
classCtx?.let { ctx ->
compileClassInfos[qualifiedName] = CompileClassInfo(
name = qualifiedName,
packageName = packageName,
fieldIds = ctx.memberFieldIds.toMap(),
methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId,
@ -7222,6 +7228,7 @@ class Compiler(
}
compileClassInfos[qualifiedName] = CompileClassInfo(
name = qualifiedName,
packageName = packageName,
fieldIds = ctx.memberFieldIds.toMap(),
methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId,
@ -8470,9 +8477,9 @@ class Compiler(
?: unwrapDirectRef(initializer)?.let { inferObjClassFromRef(it) }
?: throw ScriptError(initializer.pos, "Delegate type must be known at compile time")
if (initClass !== delegateClass &&
initClass.className != delegateClass.className &&
initClass.logicalName != delegateClass.logicalName &&
!initClass.allParentsSet.contains(delegateClass) &&
!initClass.allParentsSet.any { it.className == delegateClass.className } &&
!initClass.allParentsSet.any { it.logicalName == delegateClass.logicalName } &&
!initClass.allImplementingNames.contains(delegateClass.className)
) {
throw ScriptError(
@ -8562,7 +8569,9 @@ class Compiler(
if (closedParent != null) {
throw ScriptError(Pos.builtIn, "can't inherit from closed class ${closedParent.className}")
}
ObjInstanceClass(info.name, *parents.toTypedArray())
ObjInstanceClass(info.name, *parents.toTypedArray()).apply {
logicalPackageNameOverride = info.packageName
}
}
if (stub is ObjInstanceClass) {
for ((fieldName, fieldId) in info.fieldIds) {
@ -8982,7 +8991,11 @@ class Compiler(
if (isDelegate && initialExpression != null) {
ensureDelegateType(initialExpression)
if (isMutable && resolveInitializerObjClass(initialExpression)?.className == "lazy") {
val lazyClass = resolveClassByName("lazy")
if (isMutable &&
lazyClass != null &&
resolveInitializerObjClass(initialExpression)?.logicalName == lazyClass.logicalName
) {
throw ScriptError(initialExpression.pos, "lazy delegate is read-only")
}
}

View File

@ -106,10 +106,20 @@ open class ObjClass(
vararg parents: ObjClass,
) : Obj() {
private fun declaringModulePackageName(scope: Scope?): String? {
var current = scope
while (current != null) {
if (current is ModuleScope) return current.packageName
current = current.parent
}
return null
}
var isAnonymous: Boolean = false
var isAbstract: Boolean = false
var isClosed: Boolean = false
var logicalPackageNameOverride: String? = null
// Stable identity and simple structural version for PICs
val classId: Long = ClassIdGen.nextId()
@ -169,6 +179,16 @@ open class ObjClass(
*/
var classScope: Scope? = null
/**
* Stable logical identity for class matching across separately instantiated modules.
* Uses declaring module package plus class name when available.
*/
val logicalPackageName: String?
get() = logicalPackageNameOverride ?: declaringModulePackageName(classScope)
val logicalName: String
get() = logicalPackageName?.let { "$it::$className" } ?: className
/** Direct parents in declaration order (kept deterministic). */
val directParents: List<ObjClass> = parents.toList()

View File

@ -129,6 +129,7 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
}
val type = object : ObjClass("Delegate") {}.apply {
logicalPackageNameOverride = "lyng.stdlib"
addFn("getValue") { raiseError("Delegate.getValue is not implemented") }
addFn("setValue") { raiseError("Delegate.setValue is not implemented") }
addFn("invoke") { raiseError("Delegate.invoke is not implemented") }

View File

@ -421,7 +421,6 @@ fun with<T,R>(self: T, block: T.()->R): R {
Can only be used with 'val' properties.
*/
class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
private val creator: ThisRefType.()->T = creatorParam
private var value = Unset
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
@ -431,7 +430,7 @@ class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,T
override fun getValue(thisRef: ThisRefType, name: String): T {
if (value == Unset)
value = with(thisRef, creator)
value = with(thisRef, creatorParam)
value as T
}
}