Skip to content

Commit 2cab591

Browse files
committed
Add compiletime ops documentation
+ Add test to check that code example in the documentation compiles
1 parent da71e1d commit 2cab591

File tree

3 files changed

+201
-3
lines changed

3 files changed

+201
-3
lines changed

docs/docs/reference/metaprogramming/inline.md

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ val intTwo: 2 = natTwo
295295

296296
The `scala.compiletime` package contains helper definitions that provide support for compile time operations over values. They are described in the following.
297297

298-
#### `constValue`, `constValueOpt`, and the `S` combinator
298+
### `constValue`, `constValueOpt`, and the `S` combinator
299299

300300
`constvalue` is a function that produces the constant value represented by a
301301
type.
@@ -317,7 +317,7 @@ enabling us to handle situations where a value is not present. Note that `S` is
317317
the type of the successor of some singleton type. For example the type `S[1]` is
318318
the singleton type `2`.
319319

320-
#### `erasedValue`
320+
### `erasedValue`
321321

322322
So far we have seen inline methods that take terms (tuples and integers) as
323323
parameters. What if we want to base case distinctions on types instead? For
@@ -381,7 +381,7 @@ final val two = toIntT[Succ[Succ[Zero.type]]]
381381
behavior. Since `toInt` performs static checks over the static type of `N` we
382382
can safely use it to scrutinize its return type (`S[S[Z]]` in this case).
383383

384-
#### `error`
384+
### `error`
385385

