Compare commits

..

No commits in common. "d4bb186f8595bd4f3810aef5f5c772685f7776d9" and "75723adbbc65eef6ba38dc53dd03b2fbfa1bf4a7" have entirely different histories.

14 changed files with 55 additions and 116 deletions

View File

@ -2,11 +2,6 @@
### Unreleased ### Unreleased
- Language: Refined `protected` visibility rules
- Ancestor classes can now access `protected` members of their descendants, provided the ancestor also defines or inherits a member with the same name (indicating an override of a member known to the ancestor).
- This allows patterns where a base class calls a `protected` method that is implemented in a subclass.
- Fixed a regression where self-calls to unmangled members sometimes bypassed visibility checks incorrectly; these are now handled by the refined logic.
- Language: Added `return` statement - Language: Added `return` statement
- `return [expression]` exits the innermost enclosing callable (function or lambda). - `return [expression]` exits the innermost enclosing callable (function or lambda).
- Supports non-local returns using `@label` syntax (e.g., `return@outer 42`). - Supports non-local returns using `@label` syntax (e.g., `return@outer 42`).
@ -101,7 +96,7 @@
- Header-specified constructor arguments are passed to direct bases. - Header-specified constructor arguments are passed to direct bases.
- Visibility enforcement under MI: - Visibility enforcement under MI:
- `private` visible only inside the declaring class body. - `private` visible only inside the declaring class body.
- `protected` visible inside the declaring class and its transitive subclasses. Additionally, ancestor classes can access protected members of their descendants if it's an override of a member known to the ancestor. Unrelated contexts cannot access it (qualification/casts do not bypass). - `protected` visible inside the declaring class and any of its transitive subclasses; unrelated contexts cannot access it (qualification/casts do not bypass).
- Diagnostics improvements: - Diagnostics improvements:
- Missing member/field messages include receiver class and linearization order; hints for `this@Type` or casts when helpful. - Missing member/field messages include receiver class and linearization order; hints for `this@Type` or casts when helpful.
- Invalid `this@Type` reports that the qualifier is not an ancestor and shows the receiver lineage. - Invalid `this@Type` reports that the qualifier is not an ancestor and shows the receiver lineage.

View File

@ -32,7 +32,7 @@ High-density specification for LLMs. Reference this for all Lyng code generation
val area get = π * r * r val area get = π * r * r
``` ```
- **Mandatory `override`**: Required for all members existing in the ancestor chain. - **Mandatory `override`**: Required for all members existing in the ancestor chain.
- **Visibility**: `public` (default), `protected` (subclasses and ancestors for overrides), `private` (this class instance only). `private set` / `protected set` allowed on properties. - **Visibility**: `public` (default), `protected` (subclasses), `private` (this class instance only). `private set` / `protected set` allowed on properties.
- **Disambiguation**: `this@Base.member()` or `(obj as Base).member()`. `as` returns a qualified view. - **Disambiguation**: `this@Base.member()` or `(obj as Base).member()`. `as` returns a qualified view.
- **Abstract/Interface**: `interface` is a synonym for `abstract class`. Both support state and constructors. - **Abstract/Interface**: `interface` is a synonym for `abstract class`. Both support state and constructors.
- **Extensions**: `fun Class.ext()` or `val Class.ext get = ...`. Scope-isolated. - **Extensions**: `fun Class.ext()` or `val Class.ext get = ...`. Scope-isolated.

View File

@ -425,7 +425,7 @@ Key rules and features:
- Visibility - Visibility
- `private`: accessible only inside the declaring class body; not visible in subclasses and cannot be accessed via `this@Type` or casts. - `private`: accessible only inside the declaring class body; not visible in subclasses and cannot be accessed via `this@Type` or casts.
- `protected`: accessible in the declaring class and in any of its transitive subclasses (including MI). Additionally, ancestor classes can access protected members of their descendants if it's an override of a member known to the ancestor. Protected members are not visible from unrelated contexts; qualification/casts do not bypass it. - `protected`: accessible in the declaring class and in any of its transitive subclasses (including MI), but not from unrelated contexts; qualification/casts do not bypass it.
## Abstract Classes and Members ## Abstract Classes and Members
@ -859,29 +859,21 @@ Private fields are visible only _inside the class instance_:
### Protected members ### Protected members
Protected members are available to the declaring class and all of its transitive subclasses (including via MI). Additionally, an ancestor class can access a `protected` member of its descendant if the ancestor also defines or inherits a member with the same name (i.e., it is an override of something the ancestor knows about). Protected members are available to the declaring class and all of its transitive subclasses (including via MI), but not from unrelated contexts:
Protected members are not available from unrelated contexts: ```
class A() {
```lyng protected fun ping() { "pong" }
class Base {
abstract protected fun foo()
fun bar() {
// Ancestor can see foo() because it's an override
// of a member it defines (even as abstract):
foo()
} }
class B() : A() {
fun call() { this@A.ping() }
} }
class Derived : Base { val b = B()
override protected fun foo() { "ok" } assertEquals("pong", b.call())
}
assertEquals("ok", Derived().bar())
// Unrelated access is forbidden, even via cast // Unrelated access is forbidden, even via cast
assertThrows { (Derived() as Base).foo() } assertThrows { (b as A).ping() }
``` ```
It is possible to provide private constructor parameters so they can be It is possible to provide private constructor parameters so they can be

