Exposing an object variable as public makes its members unresolvable - kotlin

I have a Kotlin class with a member object:
private var strings = object { var foo = ""; var bar = "" }
Inside my class's functions, I can reference strings.foo and strings.bar.
But when I make strings a public var, suddenly all of its members become "unresolved references". Why?
class Foo {
// Making this `private` fixes the errors below for some reason
public var strings = object {
var foo = ""
var bar = ""
}
fun initialize() {
strings.foo = getFoo() // "Unresolved reference: foo"
strings.bar = getBar() // "Unresolved reference: bar"
}
}
(What I'm trying to do here is expose a whole bunch of string resources as constants so I don't have to use the literal string values throughout my code. I'm actually using lateinit var for each member but that doesn't change the result here.)

strings is defined with an object literal. You probably want to use an regular object declaration instead:
object Strings {...}
strings = Strings()
With the object literal, strings is an anonymous object whose type is "usable (and visible) only in the scope where it is declared".
without any explicitly declared supertype, menaing Any is implicit super type of this object.
When you make strings a public var, it escapes the current scope and is implicitly downcasted to its supertype; without any explicitly declared supertype, the supertype is Any, so of course you get errors trying to reference the properties foo and bar.
From Kotlin spec
The main difference between a regular object declaration and an anonymous object is its type. The type of an anonymous object is a special kind of type which is usable (and visible) only in the scope where it is declared. It is similar to a type of a regular object declaration, but, as it cannot be used outside the declaring scope, has some interesting effects.
When a value of an anonymous object type escapes current scope:
If the type has only one declared supertype, it is implicitly downcasted to this declared supertype;
If the type has several declared supertypes, there must be an implicit or explicit cast to any suitable type visible outside the scope, otherwise it is a compile-time error.
Note: an implicit cast may arise, for example, from the results of type inference.
Note: in this context “escaping current scope” is performed immediately if the corresponding value is declared as a non-private global- or classifier-scope property, as those are parts of an externally accessible interface.
You could also use a Companion object:
companion object Strings {
var foo = ""
var bar = ""
}
fun init(){
foo = getFoo()
bar = getBar()
}

Related

Implementation method according to the access limiter of anonymous object

When an anonymous object is public, it is simply implemented as an object return, but when it is private, it is returned as a type-cast object. look at the Kotlin code below.
private fun foo() = object {
val x: String = "x"
}
fun bar() = object {
val x: String = "x"
}
When this code is decompiled into Java, it changes like this:
private static final <undefinedtype> foo() {
return (<undefinedtype>)(new Object() {
#NotNull
private final String x = "x";
#NotNull
public final String getX() {
return this.x;
}
});
}
#NotNull
public static final Object bar() {
return new Object() {
#NotNull
private final String x = "x";
#NotNull
public final String getX() {
return this.x;
}
};
}
Therefore, when you try to use the code, only the private anonymous object can access x.
So, why do each access modifiers have different implementations?
That is how language defines the semantics of anonymous objects.
Type of an anonymous object is only usable in the declaring scope, when you use public modifier, anonymous object escapes the current scope and is cast to Any (implicit super type). since Any doesn't have any property x, you can't access it.
on the other hand when you use private modifier you make sure that it doesn't escape the current scope.
From the Kotlin language specification
The main difference between a regular object declaration and an
anonymous object is its type. The type of an anonymous object is a
special kind of type which is usable (and visible) only in the scope
where it is declared. It is similar to a type of a regular object
declaration, but, as it cannot be used outside the declaring scope,
has some interesting effects.
When a value of an anonymous object type escapes current scope:
If the type has only one declared supertype, it is implicitly downcasted to this declared supertype;
If the type has several declared supertypes, there must be an implicit or explicit cast to any suitable type visible outside the
scope, otherwise it is a compile-time error.
Note: in this context “escaping current scope” is performed
immediately if the corresponding value is declared as a non-private
global or classifier-scope property, as those are parts of an
externally accessible interface.

How to access the field of top-level object?

When I do this
val data = object {
val field = 5
}
fun main(){
println(data.field) // throws
}
It throws Unresolved reference: field.
But all of this is ok:
val field = 6
class Data(val field: Int = 7)
val data7 = Data()
fun main(){
val data4 = object {
val field = 4
}
println(field) // ok
println(data4.field) // ok
println(data7.field) // ok
}
I do not get it, why Kotlin does not let me use properties from top-level objects? I thought that object is just like class object, but anonymous (without class) and there should be no difference between data and data7 in examples above. But it seems that there is difference.
This is documented in the "Object Literals" section of the Language Specification, about the difference between object declarations and anonymous objects (the things that object literals create).
The main difference between a regular object declaration and an anonymous object is its type. The type of an anonymous object is a special kind of type which is usable (and visible) only in the scope where it is declared. It is similar to a type of a regular object declaration, but, as it cannot be used outside the declaring scope, has some interesting effects.
Your data here is considered to have escaped the declaring scope of "the top level of the file", because it is public. You can access it from the top level scopes of other files.
Note: in this context “escaping current scope” is performed immediately if the corresponding value is declared as a non-private global- or classifier-scope property, as those are parts of an externally accessible interface.
Marking it private would have fixed it. The reason for the error is that:
When a value of an anonymous object type escapes current scope:
If the type has only one declared supertype, it is implicitly downcasted to this declared supertype;
If the type has several declared supertypes, there must be an implicit or explicit cast to any suitable type visible outside the scope, otherwise it is a compile-time error.
Here, the super type is implicitly Any, so the type of data is Any, and obviously there is no field on the type Any.
On the other hand, data4 have not escaped the current scope, because it is local to the main function's statement scope. You can't access it from another scope.
See also the great example from the spec.

