| # Operator Conventions |
| |
| Kotlin allows us to provide implementations for a predefined set of operators on our types. These operators have fixed symbolic representation |
| (like `+` or `*`) and fixed (see grammar). To implement an operator, we provide a member function |
| or an extension function with a fixed name, for the corresponding type, i.e. left-hand side type for binary operations and argument type for unary ones. |
| |
| Here we describe the conventions that regulate operator overloading for different operators. |
| |
| ## Unary operations |
| |
| | Expression | Translated to | |
| |------------|---------------| |
| | `+a` | `a.plus()` | |
| | `-a` | `a.minus()` | |
| | `!a` | `a.not()` | |
| |
| This table says that when the compiler processes, for example, an expression `+a`, it performs the following steps: |
| |
| * Determines the type of `a`, let it be `T`. |
| * Looks up a function `plus()` with no parameters for the receiver `T`, i.e. a member function or an extension function. |
| * If the function is absent or ambiguous, it is a compilation error. |
| * If the function is present and its return type is `R`, the expression `+a` has type `R`. |
| |
| | Expression | Translated to | |
| |------------|---------------| |
| | `a++` | `a.inc()` + see below | |
| | `a--` | `a.dec()` + see below | |
| |
| These operations are supposed to change their receiver and (optionally) return a value. |
| |
| > **`inc()/dec()` shouldn't mutate the receiver object**.<br> |
| > By "changing the receiver" we mean _the receiver-variable_, not the receiver object. |
| {:.note} |
| |
| The compiler performs the following steps for resolution of an operator in the *postfix* form, e.g. `a++`: |
| |
| * Determines the type of `a`, let it be `T`. |
| * Looks up a function `inc()` with no parameters, applicable to the receiver of type `T`. |
| * If the function returns a type `R`, then it must be a subtype of `T`. |
| |
| The effect of computing the expression is: |
| |
| * Store the initial value of `a` to a temporary storage `a0`, |
| * Assign the result of `a.inc()` to `a`, |
| * Return `a0` as a result of the expression. |
| |
| For `a--` the steps are completely analogous. |
| |
| For the *prefix* forms `++a` and `--a` resolution works the same way, and the effect is: |
| |
| * Assign the result of `a.inc()` to `a`, |
| * Return the new value of `a` as a result of the expression. |
| |
| ## Binary operations |
| |
| | Expression | Translated to | |
| | -----------|-------------- | |
| | `a + b` | `a.plus(b)` | |
| | `a - b` | `a.minus(b)` | |
| | `a * b` | `a.times(b)` | |
| | `a / b` | `a.div(b)` | |
| | `a % b` | `a.mod(b)` | |
| | `a..b ` | `a.rangeTo(b)` | |
| |
| For the operations in this table, the compiler just resolves the expression in the *Translated to* column. |
| |
| | Expression | Translated to | |
| | -----------|-------------- | |
| | `a in b` | `b.contains(a)` | |
| | `a !in b` | `!b.contains(a)` | |
| |
| For `in` and `!in` the procedure is the same, but the order of arguments is reversed. |
| {:#in} |
| |
| {:#Equals} |
| |
| | Expression | Translated to | |
| |------------|---------------| |
| | `a == b` | `a?.equals(b) ?: b === null` | |
| | `a != b` | `!(a?.equals(b) ?: b === null)` | |
| |
| *Note*: `===` and `!==` (identity checks) are not overloadable, so no conventions exist for them |
| |
| The `==` operation is special in two ways: |
| |
| * It is translated to a complex expression that screens for `null`'s, and `null == null` is `true`. |
| * It looks up a function with a specific _signature_, not just a specific _name_. The function must be declared as |
| |
| ``` kotlin |
| fun equals(other: Any?): Boolean |
| ``` |
| |
| Or an extension function with the same parameter list and return type. |
| |
| | Symbol | Translated to | |
| |--------|---------------| |
| | `a > b` | `a.compareTo(b) > 0` | |
| | `a < b` | `a.compareTo(b) < 0` | |
| | `a >= b` | `a.compareTo(b) >= 0` | |
| | `a <= b` | `a.compareTo(b) <= 0` | |
| |
| All comparisons are translated into calls to `compareTo`, that is required to return `Int`. |
| |
| ## Indexing and invocations |
| |
| | Symbol | Translated to | |
| | -------|-------------- | |
| | `a[i]` | `a.get(i)` | |
| | `a[i, j]` | `a.get(i, j)` | |
| | `a[i_1, ..., i_n]` | `a.get(i_1, ..., i_n)` | |
| | `a[i] = b` | `a.set(i, b)` | |
| | `a[i, j] = b` | `a.set(i, j, b)` | |
| | `a[i_1, ..., i_n] = b` | `a.set(i_1, ..., i_n, b)` | |
| |
| Square brackets are translated to calls to `get` and `set` with appropriate numbers of arguments. |
| |
| | Symbol | Translated to | |
| |--------|---------------| |
| | `a(i)` | `a.invoke(i)` | |
| | `a(i, j)` | `a.invoke(i, j)` | |
| | `a(i_1, ..., i_n)` | `a.invoke(i_1, ..., i_n)` | |
| |
| Parentheses are translated to calls to invoke with appropriate number of arguments. |
| |
| ## Assignments |
| |
| | Expression | Translated to | |
| |------------|---------------| |
| | `a += b` | `a.plusAssign(b)` | |
| | `a -= b` | `a.minusAssign(b)` | |
| | `a *= b` | `a.timesAssign(b)` | |
| | `a /= b` | `a.divAssign(b)` | |
| | `a %= b` | `a.modAssign(b)` | |
| |
| For the assignment operations, e.g. `a += b`, the compiler performs the following steps: |
| |
| * If the function from the right column is available |
| * If the left-hand side can be assigned to and the corresponding binary function (i.e. `plus()` for `plusAssign()`) is available, report error (ambiguity). |
| * Make sure its return type is `Unit`, and report an error otherwise. |
| * Generate code for `a.plusAssign(b)` |
| * Otherwise, try to generate code for `a = a + b` (this includes a type check: the type of `a + b` must be a subtype of `a`). |
| |
| *Note*: assignments are *NOT* expressions in Kotlin. |
| |
| **Discussion of the ambiguity rule**: |
| We raise an error when both `plus()` and `plusAssign()` are available only if the lhs is assignable. Otherwise, the availability of `plus()` |
| is irrelevant, because we know that `a = a + b` can not compile. An important concern here is what happens when the lhs *becomes assignable* |
| after the fact (e.g. the user changes *val* to *var* or provides a `set()` function for indexing convention): in this case, the previously |
| correct call site may become incorrect, but not the other way around, which is safe, because former calls to `plusAssign()` can not be silently |
| turned into calls to `plus()`. |