386386
The `error` method is used to produce user-defined compile errors during inline expansion.
387387
It has the following signature:
@@ -411,6 +411,54 @@ inline def fail(p1: => Any) = {
411411
fail(identity("foo")) // error: failed on: identity("foo")
412412
```
413413

414+
### The `scala.compiletime.ops` package
415+
416+
The `scala.compiletime.ops` package contains types that provide support for
417+
primitive operations on singleton types. For example,
418+
`scala.compiletime.ops.int.*` provides support for multiplying two singleton
419+
`Int` types, and `scala.compiletime.ops.boolean.&&` for the conjunction of two
420+
`Boolean` types. When all arguments to a type in `scala.compiletime.ops` are
421+
singleton types, the compiler can evaluate the result of the operation.
422+
423+
```scala
424+
import scala.compiletime.ops.int._
425+
import scala.compiletime.ops.boolean._
426+
427+
val conjunction: true && true = true
428+
val multiplication: 3 * 5 = 15
429+
```
430+
431+
Many of these singleton operation types are meant to be used infix (as in [SLS §
432+
3.2.8](https://www.scala-lang.org/files/archive/spec/2.12/03-types.html#infix-types)),
433+
and are annotated with [`@infix`](scala.annotation.infix) accordingly.
434+
435+
Since type aliases have the same precedence rules as their term-level
436+
equivalents, the operations compose with the expected precedence rules:
437+
438+
```scala
439+
import scala.compiletime.ops.int._
440+
val x: 1 + 2 * 3 = 7
441+
```
442+
443+
The operation types are located in packages named after the type of the
444+
left-hand side parameter: for instance, `scala.compiletime.int.+` represents
445+
addition of two numbers, while `scala.compiletime.string.+` represents string
446+
concatenation. To use both and distinguish the two types from each other, a
447+
match type can dispatch to the correct implementation:
448+
449+
```scala
450+
import scala.compiletime.ops._
451+
import scala.annotation.infix
452+
453+
@infix type +[X <: Int | String, Y <: Int | String] = (X, Y) match {
454+
case (Int, Int) => int.+[X, Y]
455+
case (String, String) => string.+[X, Y]
456+
}
457+
458+
val concat: "a" + "b" = "ab"
459+
val addition: 1 + 1 = 2
460+
```
461+
414462
## Summoning Implicits Selectively
415463

416464
It is foreseen that many areas of typelevel programming can be done with rewrite

library/src/scala/compiletime/ops/package.scala

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,170 @@ package scala.compiletime
33
import scala.annotation.infix
44

55
package object ops {
6+
/** Equality comparison of two singleton types.
7+
* ```scala
8+
* val eq1: 1 == 1 = true
9+
* val eq2: 1 == "1" = false
10+
* val eq3: "1" == "1" = true
11+
* ```
12+
*/
613
@infix type ==[X <: AnyVal, Y <: AnyVal] <: Boolean
14+
15+
/** Inequality comparison of two singleton types.
16+
* ```scala
17+
* val eq1: 1 != 1 = false
18+
* val eq2: 1 != "1" = true
19+
* val eq3: "1" != "1" = false
20+
* ```
21+
*/
722
@infix type !=[X <: AnyVal, Y <: AnyVal] <: Boolean
823

924
object string {
25+
/** Concatenation of two `String` singleton types.
26+
* ```scala
27+
* val hello: "hello " + "world" = "hello world"
28+
* ```
29+
*/
1030
@infix type +[X <: String, Y <: String] <: String
1131
}
1232

1333
object int {
34+
/** Addition of two `Int` singleton types. Type-level equivalent of `scala.Int.+`
35+
* ```scala
36+
* val sum: 2 + 2 = 4
37+
* ```
38+
*/
1439
@infix type +[X <: Int, Y <: Int] <: Int
40+
41+
/** Subtraction of two `Int` singleton types. Type-level equivalent of `scala.Int.-`
42+
* ```scala
43+
* val sub: 4 - 2 = 2
44+
* ```
45+
*/
1546
@infix type -[X <: Int, Y <: Int] <: Int
47+
48+
/** Multiplication of two `Int` singleton types. Type-level equivalent of `scala.Int.*`
49+
* ```scala
50+
* val mul: 4 * 2 = 8
51+
* ```
52+
*/
1653
@infix type *[X <: Int, Y <: Int] <: Int
54+
55+
/** Integer division of two `Int` singleton types. Type-level equivalent of `scala.Int./`
56+
* ```scala
57+
* val div: 5 / 2 = 2
58+
* ```
59+
*/
1760
@infix type /[X <: Int, Y <: Int] <: Int
61+
62+
/** Remainder of the division of `X` by `Y`. Type-level equivalent of `scala.Int.%`
63+
* ```scala
64+
* val mod: 5 % 2 = 1
65+
* ```
66+
*/
1867
@infix type %[X <: Int, Y <: Int] <: Int
1968

69+
/** Less-than comparison of two `Int` singleton types. Type-level equivalent of `scala.Int.<`.
70+
* ```scala
71+
* val lt1: 4 < 2 = false
72+
* val lt2: 2 < 4 = true
73+
* ```
74+
*/
2075
@infix type <[X <: Int, Y <: Int] <: Boolean
76+
77+
/** Greater-than comparison of two `Int` singleton types. Type-level equivalent of `scala.Int.>`.
78+
* ```scala
79+
* val gt1: 4 > 2 = true
80+
* val gt2: 2 > 2 = false
81+
* ```
82+
*/
2183
@infix type >[X <: Int, Y <: Int] <: Boolean
84+
85+
/** Greater-or-equal comparison of two `Int` singleton types. Type-level equivalent of `scala.Int.>=`.
86+
* ```scala
87+
* val ge1: 4 >= 2 = true
88+
* val ge2: 2 >= 3 = false
89+
* ```
90+
*/
2291
@infix type >=[X <: Int, Y <: Int] <: Boolean
92+
93+
/** Less-or-equal comparison of two `Int` singleton types. Type-level equivalent of `scala.Int.<=`.
94+
* ```scala
95+
* val lt1: 4 <= 2 = false
96+
* val lt2: 2 <= 2 = true
97+
* ```
98+
*/
2399
@infix type <=[X <: Int, Y <: Int] <: Boolean
24100

101+
/** Absolute value of an `Int` singleton type. Type-level equivalent of `scala.Int.abs`.
102+
* ```scala
103+
* val abs: Abs[-1] = 1
104+
* ```
105+
*/
25106
type Abs[X <: Int] <: Int
107+
108+
/** Negation of an `Int` singleton type. Type-level equivalent of `scala.Int.unary_-`.
109+
* ```scala
110+
* val neg1: Neg[-1] = 1
111+
* val neg2: Neg[1] = -1
112+
* ```
113+
*/
26114
type Negate[X <: Int] <: Int
115+
116+
/** Minimum of two `Int` singleton types. Type-level equivalent of `scala.Int.min`.
117+
* ```scala
118+
* val min: Min[-1, 1] = -1
119+
* ```
120+
*/
27121
type Min[X <: Int, Y <: Int] <: Int
122+
123+
/** Maximum of two `Int` singleton types. Type-level equivalent of `scala.Int.max`.
124+
* ```scala
125+
* val abs: Abs[-1] = 1
126+
* ```
127+
*/
28128
type Max[X <: Int, Y <: Int] <: Int
129+
130+
/** String conversion of an `Int` singleton type. Type-level equivalent of `scala.Int.toString`.
131+
* ```scala
132+
* val abs: ToString[1] = "1"
133+
* ```
134+
*/
29135
type ToString[X <: Int] <: String
30136
}
31137

32138
object boolean {
139+
140+
/** Negation of a `Boolean` singleton type. Type-level equivalent of `scala.Boolean.unary_!`.
141+
* ```scala
142+
* val notFalse: ![false] = true
143+
* val notTrue: ![true] = false
144+
* ```
145+
*/
33146
type ![X <: Boolean] <: Boolean
147+
148+
/** Exclusive disjunction of two `Boolean` singleton types. Type-level equivalent of `scala.Boolean.^`.
149+
* ```scala
150+
* val a: true ^ true = false
151+
* val b: false ^ true = true
152+
* ```
153+
*/
34154
@infix type ^[X <: Boolean, Y <: Boolean] <: Boolean
155+
156+
/** Conjunction of two `Boolean` singleton types. Type-level equivalent of `scala.Boolean.&&`.
157+
* ```scala
158+
* val a: true && true = true
159+
* val b: false && true = false
160+
* ```
161+
*/
35162
@infix type &&[X <: Boolean, Y <: Boolean] <: Boolean
163+
164+
/** Disjunction of two `Boolean` singleton types. Type-level equivalent of `scala.Boolean.||`.
165+
* ```scala
166+
* val a: true || false = true
167+
* val b: false || false = false
168+
* ```
169+
*/
36170
@infix type ||[X <: Boolean, Y <: Boolean] <: Boolean
37171
}
38172
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import scala.compiletime.ops._
2+
import scala.annotation.infix
3+
4+
object Test {
5+
@infix type +[X <: Int | String, Y <: Int | String] = (X, Y) match {
6+
case (Int, Int) => int.+[X, Y]
7+
case (String, String) => string.+[X, Y]
8+
case (String, Int) => string.+[X, int.ToString[Y]]
9+
case (Int, String) => string.+[int.ToString[X], Y]
10+
}
11+
12+
val t0: "a" + 1 = "a1"
13+
val t1: "a" + "b" = "ab"
14+
val t2: 1 + "b" = "1b"
15+
val t3: 1 + 1 = 2
16+
}

0 commit comments

Comments
 (0)