fixed mist edge cases with new language logic

This commit is contained in:
Sergey Chernov 2026-01-10 14:49:54 +01:00
parent b9831a422a
commit 91e6ae29ce
5 changed files with 71 additions and 34 deletions

View File

@ -46,7 +46,7 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
// 2. Lexical environment (captured locals from entire ancestry)
closureScope.chainLookupIgnoreClosure(name, followClosure = true)?.let { return it }
closureScope.chainLookupIgnoreClosure(name, followClosure = true, caller = currentClassCtx)?.let { return it }
// 3. Lexical this members (captured receiver)
val receiver = thisObj

View File

@ -1482,7 +1482,14 @@ class Compiler(
miniSink?.onInitDecl(MiniInitDecl(MiniRange(id.pos, range.end), id.pos))
}
val initStmt = statement(id.pos) { scp ->
val cls = scp.thisObj.objClass
val saved = scp.currentClassCtx
scp.currentClassCtx = cls
try {
block.execute(scp)
} finally {
scp.currentClassCtx = saved
}
ObjVoid
}
statement {
@ -3313,9 +3320,11 @@ class Compiler(
val setArg = cc.requireToken(Token.Type.ID, "Expected setter argument name")
cc.requireToken(Token.Type.RPAREN)
miniSink?.onEnterFunction(null)
setter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
val finalSetter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
cc.skipWsTokens()
val body = parseBlock()
val body = inCodeContext(CodeContext.Function("<setter>")) {
parseBlock()
}
statement(body.pos) { scope ->
val value = scope.args.list.firstOrNull() ?: ObjNull
scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument)
@ -3324,11 +3333,12 @@ class Compiler(
} else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) {
cc.skipWsTokens()
cc.next() // consume '='
val expr = parseExpression() ?: throw ScriptError(
val st = inCodeContext(CodeContext.Function("<setter>")) {
parseExpression() ?: throw ScriptError(
cc.current().pos,
"Expected setter expression"
)
val st = expr
}
statement(st.pos) { scope ->
val value = scope.args.list.firstOrNull() ?: ObjNull
scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument)
@ -3337,6 +3347,7 @@ class Compiler(
} else {
throw ScriptError(cc.current().pos, "Expected { or = after set(...)")
}
setter = finalSetter
miniSink?.onExitFunction(cc.currentPos())
} else {
// private set without body: setter remains null, visibility is restricted
@ -3383,6 +3394,7 @@ class Compiler(
// In true class bodies (not inside a function), store fields under a class-qualified key to support MI collisions
// Do NOT infer declaring class from runtime thisObj here; only the compile-time captured
// ClassBody qualifies for class-field storage. Otherwise, this is a plain local.
isProperty = getter != null || setter != null
val declaringClassName = declaringClassNameCaptured
if (declaringClassName == null) {
if (context.containsLocal(name))
@ -3488,7 +3500,8 @@ class Compiler(
isAbstract = isAbstract,
isClosed = isClosed,
isOverride = isOverride,
pos = start
pos = start,
prop = prop
)
} else {
cls.createField(

View File

@ -107,9 +107,19 @@ open class Scope(
* and bindings of each frame. Instance/class member fallback must be decided by the caller.
*/
internal fun tryGetLocalRecord(s: Scope, name: String, caller: net.sergeych.lyng.obj.ObjClass?): ObjRecord? {
caller?.let { ctx ->
s.objects["${ctx.className}::$name"]?.let { rec ->
if (rec.visibility == Visibility.Private) return rec
}
}
s.objects[name]?.let { rec ->
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
}
caller?.let { ctx ->
s.localBindings["${ctx.className}::$name"]?.let { rec ->
if (rec.visibility == Visibility.Private) return rec
}
}
s.localBindings[name]?.let { rec ->
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
}
@ -120,13 +130,14 @@ open class Scope(
return null
}
internal fun chainLookupIgnoreClosure(name: String, followClosure: Boolean = false): ObjRecord? {
internal fun chainLookupIgnoreClosure(name: String, followClosure: Boolean = false, caller: net.sergeych.lyng.obj.ObjClass? = null): ObjRecord? {
var s: Scope? = this
// use frameId to detect unexpected structural cycles in the parent chain
val visited = HashSet<Long>(4)
val effectiveCaller = caller ?: currentClassCtx
while (s != null) {
if (!visited.add(s.frameId)) return null
tryGetLocalRecord(s, name, currentClassCtx)?.let { return it }
tryGetLocalRecord(s, name, effectiveCaller)?.let { return it }
s = if (followClosure && s is ClosureScope) s.closureScope else s.parent
}
return null
@ -334,12 +345,7 @@ open class Scope(
if (name == "this") return thisObj.asReadonly
// 1. Prefer direct locals/bindings declared in this frame
objects[name]?.let { rec ->
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
}
localBindings[name]?.let { rec ->
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
}
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
// 2. Then, check members of thisObj
val receiver = thisObj

View File

@ -253,28 +253,29 @@ open class ObjClass(
// remains stable even when call frames are pooled and reused.
val stableParent = classScope ?: scope.parent
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
// println("[DEBUG_LOG] createInstance: created $instance scope@${instance.instanceScope.hashCode()}")
instance.instanceScope.currentClassCtx = null
// Expose instance methods (and other callable members) directly in the instance scope for fast lookup
// This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust
for (cls in mro) {
// 1) members-defined methods
// 1) members-defined methods and fields
for ((k, v) in cls.members) {
if (v.value is Statement || v.type == ObjRecord.Type.Delegated) {
val key = if (v.visibility == Visibility.Private) "${cls.className}::$k" else k
if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field)) {
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.Delegated) "${cls.className}::$k" else k
if (!instance.instanceScope.objects.containsKey(key)) {
instance.instanceScope.objects[key] = if (v.type == ObjRecord.Type.Delegated) v.copy() else v
instance.instanceScope.objects[key] = if (v.type == ObjRecord.Type.Fun) v else v.copy()
}
}
}
// 2) class-scope methods registered during class-body execution
// 2) class-scope members registered during class-body execution
cls.classScope?.objects?.forEach { (k, rec) ->
if (rec.value is Statement || rec.type == ObjRecord.Type.Delegated) {
val key = if (rec.visibility == Visibility.Private) "${cls.className}::$k" else k
// ONLY copy methods and delegated members from class scope to instance scope.
// Fields in class scope are static fields and must NOT be per-instance.
if (!rec.isAbstract && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) "${cls.className}::$k" else k
// if not already present, copy reference for dispatch
if (!instance.instanceScope.objects.containsKey(key)) {
instance.instanceScope.objects[key] = if (rec.type == ObjRecord.Type.Delegated) rec.copy() else rec
instance.instanceScope.objects[key] = if (rec.type == ObjRecord.Type.Fun) rec else rec.copy()
}
}
}
@ -512,12 +513,13 @@ open class ObjClass(
isClosed: Boolean = false,
isOverride: Boolean = false,
pos: Pos = Pos.builtIn,
prop: ObjProperty? = null
) {
val g = getter?.let { statement { it() } }
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
val prop = if (isAbstract) ObjNull else ObjProperty(name, g, s)
val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s)
createField(
name, prop, false, visibility, writeVisibility, pos, declaringClass,
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
type = ObjRecord.Type.Property
)

View File

@ -36,6 +36,13 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// 0. Private mangled of current class context
caller?.let { c ->
// Check for private methods/properties
c.members[name]?.let { rec ->
if (rec.visibility == Visibility.Private) {
return resolveRecord(scope, rec, name, c)
}
}
// Check for private fields (stored in instanceScope)
val mangled = "${c.className}::$name"
instanceScope.objects[mangled]?.let { rec ->
if (rec.visibility == Visibility.Private) {
@ -59,9 +66,10 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
instanceScope.objects[name]?.let { rec ->
val decl = rec.declaringClass
// Unmangled access is only allowed if it's public OR we are inside the same instance's method
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, decl, caller))
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, decl, caller)) {
return resolveRecord(scope, rec, name, decl)
}
}
// 3. Fall back to super (handles class members and extensions)
return super.readField(scope, name)
@ -113,6 +121,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// 0. Private mangled of current class context
caller?.let { c ->
// Check for private methods/properties
c.members[name]?.let { rec ->
if (rec.visibility == Visibility.Private) {
updateRecord(scope, resolveRecord(scope, rec, name, c), name, newValue, c)
return
}
}
// Check for private fields (stored in instanceScope)
val mangled = "${c.className}::$name"
instanceScope.objects[mangled]?.let { rec ->
if (rec.visibility == Visibility.Private) {
@ -343,7 +359,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
if (other.objClass != objClass) return -1
for (f in comparableVars) {
val a = f.value.value
val b = other.instanceScope[f.key]!!.value
val b = other.instanceScope.objects[f.key]?.value ?: scope.raiseError("Internal error: field ${f.key} not found in other instance")
val d = a.compareTo(scope, b)
if (d != 0) return d
}
@ -370,7 +386,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
return resolveRecord(scope, rec, name, decl)
return instance.resolveRecord(scope, rec, name, decl)
}
// Then try instance locals (unmangled) only if startClass is the dynamic class itself
if (startClass === instance.objClass) {
@ -384,7 +400,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
"can't access field $name (declared in ${decl?.className ?: "?"})"
)
)
return resolveRecord(scope, rec, name, decl)
return instance.resolveRecord(scope, rec, name, decl)
}
}
// Finally try methods/properties starting from ancestor
@ -394,7 +410,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
if (!canAccessMember(r.visibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
return resolveRecord(scope, r, name, decl)
return instance.resolveRecord(scope, r, name, decl)
}
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {