another MI bug fixed, added tests for MI serialization
This commit is contained in:
parent
1e6dd89778
commit
41657b3558
@ -64,7 +64,7 @@ open class ObjClass(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* All ancestors as a Set for fast `isInstanceOf` checks. Order is not guaranteed here and
|
* All ancestors as a Set for fast `isInstanceOf` checks. Order is not guaranteed here and
|
||||||
* must not be used for resolution — use [parentsLinearized] instead.
|
* must not be used for resolution
|
||||||
*/
|
*/
|
||||||
val allParentsSet: Set<ObjClass> =
|
val allParentsSet: Set<ObjClass> =
|
||||||
buildSet {
|
buildSet {
|
||||||
@ -173,7 +173,19 @@ open class ObjClass(
|
|||||||
if (isRoot) {
|
if (isRoot) {
|
||||||
c.constructorMeta?.let { meta ->
|
c.constructorMeta?.let { meta ->
|
||||||
val argsHere = argsForThis ?: Arguments.EMPTY
|
val argsHere = argsForThis ?: Arguments.EMPTY
|
||||||
|
// Assign constructor params into instance scope (unmangled)
|
||||||
meta.assignToContext(instance.instanceScope, argsHere)
|
meta.assignToContext(instance.instanceScope, argsHere)
|
||||||
|
// Also expose them under MI-mangled storage keys `${Class}::name` so qualified views can access them
|
||||||
|
// and so that base-class casts like `(obj as Base).field` work.
|
||||||
|
for (p in meta.params) {
|
||||||
|
val rec = instance.instanceScope.objects[p.name]
|
||||||
|
if (rec != null) {
|
||||||
|
val mangled = "${c.className}::${p.name}"
|
||||||
|
// Always point the mangled name to the current record to keep writes consistent
|
||||||
|
// across re-bindings (e.g., second pass before ctor)
|
||||||
|
instance.instanceScope.objects[mangled] = rec
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Initialize direct parents first, in order
|
// Initialize direct parents first, in order
|
||||||
@ -203,6 +215,15 @@ open class ObjClass(
|
|||||||
c.constructorMeta?.let { meta ->
|
c.constructorMeta?.let { meta ->
|
||||||
val argsHere = argsForThis ?: Arguments.EMPTY
|
val argsHere = argsForThis ?: Arguments.EMPTY
|
||||||
meta.assignToContext(instance.instanceScope, argsHere)
|
meta.assignToContext(instance.instanceScope, argsHere)
|
||||||
|
// Ensure mangled aliases exist for qualified access starting from this class
|
||||||
|
for (p in meta.params) {
|
||||||
|
val rec = instance.instanceScope.objects[p.name]
|
||||||
|
if (rec != null) {
|
||||||
|
val mangled = "${c.className}::${p.name}"
|
||||||
|
// Overwrite to ensure alias refers to the latest ObjRecord after re-binding
|
||||||
|
instance.instanceScope.objects[mangled] = rec
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val execScope = instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance)
|
val execScope = instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance)
|
||||||
ctor.execute(execScope)
|
ctor.execute(execScope)
|
||||||
@ -259,7 +280,7 @@ open class ObjClass(
|
|||||||
fun addFn(
|
fun addFn(
|
||||||
name: String,
|
name: String,
|
||||||
isOpen: Boolean = false,
|
isOpen: Boolean = false,
|
||||||
visibility: net.sergeych.lyng.Visibility = net.sergeych.lyng.Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
code: suspend Scope.() -> Obj
|
code: suspend Scope.() -> Obj
|
||||||
) {
|
) {
|
||||||
val stmt = statement { code() }
|
val stmt = statement { code() }
|
||||||
|
|||||||
@ -159,7 +159,10 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||||
|
|
||||||
private val publicFields: Map<String, ObjRecord>
|
private val publicFields: Map<String, ObjRecord>
|
||||||
get() = instanceScope.objects.filter { it.value.visibility.isPublic && it.value.type.serializable }
|
get() = instanceScope.objects.filter {
|
||||||
|
// Expose only human-facing fields: skip MI-mangled storage entries like "Class::name"
|
||||||
|
!it.key.contains("::") && it.value.visibility.isPublic && it.value.type.serializable
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
||||||
@ -237,7 +240,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
|||||||
// Visibility: declaring class is the qualified ancestor for mangled storage
|
// Visibility: declaring class is the qualified ancestor for mangled storage
|
||||||
val decl = rec.declaringClass ?: startClass
|
val decl = rec.declaringClass ?: startClass
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!net.sergeych.lyng.canAccessMember(rec.visibility, decl, caller))
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
||||||
return rec
|
return rec
|
||||||
}
|
}
|
||||||
@ -246,7 +249,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
|||||||
instance.instanceScope[name]?.let { rec ->
|
instance.instanceScope[name]?.let { rec ->
|
||||||
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!net.sergeych.lyng.canAccessMember(rec.visibility, decl, caller))
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl?.className ?: "?"})"))
|
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl?.className ?: "?"})"))
|
||||||
return rec
|
return rec
|
||||||
}
|
}
|
||||||
@ -255,7 +258,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
|||||||
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
||||||
val decl = r.declaringClass ?: startClass
|
val decl = r.declaringClass ?: startClass
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!net.sergeych.lyng.canAccessMember(r.visibility, decl, caller))
|
if (!canAccessMember(r.visibility, decl, caller))
|
||||||
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
||||||
return when (val value = r.value) {
|
return when (val value = r.value) {
|
||||||
is net.sergeych.lyng.Statement -> ObjRecord(value.execute(instance.instanceScope.createChildScope(scope.pos, newThisObj = instance)), r.isMutable)
|
is net.sergeych.lyng.Statement -> ObjRecord(value.execute(instance.instanceScope.createChildScope(scope.pos, newThisObj = instance)), r.isMutable)
|
||||||
@ -269,7 +272,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
|||||||
instance.instanceScope.objects[mangled]?.let { f ->
|
instance.instanceScope.objects[mangled]?.let { f ->
|
||||||
val decl = f.declaringClass ?: startClass
|
val decl = f.declaringClass ?: startClass
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!net.sergeych.lyng.canAccessMember(f.visibility, decl, caller))
|
if (!canAccessMember(f.visibility, decl, caller))
|
||||||
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl.className})").raise()
|
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl.className})").raise()
|
||||||
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||||
if (f.value.assign(scope, newValue) == null) f.value = newValue
|
if (f.value.assign(scope, newValue) == null) f.value = newValue
|
||||||
@ -280,7 +283,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
|||||||
instance.instanceScope[name]?.let { f ->
|
instance.instanceScope[name]?.let { f ->
|
||||||
val decl = f.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
val decl = f.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!net.sergeych.lyng.canAccessMember(f.visibility, decl, caller))
|
if (!canAccessMember(f.visibility, decl, caller))
|
||||||
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl?.className ?: "?"})").raise()
|
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl?.className ?: "?"})").raise()
|
||||||
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||||
if (f.value.assign(scope, newValue) == null) f.value = newValue
|
if (f.value.assign(scope, newValue) == null) f.value = newValue
|
||||||
@ -290,7 +293,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
|||||||
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
||||||
val decl = r.declaringClass ?: startClass
|
val decl = r.declaringClass ?: startClass
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!net.sergeych.lyng.canAccessMember(r.visibility, decl, caller))
|
if (!canAccessMember(r.visibility, decl, caller))
|
||||||
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl.className})").raise()
|
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl.className})").raise()
|
||||||
if (!r.isMutable) scope.raiseError("can't assign to read-only field: $name")
|
if (!r.isMutable) scope.raiseError("can't assign to read-only field: $name")
|
||||||
if (r.value.assign(scope, newValue) == null) r.value = newValue
|
if (r.value.assign(scope, newValue) == null) r.value = newValue
|
||||||
@ -301,7 +304,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
|||||||
memberFromAncestor(name)?.let { rec ->
|
memberFromAncestor(name)?.let { rec ->
|
||||||
val decl = rec.declaringClass ?: startClass
|
val decl = rec.declaringClass ?: startClass
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!net.sergeych.lyng.canAccessMember(rec.visibility, decl, caller))
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
||||||
val saved = instance.instanceScope.currentClassCtx
|
val saved = instance.instanceScope.currentClassCtx
|
||||||
instance.instanceScope.currentClassCtx = decl
|
instance.instanceScope.currentClassCtx = decl
|
||||||
@ -316,7 +319,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
|||||||
instance.instanceScope[name]?.let { rec ->
|
instance.instanceScope[name]?.let { rec ->
|
||||||
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!net.sergeych.lyng.canAccessMember(rec.visibility, decl, caller))
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})"))
|
scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})"))
|
||||||
val saved = instance.instanceScope.currentClassCtx
|
val saved = instance.instanceScope.currentClassCtx
|
||||||
instance.instanceScope.currentClassCtx = decl
|
instance.instanceScope.currentClassCtx = decl
|
||||||
|
|||||||
@ -150,5 +150,49 @@ assertEquals(null, (buzz as? Foo)?.runA())
|
|||||||
// - Foo.protectedInFoo() is accessible inside Foo and any subclass bodies (including FooBar),
|
// - Foo.protectedInFoo() is accessible inside Foo and any subclass bodies (including FooBar),
|
||||||
// but not from unrelated classes/instances.
|
// but not from unrelated classes/instances.
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMITypes() = runTest {
|
||||||
|
eval("""
|
||||||
|
import lyng.serialization
|
||||||
|
|
||||||
|
class Point(x,y)
|
||||||
|
class Color(r,g,b)
|
||||||
|
|
||||||
|
class ColoredPoint(x, y, r, g, b): Point(x,y), Color(r,g,b)
|
||||||
|
|
||||||
|
|
||||||
|
val cp = ColoredPoint(1,2,30,40,50)
|
||||||
|
|
||||||
|
// cp is Color, Point and ColoredPoint:
|
||||||
|
assert(cp is ColoredPoint)
|
||||||
|
assert(cp is Point)
|
||||||
|
assert(cp is Color)
|
||||||
|
|
||||||
|
// Color fields must be in ColoredPoint:
|
||||||
|
assertEquals(30, cp.r)
|
||||||
|
assertEquals(40, cp.g)
|
||||||
|
assertEquals(50, cp.b)
|
||||||
|
|
||||||
|
// point fields must be available too:
|
||||||
|
assertEquals(1, cp.x)
|
||||||
|
assertEquals(2, cp.y)
|
||||||
|
|
||||||
|
|
||||||
|
// if we convert type to color, the fields should be available also:
|
||||||
|
val color = cp as Color
|
||||||
|
assert(color is Color)
|
||||||
|
assertEquals(30, color.r)
|
||||||
|
assertEquals(40, color.g)
|
||||||
|
assertEquals(50, color.b)
|
||||||
|
|
||||||
|
// converted to Point, cp fields are still available:
|
||||||
|
val p = cp as Point
|
||||||
|
assert(p is Point)
|
||||||
|
assertEquals(1, p.x)
|
||||||
|
assertEquals(2, p.y)
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -332,7 +332,9 @@ class LynonTests {
|
|||||||
val encoded = Lynon.encode(value)
|
val encoded = Lynon.encode(value)
|
||||||
println(encoded.toDump())
|
println(encoded.toDump())
|
||||||
println("Encoded size %d: %s"(encoded.size, value))
|
println("Encoded size %d: %s"(encoded.size, value))
|
||||||
assertEquals( value, Lynon.decode(encoded) )
|
Lynon.decode(encoded).also {
|
||||||
|
assertEquals( value, it )
|
||||||
|
}
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
@ -698,6 +700,29 @@ class Wallet( id, ownerKey, balance=0, createdAt=Instant.now().truncateToSecond(
|
|||||||
// println(t2.readField(s, "balance"))
|
// println(t2.readField(s, "balance"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMISerialization() = runTest {
|
||||||
|
val s = testScope()
|
||||||
|
s.eval("""
|
||||||
|
import lyng.serialization
|
||||||
|
|
||||||
|
class Point(x,y)
|
||||||
|
class Color(r,g,b)
|
||||||
|
|
||||||
|
class ColoredPoint(x, y, r, g, b): Point(x,y), Color(r,g,b)
|
||||||
|
|
||||||
|
val cp = ColoredPoint(1,2,30,40,50)
|
||||||
|
val d = testEncode( cp )
|
||||||
|
assert(d is ColoredPoint)
|
||||||
|
assert(d is Point)
|
||||||
|
assert(d is Color)
|
||||||
|
val p = d as Point
|
||||||
|
val c = d as Color
|
||||||
|
val cp2 = ColoredPoint(p.x, p.y, c.r, c.g, c.b)
|
||||||
|
assertEquals(cp, cp2)
|
||||||
|
""")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user