Skip to content

Commit e52a757

Browse files
committed
Update docs
1 parent 270a2fc commit e52a757

File tree

5 files changed

+139
-36
lines changed

5 files changed

+139
-36
lines changed

docs/docs/reference/contextual/query-types.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,10 @@ As a larger example, here is a way to define constructs for checking arbitrary p
118118
object PostConditions {
119119
opaque type WrappedResult[T] = T
120120

121-
private object WrappedResult {
122-
def wrap[T](x: T): WrappedResult[T] = x
123-
def unwrap[T](x: WrappedResult[T]): T = x
124-
}
125-
126-
def result[T] given (r: WrappedResult[T]): T = WrappedResult.unwrap(r)
121+
def result[T] given (r: WrappedResult[T]): T = f
127122

128123
def (x: T) ensuring [T](condition: given WrappedResult[T] => Boolean): T = {
129-
implied for WrappedResult[T] = WrappedResult.wrap(x)
124+
implied for WrappedResult[T] = x
130125
assert(condition)
131126
x
132127
}
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: "Opaque Type Aliases: More Details"
4+
---
5+
6+
### Syntax
7+
8+
```
9+
Modifier ::= ...
10+
| ‘opaque’
11+
```
12+
`opaque` is a [soft modifier](../soft-modifier.html). It can still be used as a normal identifier when it is not in front of a definition keyword.
13+
14+
Opaque type aliases must be members of classes, traits, or objects, or they are defined
15+
at the top-level. They cannot be defined in local blocks.
16+
17+
### Type Checking
18+
19+
The general form of a (monomorphic) opaque type alias is
20+
```scala
21+
opaque type T >: L <: U = R
22+
```
23+
where the lower bound `L` and the upper bound `U` may be missing, in which case they are assumed to be `scala.Nothing` and `scala.Any`, respectively. If bounds are given, it is checked that the right hand side `R` conforms to them, i.e. `L <: R` and `R <: U`.
24+
25+
Inside the scope of the alias definition, the alias is transparent: `T` is treated
26+
as a normal alias of `R`. Outside its scope, the alias is treated as the abstract type
27+
```scala
28+
type T >: L <: U`
29+
```
30+
A special case arises if the opaque type is defined in an object. Example:
31+
```
32+
object o {
33+
opaque type T = R
34+
}
35+
```
36+
In this case we have inside the object (also for non-opaque types) that `o.T` is equal to
37+
`T` or its expanded form `o.this.T`. Equality is understood here as mutual subtyping, i.e.
38+
`o.T <: o.this.T` and `o.this.T <: T`. Furthermore, we have by the rules of opaque types
39+
that `o.this.T` equals `R`. The two equalities compose. That is, inside `o`, it is
40+
also known that `o.T` is equal to `R`. This means the following code type-checks:
41+
```scala
42+
object o {
43+
opaque type T = Int
44+
val x: Int = id(2)
45+
}
46+
def id(x: o.T): o.T = x
47+
```
48+
49+
### Relationship to SIP 35
50+
51+
Opaque types in Dotty are an evolution from what is described in
52+
[Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html).
53+
54+
The differences compared to the state described in this SIP are:
55+
56+
1. Opaque type aliases cannot be defined anymore in local statement sequences.
57+
2. The scope where an opaque type alias is visible is now the whole scope where
58+
it is defined, instead of just a companion object.
59+
3. The notion of a companion object for opaque type aliases has been dropped.
60+
4. Opaque type aliases can have bounds.
61+
5. The notion of type equality involving opaque type aliases has been clarified. It was
62+
strengthened with respect to the previous implementation of SIP 35.
63+

docs/docs/reference/other-new-features/opaques.md

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,42 @@ title: "Opaque Type Aliases"
66
Opaque types aliases provide type abstraction without any overhead. Example:
77

88
```scala
9-
opaque type Logarithm = Double
10-
```
11-
12-
This introduces `Logarithm` as a new type, which is implemented as `Double` but is different from it. The fact that `Logarithm` is the same as `Double` is only known in the companion object of `Logarithm`. Here is a possible companion object:
9+
object Logarithms {
1310

14-
```scala
15-
object Logarithm {
11+
opaque type Logarithm = Double
1612

17-
// These are the ways to lift to the logarithm type
18-
def apply(d: Double): Logarithm = math.log(d)
13+
object Logarithm {
1914

20-
def safe(d: Double): Option[Logarithm] =
21-
if (d > 0.0) Some(math.log(d)) else None
15+
// These are the ways to lift to the logarithm type
16+
def apply(d: Double): Logarithm = math.log(d)
2217

23-
// This is the first way to unlift the logarithm type
24-
def exponent(l: Logarithm): Double = l
18+
def safe(d: Double): Option[Logarithm] =
19+
if (d > 0.0) Some(math.log(d)) else None
20+
}
2521

2622
// Extension methods define opaque types' public APIs
27-
implicit class LogarithmOps(val `this`: Logarithm) extends AnyVal {
28-
// This is the second way to unlift the logarithm type
29-
def toDouble: Double = math.exp(`this`)
30-
def +(that: Logarithm): Logarithm = Logarithm(math.exp(`this`) + math.exp(that))
31-
def *(that: Logarithm): Logarithm = Logarithm(`this` + that)
23+
implied LogarithmOps {
24+
def (x: Logarithm) toDouble: Double = math.exp(x)
25+
def (x: Logarithm) + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y))
26+
def (x: Logarithm) * (y: Logarithm): Logarithm = Logarithm(x + y)
3227
}
3328
}
3429
```
3530

36-
The companion object contains with the `apply` and `safe` methods ways to convert from doubles to `Logarithm` values. It also adds an `exponent` function and a decorator that implements `+` and `*` on logarithm values, as well as a conversion `toDouble`. All this is possible because within object `Logarithm`, the type `Logarithm` is just an alias of `Double`.
31+
This introduces `Logarithm` as a new type, which is implemented as `Double` but is different from it. The fact that `Logarithm` is the same as `Double` is only known in the scope where
32+
`Logarithm` is defined which in this case is object `Logarithms`.
33+
34+
The public API of `Logarithm` consists of the `apply` and `safe` methods that convert from doubles to `Logarithm` values, an extension method `toDouble` that converts the other way,
35+
and operations `+` and `*` on logarithm values. The implementations of these functions
36+
type-check because within object `Logarithms`, the type `Logarithm` is just an alias of `Double`.
3737

38-
Outside the companion object, `Logarithm` is treated as a new abstract type. So the
38+
Outside its scope, `Logarithm` is treated as a new abstract type. So the
3939
following operations would be valid because they use functionality implemented in the `Logarithm` object.
4040

4141
```scala
42+
import Logarithms._
43+
import Predef.{any2stringadd => _, _}
44+
4245
val l = Logarithm(1.0)
4346
val l2 = Logarithm(2.0)
4447
val l3 = l * l2
@@ -54,6 +57,53 @@ But the following operations would lead to type errors:
5457
l / l2 // error: `/` is not a member fo Logarithm
5558
```
5659

57-
`opaque` is a [soft modifier](../soft-modifier.html).
60+
Aside: the `any2stringadd => _` import suppression is necessary since otherwise the universal `+` operation in `Predef` would take precedence over the `+` extension method in `LogarithmOps`. We plan to resolve this wart by eliminating `any2stringadd`.
61+
62+
### Bounds For Opaque Type Aliases
63+
64+
Opaque type aliases can also come with bounds. Example:
65+
```scala
66+
object Access {
67+
68+
opaque type Permissions = Int
69+
opaque type PermissionChoice = Int
70+
opaque type Permission <: Permissions & PermissionChoice = Int
71+
72+
def (x: Permissions) & (y: Permissions): Permissions = x & y
73+
def (x: PermissionChoice) | (y: PermissionChoice): PermissionChoice = x | y
74+
def (x: Permissions) is (y: Permissions) = (x & y) == y
75+
def (x: Permissions) isOneOf (y: PermissionChoice) = (x & y) != 0
76+
77+
val NoPermission: Permission = 0
78+
val ReadOnly: Permission = 1
79+
val WriteOnly: Permission = 2
80+
val ReadWrite: Permissions = ReadOnly & WriteOnly
81+
val ReadOrWrite: PermissionChoice = ReadOnly | WriteOnly
82+
}
83+
```
84+
The `Access` object defines three opaque types:
85+
86+
- `Permission`, representing a single permission,
87+
- `Permissions`, representing a conjunction (logical "and") of permissions,
88+
- `PermissionChoice`, representing a disjunction (logical "or") of permissions.
89+
90+
All three opaque types have the same underlying representation type `Int`. The
91+
`Permission` type has an upper bound `Permissions & PermissionChoice`. This makes
92+
it known outside the `Access` object that `Permission` is a subtype of the other
93+
two types. Hence, the following usage scenario type-checks.
94+
```scala
95+
object User {
96+
import Access._
97+
98+
case class Item(rights: Permissions)
99+
100+
val x = Item(ReadOnly) // OK, since Permission <: Permissions
101+
102+
assert( x.rights.is(ReadWrite) == false )
103+
assert( x.rights.isOneOf(ReadOrWrite) == true )
104+
}
105+
```
106+
On the other hand, the call `x.rights.isOneOf(ReadWrite)` would give a type error
107+
since `Permissions` and `PermissionChoice` are different, unrelated types outside `Access`.
58108

59-
For more details, see [Scala SIP 35](https://docs.scala-lang.org/sips/opaque-types.html).
109+
[More details](opaques-details.md)

tests/pos/postconditions.scala

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
object PostConditions {
22
opaque type WrappedResult[T] = T
33

4-
private object WrappedResult {
5-
def wrap[T](x: T): WrappedResult[T] = x
6-
def unwrap[T](x: WrappedResult[T]): T = x
7-
}
8-
9-
def result[T] given (r: WrappedResult[T]): T = WrappedResult.unwrap(r)
4+
def result[T] given (r: WrappedResult[T]): T = r
105

116
def (x: T) ensuring [T](condition: given WrappedResult[T] => Boolean): T = {
12-
implied for WrappedResult[T] = WrappedResult.wrap(x)
7+
implied for WrappedResult[T] = x
138
assert(condition)
149
x
1510
}

tests/pos/reference/opaque.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ object Logarithms {
1919
}
2020
}
2121

22-
object Test {
22+
object LogTest {
2323
import Logarithms._
2424
import Predef.{any2stringadd => _, _}
2525

0 commit comments

Comments
 (0)