Skip to content

Commit 78cd1db

Browse files
authored
Merge pull request #6619 from dotty-staging/fix-inline-docs
Fix inline docs
2 parents 06339d9 + 601758f commit 78cd1db

13 files changed

+597
-355
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
layout: doc-page
3+
title: "Erased Terms Spec"
4+
---
5+
6+
# Implementation
7+
8+
## Rules
9+
10+
1. The `erased` modifier can appear:
11+
* At the start of a parameter block of a method, function or class
12+
* In a method definition
13+
* In a `val` definition (but not `lazy val` or `var`)
14+
15+
```scala
16+
erased val x = ...
17+
erased def f = ...
18+
19+
def g erased (x: Int) = ...
20+
21+
erased (x: Int) => ...
22+
def h(x: erased Int => Int) = ...
23+
24+
class K erased (x: Int) { ... }
25+
```
26+
27+
28+
2. A reference to an `erased` definition can only be used
29+
* Inside the expression of argument to an `erased` parameter
30+
* Inside the body of an `erased` `val` or `def`
31+
32+
33+
3. Functions
34+
* `erased (x1: T1, x2: T2, ..., xN: TN) => y : erased (T1, T2, ..., TN) => R`
35+
* `given erased (x1: T1, x2: T2, ..., xN: TN) => y : given erased (T1, T2, ..., TN) => R`
36+
* `given erased T1 => R <:< erased T1 => R`
37+
* `given erased (T1, T2) => R <:< erased (T1, T2) => R`
38+
* ...
39+
40+
Note that there is no subtype relation between `erased T => R` and `T => R` (or `given erased T => R` and `given T => R`)
41+
42+
43+
4. Eta expansion
44+
45+
if `def f erased (x: T): U` then `f: erased (T) => U`.
46+
47+
48+
5. Erasure Semantics
49+
* All `erased` parameters are removed from the function
50+
* All argument to `erased` parameters are not passed to the function
51+
* All `erased` definitions are removed
52+
* All ` erased (T1, T2, ..., TN) => R` and `(given erased T1, T2, ..., TN) => R` become `() => R`
53+
54+
55+
6. Overloading
56+
57+
Method with `erased` parameters will follow the normal overloading constraints after erasure.
58+
59+
60+
7. Overriding
61+
* Member definitions overriding each other must both be `erased` or not be `erased`
62+
* `def foo(x: T): U` cannot be overridden by `def foo erased (x: T): U` an vice-versa
63+
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
---
2+
layout: doc-page
3+
title: "Erased Terms"
4+
---
5+
6+
# Why erased terms?
7+
8+
Let's describe the motivation behind erased terms with an example. In the
9+
following we show a simple state machine which can be in a state `On` or `Off`.
10+
The machine can change state from `Off` to `On` with `turnedOn` only if it is
11+
currently `Off`. This last constraint is captured with the `IsOff[S]` implicit
12+
evidence which only exists for `IsOff[Off]`. For example, not allowing calling
13+
`turnedOn` on in an `On` state as we would require an evidence of type
14+
`IsOff[On]` that will not be found.
15+
16+
```scala
17+
sealed trait State
18+
final class On extends State
19+
final class Off extends State
20+
21+
@implicitNotFound("State is must be Off")
22+
class IsOff[S <: State]
23+
object IsOff {
24+
implicit def isOff: IsOff[Off] = new IsOff[Off]
25+
}
26+
27+
class Machine[S <: State] {
28+
def turnedOn(implicit ev: IsOff[S]): Machine[On] = new Machine[On]
29+
}
30+
31+
val m = new Machine[Off]
32+
m.turnedOn
33+
m.turnedOn.turnedOn // ERROR
34+
// ^
35+
// State is must be Off
36+
```
37+
38+
Note that in the code above the actual implicit arguments for `IsOff` are never
39+
used at runtime; they serve only to establish the right constraints at compile
40+
time. As these terms are never used at runtime there is not real need to have
41+
them around, but they still need to be present in some form in the generated
42+
code to be able to do separate compilation and retain binary compatibility. We
43+
introduce _erased terms_ to overcome this limitation: we are able to enforce the
44+
right constrains on terms at compile time. These terms have no run time
45+
semantics and they are completely erased.
46+
47+
# How to define erased terms?
48+
49+
Parameters of methods and functions can be declared as erased, placing `erased`
50+
in front of a parameter list (like `given`).
51+
52+
```scala
53+
def methodWithErasedEv erased (ev: Ev): Int = 42
54+
55+
val lambdaWithErasedEv: erased Ev => Int =
56+
erased (ev: Ev) => 42
57+
```
58+
59+
`erased` parameters will not be usable for computations, though they can be used
60+
as arguments to other `erased` parameters.
61+
62+
```scala
63+
def methodWithErasedInt1 erased (i: Int): Int =
64+
i + 42 // ERROR: can not use i
65+
66+
def methodWithErasedInt2 erased (i: Int): Int =
67+
methodWithErasedInt1(i) // OK
68+
```
69+
70+
Not only parameters can be marked as erased, `val` and `def` can also be marked
71+
with `erased`. These will also only be usable as arguments to `erased`
72+
parameters.
73+
74+
```scala
75+
erased val erasedEvidence: Ev = ...
76+
methodWithErasedEv(erasedEvidence)
77+
```
78+
79+
# What happens with erased values at runtime?
80+
81+
As `erased` are guaranteed not to be used in computations, they can and will be
82+
erased.
83+
84+
```scala
85+
// becomes def methodWithErasedEv(): Int at runtime
86+
def methodWithErasedEv erased (ev: Ev): Int = ...
87+
88+
def evidence1: Ev = ...
89+
erased def erasedEvidence2: Ev = ... // does not exist at runtime
90+
erased val erasedEvidence3: Ev = ... // does not exist at runtime
91+
92+
// evidence1 is not evaluated and no value is passed to methodWithErasedEv
93+
methodWithErasedEv(evidence1)
94+
```
95+
96+
# State machine with erased evidence example
97+
98+
The following example is an extended implementation of a simple state machine
99+
which can be in a state `On` or `Off`. The machine can change state from `Off`
100+
to `On` with `turnedOn` only if it is currently `Off`, conversely from `On` to
101+
`Off` with `turnedOff` only if it is currently `On`. These last constraint are
102+
captured with the `IsOff[S]` and `IsOn[S]` implicit evidence only exist for
103+
`IsOff[Off]` and `IsOn[On]`. For example, not allowing calling `turnedOff` on in
104+
an `Off` state as we would require an evidence `IsOn[Off]` that will not be
105+
found.
106+
107+
As the implicit evidences of `turnedOn` and `turnedOff` are not used in the
108+
bodies of those functions we can mark them as `erased`. This will remove the
109+
evidence parameters at runtime, but we would still evaluate the `isOn` and
110+
`isOff` implicits that were found as arguments. As `isOn` and `isOff` are not
111+
used except as `erased` arguments, we can mark them as `erased`, hence removing
112+
the evaluation of the `isOn` and `isOff` evidences.
113+
114+
```scala
115+
import scala.annotation.implicitNotFound
116+
117+
sealed trait State
118+
final class On extends State
119+
final class Off extends State
120+
121+
@implicitNotFound("State is must be Off")
122+
class IsOff[S <: State]
123+
object IsOff {
124+
// def isOff will not be called at runtime for turnedOn, the compiler will only require that this evidence exists
125+
implicit def isOff: IsOff[Off] = new IsOff[Off]
126+
}
127+
128+
@implicitNotFound("State is must be On")
129+
class IsOn[S <: State]
130+
object IsOn {
131+
// erased val isOn will not exist at runtime, the compiler will only require that this evidence exists at compile time
132+
erased implicit val isOn: IsOn[On] = new IsOn[On]
133+
}
134+
135+
class Machine[S <: State] private {
136+
// ev will disappear from both functions
137+
def turnedOn given erased (ev: IsOff[S]): Machine[On] = new Machine[On]
138+
def turnedOff given erased (ev: IsOn[S]): Machine[Off] = new Machine[Off]
139+
}
140+
141+
object Machine {
142+
def newMachine(): Machine[Off] = new Machine[Off]
143+
}
144+
145+
object Test {
146+
def main(args: Array[String]): Unit = {
147+
val m = Machine.newMachine()
148+
m.turnedOn
149+
m.turnedOn.turnedOff
150+
151+
// m.turnedOff
152+
// ^
153+
// State is must be On
154+
155+
// m.turnedOn.turnedOn
156+
// ^
157+
// State is must be Off
158+
}
159+
}
160+
```
161+
162+
Note that in [Inline](./inline.html) we discussed `erasedValue` and inline
163+
matches. `erasedValue` is implemented with `erased`, so the state machine above
164+
can be encoded as follows:
165+
166+
```scala
167+
import scala.compiletime._
168+
169+
sealed trait State
170+
final class On extends State
171+
final class Off extends State
172+
173+
class Machine[S <: State] {
174+
inline def turnOn() <: Machine[On] = inline erasedValue[S] match {
175+
case _: Off => new Machine[On]
176+
case _: On => error("Turning on an already turned on machine")
177+
}
178+
inline def turnOff() <: Machine[Off] = inline erasedValue[S] match {
179+
case _: On => new Machine[Off]
180+
case _: Off => error("Turning off an already turned off machine")
181+
}
182+
}
183+
184+
object Machine {
185+
def newMachine(): Machine[Off] = {
186+
println("newMachine")
187+
new Machine[Off]
188+
}
189+
}
190+
191+
object Test {
192+
val m = Machine.newMachine()
193+
m.turnOn()
194+
m.turnOn().turnOff()
195+
m.turnOn().turnOn() // error: Turning on an already turned on machine
196+
}
197+
```
198+
199+
[More Details](./erased-terms-spec.html)

0 commit comments

Comments
 (0)