Check instance of anonymous object

If I create an anonymous class in Kotlin like this:
if(condition) {
object: Foo() {
fun bar() {
// code
}
}
} else {
Foo()
}
Is there a way to check in the code that the current instance has is the object class and hence I can call bar() which does not exist in Foo?
That object expression is creating an anonymous class, like m.antkowicz has pointed out:
Note that anonymous objects can be used as types only in local and private declarations. If you use an anonymous object as a return type of a public function or the type of a public property, the actual type of that function or property will be the declared supertype of the anonymous object, or Any if you didn't declare any supertype. Members added in the anonymous object will not be accessible.
That means your if expression is returning a local type which is known to contain bar(), but once it leaves that scope it will be declared as the supertype you used - it will be a Foo which does not contain a bar() method.
So, you need to use a supertype that does contain that member - you can use an interface to this, just like when you define a (non-anonymous) class:
interface Bar {
fun bar()
}
object : Foo(), Bar {
override fun bar() {...}
}
then you can use is Foo and is Bar to check which types the object has.
If you want to arbitrarily add functions to objects outside of the type system, and have other code able to know those functions are there, you're probably looking at doing reflection

Kotlin enum constructor argument 'must be initialized' with companion object constant without qualifier name

This code compiles successfully with the qualified name used to access the constants from the companion object:
enum class CampsiteCategoryCode(val code: String) {
TENT(CampsiteCategoryCode.TENT_CODE), // intellij says 'Redundant qualifier name'
OTHER(CampsiteCategoryCode.OTHER_CODE), // intellij says 'Redundant qualifier name'
LODGING(CampsiteCategoryCode.LODGING_CODE), // intellij says 'Redundant qualifier name'
RV(CampsiteCategoryCode.RV_CODE); // intellij says 'Redundant qualifier name'
override fun toString() = code
companion object {
const val TENT_CODE = "tent"
const val OTHER_CODE = "other"
const val LODGING_CODE = "lodging"
const val RV_CODE = "rv"
}
}
However, the same code without the qualifier name fails compilation:
enum class CampsiteCategoryCode(val code: String) {
TENT(TENT_CODE), // Variable 'TENT_CODE' must be initialized
OTHER(OTHER_CODE), // Variable 'OTHER_CODE' must be initialized
LODGING(LODGING_CODE), // Variable 'LODGING_CODE' must be initialized
RV(RV_CODE); // Variable 'RV_CODE' must be initialized
override fun toString() = code
companion object {
const val TENT_CODE = "tent"
const val OTHER_CODE = "other"
const val LODGING_CODE = "lodging"
const val RV_CODE = "rv"
}
}
Why does specifying the qualifier name allow this code to compile? Or put another way, why does not having the qualifier name make the code fail to compile?
That companion object will be instantiated the moment your class becomes available (compile time). Then you are telling your class that, for it to become available, it needs a class that is not yet available (because you're compiling it at the moment and, apparently, Kotlin makes enum cases available before companion objects)
I can't tell for sure if this is intended from Kotlin, but to avoid this kind of cases, declare your constants outside this class
There are two solutions for that:
make the variable a top-level declaration (as #baguIO mentioned)
mention the scope explicitly (use CampsiteCategoryCode.TENT_CODE instead of just TENT_CODE)

::property.isInitialized cannot differentiate between method and property with same name

I'm creating a builder (for Java compat), where context is both a private property and public method.
private lateinit var context: Context
fun context(appContext: Context) = apply {
context = appContext
}
fun build(): MySdk {
// this::context fails to compile because it cannot differentiate between the
// method `context()` vs property `context`
require(this::context.isInitialized) {
"context == null"
}
But I get a compilation issue for ::context.isInitialized, because it cannot differentiate between the method context() vs property context
Does Kotlin have a workaround for this? or am I forced to use unique property/method names?
This is a case of overload resolution ambiguity and the kotlin compiler is unable to identify whether you are using the property or the method.
This is because of callable references (::) . Internally when you are using the callable references it calls a method.
Callable references : References to functions, properties, and
constructors, apart from introspecting the program structure, can also
be called or used as instances of function types.
The common supertype for all callable references is KCallable, where R is the return value type, which is the property type for properties, and the constructed type for constructors.
KCallable<out R> // supertype for all callable references
So, for function the type is KFunction and for properties the type is KProperty
interface KFunction<out R> : KCallable<R>, Function<R> (source)
interface KProperty<out R> : KCallable<R> (source)
When you use a function like :
fun context(appContext: Context) = apply {
context = appContext
}
It can be used as a Function reference
::context // This is a Function reference i.e. KFunction
When you use a property reference, like
private lateinit var context: Context
fun something(){
::context // this is a property reference, KProperty
}
A property reference can be used where a function with one parameter is expected:
val strs = listOf("a", "bc", "def")
println(strs.map(String::length))
So, its not that Kotlin forces you to use different property and function names("although it is not recommended"). Its just that its unable to differentiate in this case as
Both are KCallable and have the same name
A property reference can be used where a function with one parameter is expected
You can resolve the ambiguity between the property and the method by giving the expected type:
val prop: kotlin.reflect.KProperty0<*> = this::context
Alas, prop.isInitialized then gives a compilation error:
This declaration can only be called on a property literal (e.g. 'Foo::bar')
So this doesn't appear to be possible currently. OTOH, since the error shows isInitialized is already handled specially by the compiler, it's likely possible to fix; I suggest reporting it on http://youtrack.jetbrains.com/ (after searching for duplicates).