View File

@ -1701,7 +1701,7 @@ assertEquals(null, (buzz as? Foo)?.runA())
Notes: Notes:
- Resolution order uses C3 MRO (active): deterministic, monotonic order suitable for diamonds and complex hierarchies. Example: for `class D() : B(), C()` where both `B()` and `C()` derive from `A()`, the C3 order is `D → B → C → A`. The first visible match wins. - Resolution order uses C3 MRO (active): deterministic, monotonic order suitable for diamonds and complex hierarchies. Example: for `class D() : B(), C()` where both `B()` and `C()` derive from `A()`, the C3 order is `D → B → C → A`. The first visible match wins.
- `private` is visible only inside the declaring class; `protected` is visible from the declaring class and its subclasses. Additionally, ancestors can access protected members of descendants if they override a member known to the ancestor. Qualification (`this@Type`) or casts do not bypass visibility. - `private` is visible only inside the declaring class; `protected` is visible from the declaring class and any of its transitive subclasses. Qualialsofication (`this@Type`) or casts do not bypass visibility.
- Safe‑call `?.` works with `as?` for optional dispatch. - Safe‑call `?.` works with `as?` for optional dispatch.
## Extension members ## Extension members

View File

@ -24,9 +24,6 @@ class Person(private var _age: Int) {
### Private and Protected Setters ### Private and Protected Setters
You can now restrict the visibility of a property's or field's setter using `private set` or `protected set`. This allows members to be publicly readable but only writable from within the declaring class or its subclasses. You can now restrict the visibility of a property's or field's setter using `private set` or `protected set`. This allows members to be publicly readable but only writable from within the declaring class or its subclasses.
### Refined Protected Visibility
Ancestor classes can now access `protected` members of their descendants if it is an override of a member known to the ancestor. This enables base classes to call protected methods that are implemented or overridden in subclasses.
```lyng ```lyng
class Counter { class Counter {
var count = 0 var count = 0

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 = "1.2.1-SNAPSHOT" version = "1.2.0"
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below // Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below

View File

@ -54,14 +54,14 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
for (cls in effectiveClass.mro) { for (cls in effectiveClass.mro) {
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name) val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
if (rec != null && !rec.isAbstract) { if (rec != null && !rec.isAbstract) {
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx, name)) { if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) {
return rec.copy(receiver = receiver) return rec.copy(receiver = receiver)
} }
} }
} }
// Finally, root object fallback // Finally, root object fallback
Obj.rootObjectType.members[name]?.let { rec -> Obj.rootObjectType.members[name]?.let { rec ->
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) { if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
return rec.copy(receiver = receiver) return rec.copy(receiver = receiver)
} }
} }

View File

