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

Viewed 708

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?

2 Answers

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

  1. Both are KCallable and have the same name
  2. 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).

Related