fixed errors

This commit is contained in:
Sergey Chernov 2025-11-17 14:08:32 +01:00
parent aeeec2d417
commit 6eabcc315f
6 changed files with 72 additions and 26 deletions

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "0.10.3-SNAPSHOT" version = "0.11.1-SNAPSHOT"
buildscript { buildscript {
repositories { repositories {

View File

@ -30,10 +30,11 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) { Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
init { init {
// Preserve the lexical class context from where the lambda was defined (closure), // Preserve the lexical class context of the closure by default. This ensures that lambdas
// so that visibility checks (private/protected) inside lambdas executed within other // created inside a class method keep access to that class's private/protected members even
// methods (e.g., Mutex.withLock) still see the original declaring class context. // when executed from within another object's method (e.g., Mutex.withLock), which may set
this.currentClassCtx = closureScope.currentClassCtx // its own currentClassCtx temporarily. If the closure has no class context, inherit caller's.
this.currentClassCtx = closureScope.currentClassCtx ?: callScope.currentClassCtx
} }
override fun get(name: String): ObjRecord? { override fun get(name: String): ObjRecord? {

View File

@ -224,8 +224,19 @@ class Compiler(
while (true) { while (true) {
val opToken = cc.next() val opToken = cc.next()
val op = byLevel[level][opToken.type] val op = byLevel[level][opToken.type]
if (op == null) { if (op == null) {
// handle ternary conditional at the top precedence level only: a ? b : c
if (opToken.type == Token.Type.QUESTION && level == 0) {
val thenRef = parseExpressionLevel(level + 1)
?: throw ScriptError(opToken.pos, "Expecting expression after '?'")
val colon = cc.next()
if (colon.type != Token.Type.COLON) colon.raiseSyntax("missing ':'")
val elseRef = parseExpressionLevel(level + 1)
?: throw ScriptError(colon.pos, "Expecting expression after ':'")
lvalue = ConditionalRef(lvalue!!, thenRef, elseRef)
continue
}
cc.previous() cc.previous()
break break
} }

View File

@ -62,7 +62,10 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
val size by byteArray::size val size by byteArray::size
override fun hashCode(): Int { override fun hashCode(): Int {
return byteArray.hashCode() // On some platforms (notably JS), UByteArray.hashCode() is not content-based.
// For map/set keys we must ensure hash is consistent with equals(contentEquals).
// Convert to ByteArray and use contentHashCode() which is value-based and stable.
return byteArray.asByteArray().contentHashCode()
} }
override suspend fun compareTo(scope: Scope, other: Obj): Int { override suspend fun compareTo(scope: Scope, other: Obj): Int {

View File

@ -32,9 +32,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// Direct (unmangled) lookup first // Direct (unmangled) lookup first
instanceScope[name]?.let { instanceScope[name]?.let {
val decl = it.declaringClass ?: objClass.findDeclaringClassOf(name) val decl = it.declaringClass ?: objClass.findDeclaringClassOf(name)
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx // When execution passes through suspension points (e.g., withLock),
val allowed = if (it.visibility == net.sergeych.lyng.Visibility.Private) (decl === objClass) else canAccessMember(it.visibility, decl, caller) // currentClassCtx could be lost. Fall back to the instance scope class ctx
if (!allowed) // to preserve correct visibility semantics for private/protected members
// accessed from within the class methods.
// Allow unconditional access when accessing through `this` of the same instance
if (scope.thisObj === this) return it
val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx
val caller = caller0 // do not default to objClass for outsiders
if (!canAccessMember(it.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 it return it
} }
@ -51,15 +57,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return null return null
} }
findMangled()?.let { rec -> findMangled()?.let { rec ->
val declName = rec.importedFrom?.packageName // unused; use mangled key instead
// derive declaring class by mangled prefix: try self then parents // derive declaring class by mangled prefix: try self then parents
val declaring = when { val declaring = when {
instanceScope.objects.containsKey("${cls.className}::$name") -> cls instanceScope.objects.containsKey("${cls.className}::$name") -> cls
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") } else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
} }
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx if (scope.thisObj === this) return rec
val allowed = if (rec.visibility == net.sergeych.lyng.Visibility.Private) (declaring === objClass) else canAccessMember(rec.visibility, declaring, caller) val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx
if (!allowed) val caller = caller0 // do not default to objClass for outsiders
if (!canAccessMember(rec.visibility, declaring, caller))
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${declaring?.className ?: "?"})")) scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${declaring?.className ?: "?"})"))
return rec return rec
} }
@ -71,10 +77,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// Direct (unmangled) first // Direct (unmangled) first
instanceScope[name]?.let { f -> instanceScope[name]?.let { f ->
val decl = f.declaringClass ?: objClass.findDeclaringClassOf(name) val decl = f.declaringClass ?: objClass.findDeclaringClassOf(name)
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx if (scope.thisObj === this) {
val allowed = if (f.visibility == net.sergeych.lyng.Visibility.Private) (decl === objClass) else canAccessMember(f.visibility, decl, caller) // direct self-assignment allowed; enforce mutability below
if (!allowed) } else {
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl?.className ?: "?"})").raise() val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx
val caller = caller0 // do not default to objClass for outsiders
if (!canAccessMember(f.visibility, decl, caller))
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) if (f.value.assign(scope, newValue) == null)
f.value = newValue f.value = newValue
@ -95,10 +105,12 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
instanceScope.objects.containsKey("${cls.className}::$name") -> cls instanceScope.objects.containsKey("${cls.className}::$name") -> cls
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") } else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
} }
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx if (scope.thisObj !== this) {
val allowed = if (rec.visibility == net.sergeych.lyng.Visibility.Private) (declaring === objClass) else canAccessMember(rec.visibility, declaring, caller) val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx
if (!allowed) val caller = caller0 // do not default to objClass for outsiders
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${declaring?.className ?: "?"})").raise() if (!canAccessMember(rec.visibility, declaring, caller))
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${declaring?.className ?: "?"})").raise()
}
if (!rec.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise() if (!rec.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
if (rec.value.assign(scope, newValue) == null) if (rec.value.assign(scope, newValue) == null)
rec.value = newValue rec.value = newValue
@ -111,9 +123,9 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
onNotFoundResult: (()->Obj?)?): Obj = onNotFoundResult: (()->Obj?)?): Obj =
instanceScope[name]?.let { rec -> instanceScope[name]?.let { rec ->
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx
val allowed = if (rec.visibility == net.sergeych.lyng.Visibility.Private) (decl === objClass) else canAccessMember(rec.visibility, decl, caller) val caller = caller0 ?: if (scope.thisObj === this) objClass else null
if (!allowed) 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 ?: "?"})"))
// execute with lexical class context propagated to declaring class // execute with lexical class context propagated to declaring class
val saved = instanceScope.currentClassCtx val saved = instanceScope.currentClassCtx
@ -131,7 +143,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// fallback: class-scope function (registered during class body execution) // fallback: class-scope function (registered during class body execution)
objClass.classScope?.objects?.get(name)?.let { rec -> objClass.classScope?.objects?.get(name)?.let { rec ->
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
val caller = scope.currentClassCtx val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx
val caller = caller0 ?: if (scope.thisObj === this) objClass else null
if (!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 = instanceScope.currentClassCtx val saved = instanceScope.currentClassCtx

View File

@ -253,6 +253,24 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
} }
} }
/** Conditional (ternary) operator reference: cond ? a : b */
class ConditionalRef(
private val condition: ObjRef,
private val ifTrue: ObjRef,
private val ifFalse: ObjRef
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
val condVal = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) condition.evalValue(scope) else condition.get(scope).value
val condTrue = when (condVal) {
is ObjBool -> condVal.value
is ObjInt -> condVal.value != 0L
else -> condVal.toBool()
}
val branch = if (condTrue) ifTrue else ifFalse
return branch.get(scope)
}
}
/** Cast operator reference: left `as` rightType or `as?` (nullable). */ /** Cast operator reference: left `as` rightType or `as?` (nullable). */
class CastRef( class CastRef(
private val valueRef: ObjRef, private val valueRef: ObjRef,