Compare commits
2 Commits
75723adbbc
...
d4bb186f85
| Author | SHA1 | Date | |
|---|---|---|---|
| d4bb186f85 | |||
| 7bc17037f9 |
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
### 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`).
|
||||||
@ -96,7 +101,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 any of its transitive subclasses; unrelated contexts cannot access it (qualification/casts do not bypass).
|
- `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).
|
||||||
- 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.
|
||||||
|
|||||||
@ -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), `private` (this class instance only). `private set` / `protected set` allowed on properties.
|
- **Visibility**: `public` (default), `protected` (subclasses and ancestors for overrides), `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.
|
||||||
|
|||||||
28
docs/OOP.md
28
docs/OOP.md
@ -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), but not from unrelated contexts; qualification/casts do not bypass it.
|
- `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.
|
||||||
|
|
||||||
## Abstract Classes and Members
|
## Abstract Classes and Members
|
||||||
|
|
||||||
@ -859,21 +859,29 @@ 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), but not from unrelated contexts:
|
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 not available from unrelated contexts:
|
||||||
class A() {
|
|
||||||
protected fun ping() { "pong" }
|
```lyng
|
||||||
|
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() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val b = B()
|
class Derived : Base {
|
||||||
assertEquals("pong", b.call())
|
override protected fun foo() { "ok" }
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("ok", Derived().bar())
|
||||||
|
|
||||||
// Unrelated access is forbidden, even via cast
|
// Unrelated access is forbidden, even via cast
|
||||||
assertThrows { (b as A).ping() }
|
assertThrows { (Derived() as Base).foo() }
|
||||||
```
|
```
|
||||||
|
|
||||||
It is possible to provide private constructor parameters so they can be
|
It is possible to provide private constructor parameters so they can be
|
||||||
|
|||||||
@ -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 any of its transitive subclasses. Qualialsofication (`this@Type`) or casts do not bypass visibility.
|
- `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.
|
||||||
- Safe‑call `?.` works with `as?` for optional dispatch.
|
- Safe‑call `?.` works with `as?` for optional dispatch.
|
||||||
|
|
||||||
## Extension members
|
## Extension members
|
||||||
|
|||||||
@ -24,6 +24,9 @@ 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
|
||||||
|
|||||||
@ -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.0"
|
version = "1.2.1-SNAPSHOT"
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
|
|||||||
@ -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)) {
|
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx, name)) {
|
||||||
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)) {
|
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
||||||
return rec.copy(receiver = receiver)
|
return rec.copy(receiver = receiver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)) return rec
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) 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)) return rec
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) 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)) return rec
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) 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)) {
|
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
||||||
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)) {
|
if (canAccessMember(rec.visibility, rec.declaringClass, caller, name)) {
|
||||||
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)) {
|
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx, name)) {
|
||||||
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)) {
|
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
||||||
return rec.copy(receiver = receiver)
|
return rec.copy(receiver = receiver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 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,7 +25,12 @@ 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(visibility: Visibility, decl: net.sergeych.lyng.obj.ObjClass?, caller: net.sergeych.lyng.obj.ObjClass?): Boolean {
|
fun canAccessMember(
|
||||||
|
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)
|
||||||
@ -33,7 +38,14 @@ fun canAccessMember(visibility: Visibility, decl: net.sergeych.lyng.obj.ObjClass
|
|||||||
decl == null -> false
|
decl == null -> false
|
||||||
caller == null -> false
|
caller == null -> false
|
||||||
caller === decl -> true
|
caller === decl -> true
|
||||||
else -> (caller.allParentsSet.contains(decl))
|
caller.allParentsSet.contains(decl) -> true
|
||||||
|
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
|
||||||
|
|||||||
@ -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))
|
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||||
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))
|
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||||
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 Lyng object to its Kotlin counterpart
|
* Convert a Lyng object to its Kotlin counterpart
|
||||||
*/
|
*/
|
||||||
open suspend fun toKotlin(scope: Scope): Any? {
|
open suspend fun toKotlin(scope: Scope): Any? {
|
||||||
return toString()
|
return toString(scope).value
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||||
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))
|
if (!canAccessMember(obj.visibility, decl, caller, name))
|
||||||
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))
|
if (!canAccessMember(field.effectiveWriteVisibility, decl, caller, name))
|
||||||
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")
|
||||||
|
|||||||
@ -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)) {
|
if (!existing.visibility.isPublic && !canAccessMember(existing.visibility, existing.declaringClass, this, name)) {
|
||||||
// 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
|
||||||
|
|||||||
@ -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 ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, cls, caller)) {
|
if (canAccessMember(rec.visibility, cls, caller, name)) {
|
||||||
return resolveRecord(scope, rec, name, cls)
|
return resolveRecord(scope, rec, name, cls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,8 +77,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
|
||||||
// Unmangled access is only allowed if it's public OR we are inside the same instance's method
|
if (canAccessMember(rec.visibility, decl, caller, name)) {
|
||||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, decl, caller)) {
|
|
||||||
return resolveRecord(scope, rec, name, decl)
|
return resolveRecord(scope, rec, name, decl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,7 +173,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 ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, cls, caller)) {
|
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) {
|
||||||
updateRecord(scope, rec, name, newValue, cls)
|
updateRecord(scope, rec, name, newValue, cls)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -184,7 +183,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 ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, decl, caller)) {
|
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
|
||||||
updateRecord(scope, rec, name, newValue, decl)
|
updateRecord(scope, rec, name, newValue, decl)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -282,7 +281,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))
|
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
|
||||||
scope.raiseError(
|
scope.raiseError(
|
||||||
ObjIllegalAccessException(
|
ObjIllegalAccessException(
|
||||||
scope,
|
scope,
|
||||||
@ -433,7 +432,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))
|
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -442,7 +441,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))
|
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||||
scope.raiseError(
|
scope.raiseError(
|
||||||
ObjIllegalAccessException(
|
ObjIllegalAccessException(
|
||||||
scope,
|
scope,
|
||||||
@ -456,7 +455,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))
|
if (!canAccessMember(r.visibility, decl, caller, name))
|
||||||
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)
|
||||||
@ -468,7 +467,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))
|
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
|
||||||
ObjIllegalAccessException(
|
ObjIllegalAccessException(
|
||||||
scope,
|
scope,
|
||||||
"can't assign to field $name (declared in ${decl.className})"
|
"can't assign to field $name (declared in ${decl.className})"
|
||||||
@ -482,7 +481,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))
|
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
|
||||||
ObjIllegalAccessException(
|
ObjIllegalAccessException(
|
||||||
scope,
|
scope,
|
||||||
"can't assign to field $name (declared in ${decl?.className ?: "?"})"
|
"can't assign to field $name (declared in ${decl?.className ?: "?"})"
|
||||||
@ -495,7 +494,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))
|
if (!canAccessMember(r.effectiveWriteVisibility, decl, caller, name))
|
||||||
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
|
||||||
@ -511,7 +510,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))
|
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||||
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
|
||||||
@ -526,7 +525,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))
|
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||||
scope.raiseError(
|
scope.raiseError(
|
||||||
ObjIllegalAccessException(
|
ObjIllegalAccessException(
|
||||||
scope,
|
scope,
|
||||||
|
|||||||
@ -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))
|
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
|
||||||
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)) {
|
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||||
// 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)) {
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||||
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)) {
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||||
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)) {
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||||
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)) {
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||||
scope.assign(rec, name, newValue)
|
scope.assign(rec, name, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -776,4 +776,38 @@ 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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user