- Type: Design proposal
- Author: Alexander Udalov
- Contributors: Andrey Breslav, Stanislav Erokhin, Denis Zharkov
- Status: Under consideration
- Prototype: In progress
Discussion of this proposal is held in this issue.
Support "bound callable references" and "bound class literals".
- 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
- 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).
- 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.
- 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
}
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.
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
.
- 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?
- 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.
- 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).
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
::<member>
,::class
. For a class member, empty LHS of a callable reference may meanthis
. Rationale: inside the class you can callmember()
, why not::member
then?super::<member>
(KT-11520)- Support references to member extensions (KT-8835)
All these features can be safely introduced later.