Enum class accessing static members with redundant qualifier yields unexpected results

In this following minimum repro, we can see that the behaviour of enums differs from regular classes when accessing static members (see also Initialization order of companion object and initializers in an enum) however there is a weird edge case where if you use the fully qualified enum class, it compiles, and the value yielded is not the expected value, not a null pointer exception, but rather the zero byte representation of that type which can lead to very confusing bugs.

enum class FooEnum {

    A;

    companion object {
        val BAR = 42
    }

    init {
        println("FooEnum.BAR is ${FooEnum.BAR}") // FooEnum.BAR is 0
    }

}



class FooClass {

    companion object {
        val BAR = 42
    }

    init {
        println("Foo.BAR is ${FooClass.BAR}") // Foo.BAR is 42
    }

}

fun main() {
    FooEnum.A
    FooClass()
}

running the above prints
FooEnum.BAR is 0
Foo.BAR is 42

I would have expected (ideally) a compiler error (you get this if you remove the redundant qualifier) or a null pointer exception.

1 Like

Here’s a runnable and editable example:

enum class FooEnum {
    A;

    companion object {
        val BAR = 42
    }

    init {
        println("FooEnum.BAR is ${FooEnum.BAR}") // FooEnum.BAR is 0
    }
}

class FooClass {
    companion object {
        val BAR = 42
    }

    init {
        println("Foo.BAR is ${FooClass.BAR}") // Foo.BAR is 42
    }
}

fun main() {
    FooEnum.A
    FooClass()
}

In the enum case the init block is executed before the companion object initialization, this is because the enum is “read” in order by the compiler, which while constructing A calls to init.

The class case is different, the instance of FooClass is being constructed after all the class has been parsed (companion object included).

1 Like