File: operator-conventions.md

package info (click to toggle)
kotlin 1.3.31%2Bds1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 109,908 kB
  • sloc: java: 454,756; xml: 18,599; javascript: 10,452; sh: 513; python: 97; makefile: 69; ansic: 4
file content (150 lines) | stat: -rw-r--r-- 5,888 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# 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()`.