From 4269310beb5ec476d35d6325d3de4bce1108c971 Mon Sep 17 00:00:00 2001 From: sergeych Date: Fri, 27 Mar 2026 19:19:38 +0300 Subject: [PATCH] Use canonical class identity for delegate checks --- .../kotlin/net/sergeych/lyng/Compiler.kt | 23 +++++++++++++++---- .../kotlin/net/sergeych/lyng/obj/ObjClass.kt | 20 ++++++++++++++++ .../net/sergeych/lyng/obj/ObjDynamic.kt | 1 + lynglib/stdlib/lyng/root.lyng | 3 +-- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index faa8fdf..9b52b49 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -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, val methodIds: Map, 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") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt index a09a22d..1441bab 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -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 = parents.toList() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt index 16e723e..1a602ba 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt @@ -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") } diff --git a/lynglib/stdlib/lyng/root.lyng b/lynglib/stdlib/lyng/root.lyng index 2bcf039..1aedcc7 100644 --- a/lynglib/stdlib/lyng/root.lyng +++ b/lynglib/stdlib/lyng/root.lyng @@ -421,7 +421,6 @@ fun with(self: T, block: T.()->R): R { Can only be used with 'val' properties. */ class lazy(creatorParam: ThisRefType.()->T) : Delegate { - 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(creatorParam: ThisRefType.()->T) : Delegate