Skip to content

Commit c62aeb4

Browse files
committed
Changes to default names for context bound witnesses
1. Use the constrained type's name as a term name only for single context bounds 2. Apply the same scheme to deferred givens
1 parent bbf8cdd commit c62aeb4

File tree

7 files changed

+152
-14
lines changed

7 files changed

+152
-14
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,14 @@ object desugar {
232232
flags: FlagSet,
233233
freshName: => TermName)(using Context): Tree = rhs match
234234
case ContextBounds(tbounds, cxbounds) =>
235-
var useParamName = Feature.enabled(Feature.modularity)
236235
for bound <- cxbounds do
237236
val evidenceName = bound match
238-
case ContextBoundTypeTree(_, _, ownName) if !ownName.isEmpty => ownName
239-
case _ if useParamName => tname.toTermName
240-
case _ => freshName
241-
useParamName = false
237+
case ContextBoundTypeTree(_, _, ownName) if !ownName.isEmpty =>
238+
ownName
239+
case _ if Feature.enabled(Feature.modularity) && cxbounds.tail.isEmpty =>
240+
tname.toTermName
241+
case _ =>
242+
freshName
242243
val evidenceParam = ValDef(evidenceName, bound, EmptyTree).withFlags(flags)
243244
evidenceParam.pushAttachment(ContextBoundParam, ())
244245
evidenceBuf += evidenceParam
@@ -484,7 +485,7 @@ object desugar {
484485
val evidenceBuf = new ListBuffer[ValDef]
485486
val result = cpy.TypeDef(tdef)(rhs =
486487
desugarContextBounds(tdef.name, tdef.rhs, evidenceBuf,
487-
(tdef.mods.flags.toTermFlags & AccessFlags) | DeferredGivenFlags, EmptyTermName))
488+
(tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags, EmptyTermName))
488489
if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList)
489490

490491
/** The expansion of a class definition. See inline comments for what is involved */

docs/_docs/reference/experimental/typeclasses-syntax.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,15 @@ So far, an unnamed context bound for a type parameter gets a synthesized fresh n
9090
xs.foldLeft(A.unit)(_ `combine` _)
9191
```
9292

93+
94+
9395
The use of a name like `A` above in two variants, both as a type name and as a term name is of course familiar to Scala programmers. We use the same convention for classes and companion objects. In retrospect, the idea of generalizing this to also cover type parameters is obvious. It is surprising that it was not brought up before.
9496

9597
**Proposed Rules**
9698

9799
1. The generated evidence parameter for a context bound `A : C as a` has name `a`
98100
2. The generated evidence for a context bound `A : C` without an `as` binding has name `A` (seen as a term name). So, `A : C` is equivalent to `A : C as A`.
99-
3. If there are more than one context bounds for a type parameter, the generated evidence parameter for every context bound except the first one has a fresh synthesized name, unless the context bound carries an `as` clause, in which case rule (1) applies.
101+
3. If there are multiple context bounds for a type parameter, as in `A : {C_1, ..., C_n}`, the generated evidence parameter for every context bound `C_i` has a fresh synthesized name, unless the context bound carries an `as` clause, in which case rule (1) applies.
100102

101103
The default naming convention reduces the need for named context bounds. But named context bounds are still essential, for at least two reasons:
102104

@@ -183,15 +185,22 @@ The compiler expands this to the following implementation:
183185
```scala
184186
trait Sorted:
185187
type Element
186-
given Ord[Element] = compiletime.deferred
188+
given Ord[Element] as Element = compiletime.deferred
187189

188190
class SortedSet[A](using A: Ord[A]) extends Sorted:
189191
type Element = A
190-
override given Ord[Element] = A // i.e. the A defined by the using clause
192+
override given Ord[Element] as Element = A // i.e. the A defined by the using clause
191193
```
192194

193195
The using clause in class `SortedSet` provides an implementation for the deferred given in trait `Sorted`.
194196

197+
If there is a single context bound, as in
198+
```scala
199+
type T : C
200+
```
201+
the synthesized deferred given will get the (term-)name of the constrained type `T`. If there are multiple bounds,
202+
the standard convention for naming anonymous givens applies.
203+
195204
**Benefits:**
196205

197206
- Better orthogonality, type parameters and abstract type members now accept the same kinds of bounds.

docs/_docs/reference/experimental/typeclasses.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ The use of a name like `A` above in two variants, both as a type name and as a t
219219

220220
1. The generated evidence parameter for a context bound `A : C as a` has name `a`
221221
2. The generated evidence for a context bound `A : C` without an `as` binding has name `A` (seen as a term name). So, `A : C` is equivalent to `A : C as A`.
222-
3. If there are more than one context bounds for a type parameter, the generated evidence parameter for every context bound except the first one has a fresh synthesized name, unless the context bound carries an `as` clause, in which case rule (1) applies.
222+
3. If there are multiple context bounds for a type parameter, as in `A : {C_1, ..., C_n}`, the generated evidence parameter for every context bound `C_i` has a fresh synthesized name, unless the context bound carries an `as` clause, in which case rule (1) applies.
223223

224224
The default naming convention reduces the need for named context bounds. But named context bounds are still essential, for at least two reasons:
225225

@@ -306,15 +306,22 @@ The compiler expands this to the following implementation:
306306
```scala
307307
trait Sorted:
308308
type Element
309-
given Ord[Element] = compiletime.deferred
309+
given Ord[Element] as Element = compiletime.deferred
310310