@ -112,7 +112,7 @@ open class Scope(
} }
} }
s.objects[name]?.let { rec -> s.objects[name]?.let { rec ->
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
} }
caller?.let { ctx -> caller?.let { ctx ->
s.localBindings[ctx.mangledName(name)]?.let { rec -> s.localBindings[ctx.mangledName(name)]?.let { rec ->
@ -120,11 +120,11 @@ open class Scope(
} }
} }
s.localBindings[name]?.let { rec -> s.localBindings[name]?.let { rec ->
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
} }
s.getSlotIndexOf(name)?.let { idx -> s.getSlotIndexOf(name)?.let { idx ->
val rec = s.getSlotRecord(idx) val rec = s.getSlotRecord(idx)
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
} }
return null return null
} }
@ -163,7 +163,7 @@ open class Scope(
this.extensions[cls]?.get(name)?.let { return it } this.extensions[cls]?.get(name)?.let { return it }
} }
return thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec -> return thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) { if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) null if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) null
else rec else rec
} else null } else null
@ -186,7 +186,7 @@ open class Scope(
s.extensions[cls]?.get(name)?.let { return it } s.extensions[cls]?.get(name)?.let { return it }
} }
s.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec -> s.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
if (canAccessMember(rec.visibility, rec.declaringClass, caller, name)) { if (canAccessMember(rec.visibility, rec.declaringClass, caller)) {
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) { if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) {
// ignore fields, properties and abstracts here, they will be handled by the caller via readField // ignore fields, properties and abstracts here, they will be handled by the caller via readField
} else return rec } else return rec
@ -358,14 +358,14 @@ open class Scope(
for (cls in effectiveClass.mro) { for (cls in effectiveClass.mro) {
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name) val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
if (rec != null && !rec.isAbstract) { if (rec != null && !rec.isAbstract) {
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx, name)) { if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) {
return rec.copy(receiver = receiver) return rec.copy(receiver = receiver)
} }
} }
} }
// Finally, root object fallback // Finally, root object fallback
Obj.rootObjectType.members[name]?.let { rec -> Obj.rootObjectType.members[name]?.let { rec ->
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) { if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
return rec.copy(receiver = receiver) return rec.copy(receiver = receiver)
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,12 +25,7 @@ enum class Visibility {
} }
/** MI-aware visibility check: whether [caller] can access a member declared in [decl] with [visibility]. */ /** MI-aware visibility check: whether [caller] can access a member declared in [decl] with [visibility]. */
fun canAccessMember( fun canAccessMember(visibility: Visibility, decl: net.sergeych.lyng.obj.ObjClass?, caller: net.sergeych.lyng.obj.ObjClass?): Boolean {
visibility: Visibility,
decl: net.sergeych.lyng.obj.ObjClass?,
caller: net.sergeych.lyng.obj.ObjClass?,
name: String? = null
): Boolean {
val res = when (visibility) { val res = when (visibility) {
Visibility.Public -> true Visibility.Public -> true
Visibility.Private -> (decl != null && caller === decl) Visibility.Private -> (decl != null && caller === decl)
@ -38,14 +33,7 @@ fun canAccessMember(
decl == null -> false decl == null -> false
caller == null -> false caller == null -> false
caller === decl -> true caller === decl -> true
caller.allParentsSet.contains(decl) -> true else -> (caller.allParentsSet.contains(decl))
name != null && decl.allParentsSet.contains(caller) -> {
// Ancestor can access protected member of its descendant if it also has this member
// (i.e. it's an override of something the ancestor knows about)
val existing = caller.getInstanceMemberOrNull(name, includeAbstract = true)
existing != null && (existing.visibility == Visibility.Protected || existing.visibility == Visibility.Public)
}
else -> false
} }
} }
return res return res

View File

@ -113,7 +113,7 @@ open class Obj {
if (rec != null && !rec.isAbstract) { if (rec != null && !rec.isAbstract) {
val decl = rec.declaringClass ?: cls val decl = rec.declaringClass ?: cls
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name)) if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
if (rec.type == ObjRecord.Type.Property) { if (rec.type == ObjRecord.Type.Property) {
@ -140,7 +140,7 @@ open class Obj {
cls.members[name]?.let { rec -> cls.members[name]?.let { rec ->
val decl = rec.declaringClass ?: cls val decl = rec.declaringClass ?: cls
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name)) if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
if (rec.type == ObjRecord.Type.Property) { if (rec.type == ObjRecord.Type.Property) {
@ -429,10 +429,10 @@ open class Obj {
} }
/** /**
* Convert a Lyng object to its Kotlin counterpart * Convert Lyng object to its Kotlin counterpart
*/ */
open suspend fun toKotlin(scope: Scope): Any? { open suspend fun toKotlin(scope: Scope): Any? {
return toString(scope).value return toString()
} }
fun willMutate(scope: Scope) { fun willMutate(scope: Scope) {
@ -482,7 +482,7 @@ open class Obj {
cls.members[name]?.let { rec -> cls.members[name]?.let { rec ->
val decl = rec.declaringClass ?: cls val decl = rec.declaringClass ?: cls
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name)) if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
val resolved = resolveRecord(scope, rec, name, decl) val resolved = resolveRecord(scope, rec, name, decl)
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
@ -526,7 +526,7 @@ open class Obj {
} }
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
// Check visibility for non-property members here if they weren't checked before // Check visibility for non-property members here if they weren't checked before
if (!canAccessMember(obj.visibility, decl, caller, name)) if (!canAccessMember(obj.visibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})")) scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
return obj return obj
} }
@ -574,7 +574,7 @@ open class Obj {
val decl = field.declaringClass val decl = field.declaringClass
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
if (!canAccessMember(field.effectiveWriteVisibility, decl, caller, name)) if (!canAccessMember(field.effectiveWriteVisibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't assign field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})")) scope.raiseError(ObjIllegalAccessException(scope, "can't assign field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
if (field.type == ObjRecord.Type.Delegated) { if (field.type == ObjRecord.Type.Delegated) {
val del = field.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate") val del = field.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")

View File

@ -461,7 +461,7 @@ open class ObjClass(
if (existing != null && existing.declaringClass != this) { if (existing != null && existing.declaringClass != this) {
// If the existing member is private in the ancestor, it's not visible for overriding. // If the existing member is private in the ancestor, it's not visible for overriding.
// It should be treated as a new member in this class. // It should be treated as a new member in this class.
if (!existing.visibility.isPublic && !canAccessMember(existing.visibility, existing.declaringClass, this, name)) { if (!existing.visibility.isPublic && !canAccessMember(existing.visibility, existing.declaringClass, this)) {
// It's effectively not there for us, so actualOverride remains false // It's effectively not there for us, so actualOverride remains false
} else { } else {
actualOverride = true actualOverride = true

View File

@ -68,7 +68,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
if (cls.className == "Obj") break if (cls.className == "Obj") break
val mangled = cls.mangledName(name) val mangled = cls.mangledName(name)
instanceScope.objects[mangled]?.let { rec -> instanceScope.objects[mangled]?.let { rec ->
if (canAccessMember(rec.visibility, cls, caller, name)) { if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, cls, caller)) {
return resolveRecord(scope, rec, name, cls) return resolveRecord(scope, rec, name, cls)
} }
} }
@ -77,7 +77,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// 2. Unmangled storage // 2. Unmangled storage
instanceScope.objects[name]?.let { rec -> instanceScope.objects[name]?.let { rec ->
val decl = rec.declaringClass val decl = rec.declaringClass
if (canAccessMember(rec.visibility, decl, caller, name)) { // 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)) {
return resolveRecord(scope, rec, name, decl) return resolveRecord(scope, rec, name, decl)
} }
} }
@ -173,7 +174,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
if (cls.className == "Obj") break if (cls.className == "Obj") break
val mangled = cls.mangledName(name) val mangled = cls.mangledName(name)
instanceScope.objects[mangled]?.let { rec -> instanceScope.objects[mangled]?.let { rec ->
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) { if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, cls, caller)) {
updateRecord(scope, rec, name, newValue, cls) updateRecord(scope, rec, name, newValue, cls)
return return
} }
@ -183,7 +184,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// 2. Unmangled storage // 2. Unmangled storage
instanceScope.objects[name]?.let { rec -> instanceScope.objects[name]?.let { rec ->
val decl = rec.declaringClass val decl = rec.declaringClass
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) { if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, decl, caller)) {
updateRecord(scope, rec, name, newValue, decl) updateRecord(scope, rec, name, newValue, decl)
return return
} }
@ -281,7 +282,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
} }
val decl = rec.declaringClass ?: cls val decl = rec.declaringClass ?: cls
val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name)) if (!canAccessMember(rec.visibility, decl, effectiveCaller))
scope.raiseError( scope.raiseError(
ObjIllegalAccessException( ObjIllegalAccessException(
scope, scope,
@ -432,7 +433,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 (!canAccessMember(rec.visibility, decl, caller, name)) if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})")) scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
return instance.resolveRecord(scope, rec, name, decl) return instance.resolveRecord(scope, rec, name, decl)
} }
@ -441,7 +442,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 (!canAccessMember(rec.visibility, decl, caller, name)) if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError( scope.raiseError(
ObjIllegalAccessException( ObjIllegalAccessException(
scope, scope,
@ -455,7 +456,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 (!canAccessMember(r.visibility, decl, caller, name)) if (!canAccessMember(r.visibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})")) scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
return instance.resolveRecord(scope, r, name, decl) return instance.resolveRecord(scope, r, name, decl)
@ -467,7 +468,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 (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name)) if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
ObjIllegalAccessException( ObjIllegalAccessException(
scope, scope,
"can't assign to field $name (declared in ${decl.className})" "can't assign to field $name (declared in ${decl.className})"
@ -481,7 +482,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 (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name)) if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
ObjIllegalAccessException( ObjIllegalAccessException(
scope, scope,
"can't assign to field $name (declared in ${decl?.className ?: "?"})" "can't assign to field $name (declared in ${decl?.className ?: "?"})"
@ -494,7 +495,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 (!canAccessMember(r.effectiveWriteVisibility, decl, caller, name)) if (!canAccessMember(r.effectiveWriteVisibility, decl, caller))
ObjIllegalAccessException(scope, "can't assign to field $name (declared in ${decl.className})").raise() ObjIllegalAccessException(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
@ -510,7 +511,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 (!canAccessMember(rec.visibility, decl, caller, name)) if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})")) scope.raiseError(ObjIllegalAccessException(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
@ -525,7 +526,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 (!canAccessMember(rec.visibility, decl, caller, name)) if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError( scope.raiseError(
ObjIllegalAccessException( ObjIllegalAccessException(
scope, scope,

View File

@ -1363,7 +1363,7 @@ class MethodCallRef(
val decl = hierarchyMember.declaringClass ?: base.objClass val decl = hierarchyMember.declaringClass ?: base.objClass
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
val inst = obj as ObjInstance val inst = obj as ObjInstance
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx))
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name")) sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
callable.invoke(inst.instanceScope, inst, a) callable.invoke(inst.instanceScope, inst, a)
} }
@ -1447,7 +1447,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
val slot = if (hit) cachedSlot else resolveSlot(scope) val slot = if (hit) cachedSlot else resolveSlot(scope)
if (slot >= 0) { if (slot >= 0) {
val rec = scope.getSlotRecord(slot) val rec = scope.getSlotRecord(slot)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
// Not visible via slot, fallback to other lookups // Not visible via slot, fallback to other lookups
} else { } else {
if (PerfFlags.PIC_DEBUG_COUNTERS) { if (PerfFlags.PIC_DEBUG_COUNTERS) {
@ -1499,7 +1499,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
val slot = if (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount()) cachedSlot else resolveSlot(scope) val slot = if (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount()) cachedSlot else resolveSlot(scope)
if (slot >= 0) { if (slot >= 0) {
val rec = scope.getSlotRecord(slot) val rec = scope.getSlotRecord(slot)
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
scope.assign(rec, name, newValue) scope.assign(rec, name, newValue)
return return
} }
@ -1608,7 +1608,7 @@ class FastLocalVarRef(
val actualOwner = cachedOwnerScope val actualOwner = cachedOwnerScope
if (slot >= 0 && actualOwner != null) { if (slot >= 0 && actualOwner != null) {
val rec = actualOwner.getSlotRecord(slot) val rec = actualOwner.getSlotRecord(slot)
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
if (PerfFlags.PIC_DEBUG_COUNTERS) { if (PerfFlags.PIC_DEBUG_COUNTERS) {
if (ownerValid) PerfStats.fastLocalHit++ else PerfStats.fastLocalMiss++ if (ownerValid) PerfStats.fastLocalHit++ else PerfStats.fastLocalMiss++
} }
@ -1652,7 +1652,7 @@ class FastLocalVarRef(
val actualOwner = cachedOwnerScope val actualOwner = cachedOwnerScope
if (slot >= 0 && actualOwner != null) { if (slot >= 0 && actualOwner != null) {
val rec = actualOwner.getSlotRecord(slot) val rec = actualOwner.getSlotRecord(slot)
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
return scope.resolve(rec, name) return scope.resolve(rec, name)
} }
} }
@ -1698,7 +1698,7 @@ class FastLocalVarRef(
val actualOwner = cachedOwnerScope val actualOwner = cachedOwnerScope
if (slot >= 0 && actualOwner != null) { if (slot >= 0 && actualOwner != null) {
val rec = actualOwner.getSlotRecord(slot) val rec = actualOwner.getSlotRecord(slot)
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
scope.assign(rec, name, newValue) scope.assign(rec, name, newValue)
return return
} }

View File

@ -776,38 +776,4 @@ class OOTest {
assertEquals("success", result.toString(), "Parameter 'id' should shadow method 'id' in block") assertEquals("success", result.toString(), "Parameter 'id' should shadow method 'id' in block")
} }
@Test
fun testOverrideVisibilityRules1() = runTest {
eval("""
interface Base {
abstract protected fun foo()
fun bar() {
// it must see foo() as it is protected and
// is declared here (even as abstract):
foo()
}
}
class Derived : Base {
protected val suffix = "!"
private fun fooPrivateImpl() = "bar"
override protected fun foo() {
// it should access own private and all protected memberes here:
fooPrivateImpl() + suffix
}
}
class Derived2: Base {
private var value = 42
override protected fun foo() {
if( value < 10 ) 10 else value
}
}
assertEquals("bar!", Derived().bar())
assertEquals(42, Derived2().bar())
""".trimIndent())
}
} }