Version 1.9-rfc+0.1
kotlin.Any
kotlin.Nothing
kotlin.Unit
kotlin.Boolean
kotlin.Char
kotlin.String
kotlin.Enum
kotlin.Throwable
kotlin.Comparable
kotlin.Function
kotlin.annotation.Retention
kotlin.annotation.Target
kotlin.annotation.Repeatable
kotlin.RequiresOptIn
/ kotlin.OptIn
kotlin.Deprecated
/ kotlin.ReplaceWith
kotlin.Suppress
kotlin.SinceKotlin
kotlin.UnsafeVariance
kotlin.DslMarker
kotlin.PublishedApi
kotlin.BuilderInference
kotlin.RestrictSuspension
kotlin.OverloadResolutionByLambdaReturnType
Some syntax forms in Kotlin are defined by convention, meaning that their semantics are defined through syntactic expansion of one syntax form into another syntax form.
Particular cases of definition by convention include:
invoke
convention;Important: another case of definition by convention is safe navigation, which is covered in more detail in its respective section.
There are several points shared among all the syntax forms defined using definition by convention:
This expansion of a particular syntax form to a different piece of code is usually defined in the terms of operator functions.
Operator functions are function which are declared with a special keyword operator
and are not different from regular functions when called via function calls. However, operator functions can also be used in definition by convention.
Note: it is not important whether an operator function is a member or an extension, nor whether it is suspending or not. The only requirements are the ones listed in the respected sections.
For example, for an operator form
a + b
wherea
is of typeA
andb
is of typeB
any of the following function definitions are applicable:class A { // member function operator fun plus(b: B) = ... // suspending member function operator fun plus(b: B) = ... suspend } // extension function operator fun A.plus(b: B) = ... // suspending extension function operator fun A.plus(b: B) = ... suspend
Assuming additional implicit receiver of this type is available, it may also be an extension defined in another type:
object Ctx { // extension that is a member of some context type operator fun A.plus(b: B) = ... fun add(a: A, b: B) = a + b }
Note: different platforms may add additional criteria on whether a function may be considered a suitable candidate for operator convention.
The details of individual expansions are available in the sections of their respective operators, here we would like to describe how they interoperate.
For example, take the following declarations:
class A {
operator fun inc(): A { ... }
}
object B {
operator fun get(i: Int): A { ... }
operator fun set(i: Int, value: A) { ... }
}
object C {
operator fun get(i: Int): B { ... }
}
The expression C[0][0]++
is expanded (see the Expressions section for details) using the following rules:
The operations are expanded in order of their priority.
First, the increment operator is expanded, resulting in:
0][0] = C[0][0].inc() C[
Second, the assignment to an indexing expression (produced by the previous expansion) is expanded, resulting in:
0].set(C[0][0].inc()) C[
Third, the indexing expressions are expanded, resulting in:
get(0).set(C.get(0).get(0).inc()) C.
Important: although the resulting expression contains several instances of the subexpression
C.get(0)
, as all these instances were created from the same original syntax form, the subexpression is evaluated only once, making this code roughly equivalent to:val $tmp = C.get(0) tmp.set($tmp.get(0).inc()) $
A special case of definition by convention is the destructuring declaration of properties, which is available for local properties, parameters of lambda literals and the iteration variable of for-loops. See the corresponding sections for particular syntax.
This convention allows to introduce a number (one or more) of properties in the place of one by immediately destructuring the property during construction. The immediate value (that is, the initializing expression of the local property, the value acquired from the operator convention of a for-loop statement, or an argument passed into a lambda body) is assigned to a number of placeholders
where each placeholder is either an identifier or a special ignoring placeholder _
(note that _
is not a valid identifier in Kotlin). For each identifier the corresponding operator function componentK
with
being equal to the position of the placeholder in the declaration (starting from 1) is called without arguments and the result is assigned to a fresh value referred to as the identifier used. For each ignoring placeholder, no calls are performed and nothing is assigned. Each placeholder may be provided with an optional type signature
which is used in type inference as any property type would. Note that an ignoring placeholder may also be provided with a type signature, in which case although the call to corresponding componentM
function is not performed, it still must be checked for function applicability during type inference.
Examples:
val (x: A, _, z) = f()
is expanded to
val $tmp = f() val x: A = $tmp.component1() val z = $tmp.component3()
where
component1
andcomponent3
are suitable operator functions available on the value returned byf()
for((x: A, _, z) in f()) { ... }
is expanded to (as per for-loop expansion)
when(val $iterator = f().iterator()) { else -> while ($iterator.hasNext()) { val $tmp = $iterator.next() val x: A = $tmp.component1() val z = $tmp.component3() ...} }
where
iterator()
,next()
,hasNext()
,component1()
andcomponent3
are all suitable operator functions available on their respective receivers.{ (x: A, _, z) -> ... } foo
is expanded to
{ $tmp -> foo val x: A = $tmp.component1() val z = $tmp.component3() ...}
where
component1()
andcomponent3
are all suitable operator functions available on the value of lambda argument.