Support extensions on singleton objects

This commit is contained in:
sergeych committed 2026-04-06 19:24:28 +03:00
1 parent 671583638b
commit 35628c8453
5 files changed
+56 -9

No files matched your search

@@ -85,8 +85,7 @@ internal suspend fun executeFunctionDecl(
}
if (spec.extTypeName != null) {
val type = scope[spec.extTypeName]?.value ?: scope.raiseSymbolNotFound("class ${spec.extTypeName} not found")
if (type !is ObjClass) scope.raiseClassCastError("${spec.extTypeName} is not the class instance")
val type = scope.resolveExtensionReceiverClass(spec.extTypeName)
scope.addExtension(
type,
spec.name,
@@ -167,8 +166,7 @@ internal suspend fun executeFunctionDecl(
val compiledFnBody = annotatedFnBody
spec.extTypeName?.let { typeName ->
val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found")
if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance")
val type = scope.resolveExtensionReceiverClass(typeName)
if (spec.isStatic) {
type.createClassField(
spec.name,
@@ -1004,4 +1004,13 @@ open class Scope(
return rec.value as? net.sergeych.lyng.obj.ObjClass
?: raiseClassCastError("Expected class $name, got ${rec.value.objClass.className}")
}
internal fun resolveExtensionReceiverClass(name: String): net.sergeych.lyng.obj.ObjClass {
val value = get(name)?.value ?: raiseSymbolNotFound("class $name not found")
return when (value) {
is net.sergeych.lyng.obj.ObjClass -> value
is net.sergeych.lyng.obj.ObjInstance -> value.objClass
else -> raiseClassCastError("$name is not the class instance")
}
}
}
@@ -3094,11 +3094,7 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl
?: error("DECL_EXT_PROPERTY expects ExtensionPropertyDecl at $constId")
val type = frame.ensureScope()[decl.extTypeName]?.value
?: frame.ensureScope().raiseSymbolNotFound("class ${decl.extTypeName} not found")
if (type !is ObjClass) {
frame.ensureScope().raiseClassCastError("${decl.extTypeName} is not the class instance")
}
val type = frame.ensureScope().resolveExtensionReceiverClass(decl.extTypeName)
frame.ensureScope().addExtension(
type,
decl.property.name,
+26
View File
@@ -384,6 +384,32 @@ class OOTest {
}
}
@Test
fun testObjectSingletonSupportsExtensions() = runTest {
val scope = Script.newScope()
scope.eval(
"""
object X {
fun base() = "base"
}
fun X.decorate(value): String {
this.base() + ":" + value.toString()
}
val X.tag get() = this.base() + ":tag"
assertEquals("base", X.base())
assertEquals("base:42", X.decorate(42))
assertEquals("base:ok", X.decorate("ok"))
assertEquals("base:tag", X.tag)
// Wrapper names should be generated for singleton-object receivers too.
assertEquals("base:17", __ext__X__decorate(X, 17))
assertEquals("base:tag", __ext_get__X__tag(X))
""".trimIndent()
)
}
@Test
fun testExtensionsAreScopeIsolated() = runTest {
val scope1 = Script.newScope()