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
- 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
- `return [expression]` exits the innermost enclosing callable (function or lambda).
- 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.
- Visibility enforcement under MI:
- `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:
- 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.

View File

@ -32,7 +32,7 @@ High-density specification for LLMs. Reference this for all Lyng code generation
val area get = π * r * r
```
- **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.
- **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.

View File

@ -425,7 +425,7 @@ Key rules and features:
- Visibility
- `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
@ -859,29 +859,21 @@ Private fields are visible only _inside the class instance_:
### 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:
```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 A() {
protected fun ping() { "pong" }
}
class B() : A() {
fun call() { this@A.ping() }
}
class Derived : Base {
override protected fun foo() { "ok" }
}
assertEquals("ok", Derived().bar())
val b = B()
assertEquals("pong", b.call())
// 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

View File

@ -1701,7 +1701,7 @@ assertEquals(null, (buzz as? Foo)?.runA())
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.
- `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.
## Extension members

View File

@ -24,9 +24,6 @@ class Person(private var _age: Int) {
### 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.
### 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
class Counter {
var count = 0

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
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

View File

@ -54,14 +54,14 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
for (cls in effectiveClass.mro) {
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
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)
}
}
}
// Finally, root object fallback
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)
}
}

View File

@ -112,7 +112,7 @@ open class Scope(
}
}
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 ->
s.localBindings[ctx.mangledName(name)]?.let { rec ->
@ -120,11 +120,11 @@ open class Scope(
}
}
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 ->
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
}
@ -163,7 +163,7 @@ open class Scope(
this.extensions[cls]?.get(name)?.let { return it }
}
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
else rec
} else null
@ -186,7 +186,7 @@ open class Scope(
s.extensions[cls]?.get(name)?.let { return it }
}
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) {
// ignore fields, properties and abstracts here, they will be handled by the caller via readField
} else return rec
@ -358,14 +358,14 @@ open class Scope(
for (cls in effectiveClass.mro) {
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
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)
}
}
}
// Finally, root object fallback
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)
}
}

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");
* 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]. */
fun canAccessMember(
visibility: Visibility,
decl: net.sergeych.lyng.obj.ObjClass?,
caller: net.sergeych.lyng.obj.ObjClass?,
name: String? = null
): Boolean {
fun canAccessMember(visibility: Visibility, decl: net.sergeych.lyng.obj.ObjClass?, caller: net.sergeych.lyng.obj.ObjClass?): Boolean {
val res = when (visibility) {
Visibility.Public -> true
Visibility.Private -> (decl != null && caller === decl)
@ -38,14 +33,7 @@ fun canAccessMember(
decl == null -> false
caller == null -> false
caller === decl -> true
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
else -> (caller.allParentsSet.contains(decl))
}
}
return res

View File

@ -113,7 +113,7 @@ open class Obj {
if (rec != null && !rec.isAbstract) {
val decl = rec.declaringClass ?: cls
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 ?: "?"})"))
if (rec.type == ObjRecord.Type.Property) {
@ -140,7 +140,7 @@ open class Obj {
cls.members[name]?.let { rec ->
val decl = rec.declaringClass ?: cls
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 ?: "?"})"))
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? {
return toString(scope).value
return toString()
}
fun willMutate(scope: Scope) {
@ -482,7 +482,7 @@ open class Obj {
cls.members[name]?.let { rec ->
val decl = rec.declaringClass ?: cls
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 ?: "?"})"))
val resolved = resolveRecord(scope, rec, name, decl)
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
@ -526,7 +526,7 @@ open class Obj {
}
val caller = scope.currentClassCtx
// 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 ?: "?"})"))
return obj
}
@ -574,7 +574,7 @@ open class Obj {
val decl = field.declaringClass
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 ?: "?"})"))
if (field.type == ObjRecord.Type.Delegated) {
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 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.
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
} else {
actualOverride = true

View File

@ -68,7 +68,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
if (cls.className == "Obj") break
val mangled = cls.mangledName(name)
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)
}
}
@ -77,7 +77,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// 2. Unmangled storage
instanceScope.objects[name]?.let { rec ->
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)
}
}
@ -173,7 +174,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
if (cls.className == "Obj") break
val mangled = cls.mangledName(name)
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)
return
}
@ -183,7 +184,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// 2. Unmangled storage
instanceScope.objects[name]?.let { rec ->
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)
return
}
@ -281,7 +282,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
}
val decl = rec.declaringClass ?: cls
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(
ObjIllegalAccessException(
scope,
@ -432,7 +433,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
// Visibility: declaring class is the qualified ancestor for mangled storage
val decl = rec.declaringClass ?: startClass
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})"))
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 ->
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name))
if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError(
ObjIllegalAccessException(
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 decl = r.declaringClass ?: startClass
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})"))
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 ->
val decl = f.declaringClass ?: startClass
val caller = scope.currentClassCtx
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
ObjIllegalAccessException(
scope,
"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 ->
val decl = f.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
val caller = scope.currentClassCtx
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
ObjIllegalAccessException(
scope,
"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 decl = r.declaringClass ?: startClass
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()
if (!r.isMutable) scope.raiseError("can't assign to read-only field: $name")
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 ->
val decl = rec.declaringClass ?: startClass
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})"))
val saved = instance.instanceScope.currentClassCtx
instance.instanceScope.currentClassCtx = decl
@ -525,7 +526,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
instance.instanceScope[name]?.let { rec ->
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name))
if (!canAccessMember(rec.visibility, decl, caller))
scope.raiseError(
ObjIllegalAccessException(
scope,

View File

@ -1363,7 +1363,7 @@ class MethodCallRef(
val decl = hierarchyMember.declaringClass ?: base.objClass
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
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"))
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)
if (slot >= 0) {
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
} else {
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)
if (slot >= 0) {
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)
return
}
@ -1608,7 +1608,7 @@ class FastLocalVarRef(
val actualOwner = cachedOwnerScope
if (slot >= 0 && actualOwner != null) {
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 (ownerValid) PerfStats.fastLocalHit++ else PerfStats.fastLocalMiss++
}
@ -1652,7 +1652,7 @@ class FastLocalVarRef(
val actualOwner = cachedOwnerScope
if (slot >= 0 && actualOwner != null) {
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)
}
}
@ -1698,7 +1698,7 @@ class FastLocalVarRef(
val actualOwner = cachedOwnerScope
if (slot >= 0 && actualOwner != null) {
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)
return
}

View File

@ -776,38 +776,4 @@ class OOTest {
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())
}
}