Skip to content

Latest commit

 

History

History
137 lines (104 loc) · 5.16 KB

bound-callable-references.md

File metadata and controls

137 lines (104 loc) · 5.16 KB

Bound Callable References

  • Type: Design proposal
  • Author: Alexander Udalov
  • Contributors: Andrey Breslav, Stanislav Erokhin, Denis Zharkov
  • Status: Under consideration
  • Prototype: In progress

Feedback

Discussion of this proposal is held in this issue.

Summary

Support "bound callable references" and "bound class literals".

Motivation / use cases

  • It's painful to write lambdas every time
  • There's currently no way to reference an object member, which is sort of inconsistent with the fact that it's possible to reference static Java members
  • It's present in Java and its absence in Kotlin is rather inconvenient
  • 42 votes on KT-6947: Callable reference with expression on the left hand side

Description

  • Support the following syntax: <expression>::<member name> (see semantics below).
  • Drop the "callable reference to object/companion member" error. Leave other diagnostics for now.
  • Support the following syntax: <expression>::class (see semantics below).

Resolution

  1. Try interpreting the LHS as an expression with the usual resolution algorithm for qualified expressions. If the result represents a companion object of some class, continue to p.2 Otherwise continue resolution of the member in the scope of the expression's type.
  2. Resolve the unbound reference with the existing algorithm.

Examples:

class C {
    companion object {
        fun foo() {}
    }

    fun foo() {}
}

fun test() {
    C::foo             // unbound reference to 'foo' in C, type: '(C) -> Unit'
    C()::foo           // bound reference to 'foo' in C, type: '() -> Unit'
    (C)::foo           // bound reference to 'foo' in C.Companion, type: '() -> Unit'
    C.Companion::foo   // bound reference to 'foo' in C.Companion, type: '() -> Unit'
}

Note that references to object members will be considered bound by this algorithm (whether or not it should be possible to obtain an unbound reference for an object/companion member is an open question):

object O {
    fun foo() {}
}

fun consume(f: (Any?) -> Unit) {}

fun test() {
    O::foo             // bound reference to 'foo' in O, type: '() -> Unit'
    consume(O::foo)    // error, type mismatch (or maybe allow this)
}

Resolution of a LHS of a class literal expression is performed exactly the same, so that C::class means the class of C and (C)::class means the class of C.Companion.

It is an error if the LHS expression has nullable type and the resolved member is not an extension to nullable type. It is an error if the LHS of a bound class literal has nullable type:

class C {
    fun foo() {}
}

fun test(c: C?) {
    c::foo             // error
    c::class           // error
    null::class        // error
}

Code generation (JVM)

Putting aside reflection features, generated bytecode for val res = <expr>::foo should be similar to the following:

val tmp = <expr>
val res = { args -> tmp.foo(args) }

Generated bytecode for <expr>::class should be similar to <expr>.javaClass.kotlin. An intrinsic for <expr>::class.java, meaning <expr>.javaClass, would be nice.

Reflection

A KFunction instance for a bound callable reference has no receiver parameter. Its parameters property doesn't have this parameter and the argument should not be passed to call.

Open questions

  • API for "unbinding" a reference
    • Information about the original receiver type is absent in the type of a bound reference, so it's unclear what signature will the hypothetical "unbind" function have.
      • One option would be an unsafe "unbind" with a generic parameter which prepends that parameter to function type's parameter types:

        class O {
            fun foo() {}
            val bound: () -> Unit = this::foo
            val unbound: (O) -> Unit = this::foo.unbind<O>()
        }
        
    • If unbound already, throw or return null, or provide both?
  • Should there be a way to obtain an unbound reference to an object member?
    • May be covered with the general API for unbinding a reference, or may be approached in a completely different way (with a language feature, or a library function).

Alternatives

We could try resolve type in LHS first, and only then try expression. This has the following disadvantages:

  • Reference to object member would be unbound and so have the additional parameter of object type (but this can actually be special-cased like classes with companions above)
  • It would be counter to Java
  • It would contradict with Kotlin's philosophy of "locals win" (taken in a broader sense) in case when a top-level class and a local variable have the same name, which is referenced in LHS

Possible future advancements

  • ::<member>, ::class. For a class member, empty LHS of a callable reference may mean this. Rationale: inside the class you can call member(), why not ::member then?
  • super::<member> (KT-11520)
  • Support references to member extensions (KT-8835)

All these features can be safely introduced later.