Kotlin object expression property scope and shadowing rules
Asked Answered
T

1

7

Why does the following code print 0?

class X {
    fun f() {
        val x = 0
        val a = object {
            val x = 1
            fun g() {
                println(x)
            }
        }
        a.g()
    }
}

fun main() {
    val x = X()
    x.f()
}

Why the inner property declaration does not shadow the outer one?

Tope answered 12/9, 2024 at 10:32 Comment(0)
Y
11

For an unqualified name, local callable (i.e. declarations in a statement scope) are always prioritised over non-local ones. The "things in inner scopes hides things in outer scopes" rule comes after that.

From the overload resolution section of the spec,

For an identifier named f the following sets are analyzed (in the given order):

  1. Local non-extension callables named f in the current scope and its upwards-linked scopes, ordered by the size of the scope (smallest first), excluding the package scope;
  2. The overload candidate sets for each pair of implicit receivers e and d available in the current scope, calculated as if e is the explicit receiver, in order of the receiver priority;
  3. [not relevant to this scenario]

Step 1 considers all the local callables, and only after that do we try callables with a receiver in the second step.

Applying this to your code,

fun f() {
    val x = 0 // This 'x' is local to 'f'
    val a = object {
        val x = 1 // This 'x' is not local. The body of an object literal is a declaration scope, not a statement scope
        fun g() {
            println(x)
        }
    }
    a.g()
}

If locals are not prioritised like this, you'd have no way of referring to the local x when you're in g.

Youngs answered 12/9, 2024 at 10:54 Comment(7)
Is there a reason they chose such scoping rules? It's counter intuitive for block-like lexical scopes, you'd expect the inner things shadow the outer ones.Tope
@Tope If x on its own accesses the object's property, how would you access the x that is local to f, in the body of g? You might invent some new syntax and introduce more complexity, but that's not necessary if x referred to the local x, since you can already refer to the object's property using this.x.Youngs
I guess that's the exact question Kotlin designers faced. I'd let x to bind the field, and not letting accessing the outer one. How would you access outer x here in Java? ``` void f() { int x = 0; class A { int x = 1; void g() { System.out.println(x); } } A a = new A(); a.g(); } ```Tope
@Tope I don't think you can do that in Java... And that's why Kotlin is better :)Youngs
@Tope To be clear, I am not one of the designers and I don't know what they were thinking. I'm just pointing out something that you would not be able to do if it were not designed this way.Youngs
So basically the scope says (if I can understand its language) is that inside inner classes, object, etc, everything first is resolved against the outer scope first?Tope
@Tope No, unqualified names are resolved against locals first. If there are no matches, try implicit receivers (i.e. implicit this). Locals are things declared in statement scopes (scopes where you can put statements, like for loops).Youngs

© 2022 - 2025 — McMap. All rights reserved.