Support extensions on singleton objects
This commit is contained in:
parent
671583638b
commit
35628c8453
@ -9,6 +9,7 @@ For a programmer-focused migration summary across 1.5.x, see `docs/whats_new_1_5
|
||||
- `1.5.4` is the stabilization release for the 1.5 feature set.
|
||||
- The 1.5 line now brings together richer ranges and loops, interpolation, math modules, immutable and observable collections, richer `lyngio`, and much better CLI/IDE support.
|
||||
- `1.5.4` specifically fixes user-visible issues around decimal arithmetic, mixed numeric flows, list behavior, and observable list hooks.
|
||||
- `1.5.4` also fixes extension-member registration for named singleton `object` declarations, so `fun X.foo()` and `val X.bar` now work as expected.
|
||||
- The docs, homepage samples, and release metadata now point at the current stable version.
|
||||
|
||||
## User Highlights Across 1.5.x
|
||||
@ -303,6 +304,23 @@ object Config {
|
||||
Config.show()
|
||||
```
|
||||
|
||||
Named singleton objects can also be used as extension receivers:
|
||||
|
||||
```lyng
|
||||
object X {
|
||||
fun base() = "base"
|
||||
}
|
||||
|
||||
fun X.decorate(value): String {
|
||||
this.base() + ":" + value.toString()
|
||||
}
|
||||
|
||||
val X.tag get() = this.base() + ":tag"
|
||||
|
||||
assertEquals("base:42", X.decorate(42))
|
||||
assertEquals("base:tag", X.tag)
|
||||
```
|
||||
|
||||
### Nested Declarations and Lifted Enums
|
||||
You can now declare classes, objects, enums, and type aliases inside another class. These nested declarations live in the class namespace (no outer instance capture) and are accessed with a qualifier.
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user