311311
class SortedSet[A](using A: Ord[A]) extends Sorted:
312312
type Element = A
313-
override given Ord[Element] = A // i.e. the A defined by the using clause
313+
override given Ord[Element] as Element = A // i.e. the A defined by the using clause
314314
```
315315

316316
The using clause in class `SortedSet` provides an implementation for the deferred given in trait `Sorted`.
317317

318+
If there is a single context bound, as in
319+
```scala
320+
type T : C
321+
```
322+
the synthesized deferred given will get the (term-)name of the constrained type `T`. If there are multiple bounds,
323+
the standard convention for naming anonymous givens applies.
324+
318325
**Benefits:**
319326

320327
- Better orthogonality, type parameters and abstract type members now accept the same kinds of bounds.

tests/neg/FromString.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//> using options -language:experimental.modularity -source future
2+
3+
trait FromString:
4+
type Self
5+
def fromString(s: String): Self
6+
7+
given Int forms FromString = _.toInt
8+
9+
given Double forms FromString = _.toDouble
10+
11+
def add[N: {FromString, Numeric as num}](a: String, b: String): N =
12+
num.plus(N.fromString(a), N.fromString(b)) // error: Not found: N // error

tests/pos/FromString.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ given Int forms FromString = _.toInt
88

99
given Double forms FromString = _.toDouble
1010

11-
def add[N: {FromString, Numeric as num}](a: String, b: String): N =
12-
num.plus(N.fromString(a), N.fromString(b))
11+
def add[N: {FromString as fs, Numeric as num}](a: String, b: String): N =
12+
num.plus(fs.fromString(a), fs.fromString(b))

tests/pos/deferred-givens.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
//> using options -language:experimental.modularity -source future
22
import compiletime.*
33
class Ord[Elem]
4+
given Ord[Double]
5+
6+
trait A:
7+
type Elem : Ord
8+
def foo = summon[Ord[Elem]]
9+
10+
class AC extends A:
11+
type Elem = Double
12+
override given Ord[Elem] as Elem = ???
13+
14+
class AD extends A:
15+
type Elem = Double
416

517
trait B:
618
type Elem
@@ -22,3 +34,4 @@ class E(using x: Ord[String]) extends B:
2234

2335
class F[X: Ord] extends B:
2436
type Elem = X
37+

tests/run/for-desugar-strawman.scala

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
2+
@main def Test =
3+
println:
4+
for
5+
x <- List(1, 2, 3)
6+
y = x + x
7+
if x >= 2
8+
i <- List.range(0, y)
9+
z = i * i
10+
if z % 2 == 0
11+
yield
12+
i * x
13+
14+
println:
15+
val xs = List(1, 2, 3)
16+
xs.flatMapDefined: x =>
17+
val y = x + x
18+
xs.applyFilter(x >= 2):
19+
val is = List.range(0, y)
20+
is.mapDefined: i =>
21+
val z = i * i
22+
is.applyFilter(z % 2 == 0):
23+
i * x
24+
25+
extension [A](as: List[A])
26+
27+
def applyFilter[B](p: => Boolean)(b: => B) =
28+
if p then Some(b) else None
29+
30+
def flatMapDefined[B](f: A => Option[IterableOnce[B]]): List[B] =
31+
as.flatMap: x =>
32+
f(x).getOrElse(Nil)
33+
34+
def mapDefined[B](f: A => Option[B]): List[B] =
35+
as.flatMap(f)
36+
37+
object UNDEFINED
38+
39+
extension [A](as: Vector[A])
40+
41+
def applyFilter[B](p: => Boolean)(b: => B) =
42+
if p then b else UNDEFINED
43+
44+
def flatMapDefined[B](f: A => IterableOnce[B] | UNDEFINED.type): Vector[B] =
45+
as.flatMap: x =>
46+
f(x) match
47+
case UNDEFINED => Nil
48+
case y: IterableOnce[B] => y
49+
50+
def mapDefined[B](f: A => B | UNDEFINED.type): Vector[B] =
51+
as.flatMap: x =>
52+
f(x) match
53+
case UNDEFINED => Nil
54+
case y: B => y :: Nil
55+
56+
/*
57+
F ::= val x = E; F
58+
x <- E; G
59+
G ::= []
60+
val x = E; G
61+
if E; G
62+
x <- E; G
63+
64+
Translation scheme:
65+
66+
{ for F yield E }c where c = undefined
67+
{ for G yield E }c where c is a reference to the generator preceding the G sequence
68+
69+
{ for [] yield E }c = E
70+
{ for p = Ep; G yield E }c = val p = Ep; { for G yield E }c
71+
{ for if Ep; G yield E}c = c.applyFilter(Ep)({ for G yield E }c)
72+
{ for p <- Ep; G yield E }c = val c1 = Ep; c1.BIND{ case p => { for G yield E }c1 } (c1 fresh)
73+
74+
where BIND = flatMapDefined if isGen(G), isFilter(G)
75+
= mapDefined if !isGen(G), isFilter(G)
76+
= flatMap if isGen(G), !isFilter(G)
77+
= map if !isGen(G), !isFilter(G)
78+
79+
{ for case p <- Ep; G yield E }c = { for $x <- Ep; if $x match case p => true case _ => false; p = $x@RuntimeChecked; G yield E }c
80+
{ for case p = Ep; G yield E }c = { for $x = Ep; if $x match case p => true case _ => false; p = $x@RuntimeChecked; G yield E}c
81+
82+
isFilter(if E; S)
83+
isFilter(val x = E; S) if isFilter(S)
84+
85+
isGen(x <- E; S)
86+
isGen(val x = E; S) if isGen(S)
87+
isGen(if E; S) if isGen(S)
88+
89+
*/
90+
91+
val foo = 1
92+
93+
def main2 =
94+
foo
95+
???
96+
??? match { case _ => 0 }

0 commit comments

Comments
 (0)