Skip to content

Commit 40676a1

Browse files
committed
Allow context bounds in type declarations
Expand them to deferred givens
1 parent 2106bee commit 40676a1

20 files changed

+1355
-34
lines changed

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,13 @@ object desugar {
237237

238238
def desugarRhs(rhs: Tree): Tree = rhs match
239239
case ContextBounds(tbounds, cxbounds) =>
240+
val isMember = flags.isAllOf(DeferredGivenFlags)
240241
for bound <- cxbounds do
241242
val evidenceName = bound match
242243
case ContextBoundTypeTree(_, _, ownName) if !ownName.isEmpty =>
243244
ownName
244-
case _ if Config.nameSingleContextBounds && cxbounds.tail.isEmpty
245-
&& Feature.enabled(Feature.modularity) =>
245+
case _ if Config.nameSingleContextBounds && !isMember
246+
&& cxbounds.tail.isEmpty && Feature.enabled(Feature.modularity) =>
246247
tdef.name.toTermName
247248
case _ =>
248249
freshName(bound)
@@ -262,7 +263,6 @@ object desugar {
262263
private def elimContextBounds(meth: DefDef, isPrimaryConstructor: Boolean)(using Context): DefDef =
263264
val DefDef(_, paramss, tpt, rhs) = meth
264265
val evidenceParamBuf = ListBuffer[ValDef]()
265-
266266
var seenContextBounds: Int = 0
267267
def freshName(unused: Tree) =
268268
seenContextBounds += 1 // Start at 1 like FreshNameCreator.
@@ -492,6 +492,14 @@ object desugar {
492492
Apply(fn, params.map(refOfDef))
493493
}
494494

495+
def typeDef(tdef: TypeDef)(using Context): Tree =
496+
val evidenceBuf = new ListBuffer[ValDef]
497+
val result = desugarContextBounds(
498+
tdef, evidenceBuf,
499+
(tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags,
500+
inventGivenOrExtensionName, Nil)
501+
if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList)
502+
495503
/** The expansion of a class definition. See inline comments for what is involved */
496504
def classDef(cdef: TypeDef)(using Context): Tree = {
497505
val impl @ Template(constr0, _, self, _) = cdef.rhs: @unchecked
@@ -1425,7 +1433,7 @@ object desugar {
14251433
case tree: TypeDef =>
14261434
if (tree.isClassDef) classDef(tree)
14271435
else if (ctx.mode.isQuotedPattern) quotedPatternTypeDef(tree)
1428-
else tree
1436+
else typeDef(tree)
14291437
case tree: DefDef =>
14301438
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef
14311439
else defDef(tree)

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3888,51 +3888,54 @@ object Parsers {
38883888
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
38893889
}
38903890

3891-
/** TypeDef ::= id [TypeParamClause] {FunParamClause} TypeBounds [‘=’ Type]
3891+
/** TypeDef ::= id [TypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type]
38923892
*/
38933893
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
38943894
newLinesOpt()
38953895
atSpan(start, nameStart) {
38963896
val nameIdent = typeIdent()
3897+
val tname = nameIdent.name.asTypeName
38973898
val tparams = typeParamClauseOpt(ParamOwner.Type)
38983899
val vparamss = funParamClauses()
3900+
38993901
def makeTypeDef(rhs: Tree): Tree = {
39003902
val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
39013903
val tdef = TypeDef(nameIdent.name.toTypeName, rhs1)
39023904
if (nameIdent.isBackquoted)
39033905
tdef.pushAttachment(Backquoted, ())
39043906
finalizeDef(tdef, mods, start)
39053907
}
3908+
39063909
in.token match {
39073910
case EQUALS =>
39083911
in.nextToken()
39093912
makeTypeDef(toplevelTyp())
39103913
case SUBTYPE | SUPERTYPE =>
3911-
val bounds = typeBounds()
3912-
if (in.token == EQUALS) {
3913-
val eqOffset = in.skipToken()
3914-
var rhs = toplevelTyp()
3915-
rhs match {
3916-
case mtt: MatchTypeTree =>
3917-
bounds match {
3918-
case TypeBoundsTree(EmptyTree, upper, _) =>
3919-
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
3920-
case _ =>
3921-
syntaxError(em"cannot combine lower bound and match type alias", eqOffset)
3922-
}
3923-
case _ =>
3924-
if mods.is(Opaque) then
3925-
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
3926-
else
3927-
syntaxError(em"cannot combine bound and alias", eqOffset)
3928-
}
3929-
makeTypeDef(rhs)
3930-
}
3931-
else makeTypeDef(bounds)
3914+
typeAndCtxBounds(tname) match
3915+
case bounds: TypeBoundsTree if in.token == EQUALS =>
3916+
val eqOffset = in.skipToken()
3917+
var rhs = toplevelTyp()
3918+
rhs match {
3919+
case mtt: MatchTypeTree =>
3920+
bounds match {
3921+
case TypeBoundsTree(EmptyTree, upper, _) =>
3922+
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
3923+
case _ =>
3924+
syntaxError(em"cannot combine lower bound and match type alias", eqOffset)
3925+
}
3926+
case _ =>
3927+
if mods.is(Opaque) then
3928+
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
3929+
else
3930+
syntaxError(em"cannot combine bound and alias", eqOffset)
3931+
}
3932+
makeTypeDef(rhs)
3933+
case bounds => makeTypeDef(bounds)
39323934
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
3933-
makeTypeDef(typeBounds())
3934-
case _ if (staged & StageKind.QuotedPattern) != 0 =>
3935-
makeTypeDef(typeBounds())
3935+
makeTypeDef(typeAndCtxBounds(tname))
3936+
case _ if (staged & StageKind.QuotedPattern) != 0
3937+
|| in.featureEnabled(Feature.modularity) && in.isColon =>
3938+
makeTypeDef(typeAndCtxBounds(tname))
39363939
case _ =>
39373940
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token))
39383941
return EmptyTree // return to avoid setting the span to EmptyTree

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ parsercombinators-givens.scala
123123
parsercombinators-givens-2.scala
124124
parsercombinators-arrow.scala
125125
hylolib-deferred-given
126+
hylolib-cb
126127

127128

128129

docs/_docs/internals/syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ PatDef ::= ids [‘:’ Type] [‘=’ Expr]
449449
DefDef ::= DefSig [‘:’ Type] [‘=’ Expr] DefDef(_, name, paramss, tpe, expr)
450450
| ‘this’ TypelessClauses [DefImplicitClause] ‘=’ ConstrExpr DefDef(_, <init>, vparamss, EmptyTree, expr | Block)
451451
DefSig ::= id [DefParamClauses] [DefImplicitClause]
452-
TypeDef ::= id [TypeParamClause] {FunParamClause} TypeBounds TypeDefTree(_, name, tparams, bound
452+
TypeDef ::= id [TypeParamClause] {FunParamClause} TypeAndCtxBounds TypeDefTree(_, name, tparams, bound
453453
[‘=’ Type]
454454
455455
TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef

tests/pos/deferredSummon.scala

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ trait A:
99
given Ord[Elem] = deferred
1010
def foo = summon[Ord[Elem]]
1111

12+
trait B:
13+
type Elem: Ord
14+
def foo = summon[Ord[Elem]]
15+
1216
object Inst:
1317
given Ord[Int]:
1418
def less(x: Int, y: Int) = x < y
1519

16-
object Test:
20+
object Test1:
1721
import Inst.given
1822
class C extends A:
1923
type Elem = Int
@@ -22,9 +26,22 @@ object Test:
2226
given A:
2327
type Elem = Int
2428

25-
class D[T: Ord] extends A:
29+
class D1[T: Ord] extends B:
30+
type Elem = T
31+
32+
object Test2:
33+
import Inst.given
34+
class C extends B:
35+
type Elem = Int
36+
object E extends B:
37+
type Elem = Int
38+
given B:
39+
type Elem = Int
40+
41+
class D2[T: Ord] extends B:
2642
type Elem = T
2743

2844

2945

3046

47+

tests/pos/dep-context-bounds.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//> using options -language:experimental.modularity -source future
2+
trait A[X]:
3+
type Self = X
4+
5+
object Test2:
6+
def foo[X: A as x](a: x.Self) = ???
7+
8+
def bar[X: A as x](a: Int) = ???
9+
10+
def baz[X: A as x](a: Int)(using String) = ???

tests/pos/hylolib-cb-extract.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//> using options -language:experimental.modularity -source future
2+
package hylotest
3+
import compiletime.deferred
4+
5+
trait Value[Self]
6+
7+
/** A collection of elements accessible by their position. */
8+
trait Collection[Self]:
9+
10+
/** The type of the elements in the collection. */
11+
type Element: Value
12+
13+
class BitArray
14+
15+
given Value[Boolean] {}
16+
17+
given Collection[BitArray] with
18+
type Element = Boolean
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package hylo
2+
3+
/** A type-erased collection.
4+
*
5+
* A `AnyCollection` forwards its operations to a wrapped value, hiding its implementation.
6+
*/
7+
final class AnyCollection[Element] private (
8+
val _start: () => AnyValue,
9+
val _end: () => AnyValue,
10+
val _after: (AnyValue) => AnyValue,
11+
val _at: (AnyValue) => Element
12+
)
13+
14+
object AnyCollection {
15+
16+
/** Creates an instance forwarding its operations to `base`. */
17+
def apply[Base](using b: Collection[Base])(base: Base): AnyCollection[b.Element] =
18+
// NOTE: This evidence is redefined so the compiler won't report ambiguity between `intIsValue`
19+
// and `anyValueIsValue` when the method is called on a collection of `Int`s. None of these
20+
// choices is even correct! Note also that the ambiguity is suppressed if the constructor of
21+
// `AnyValue` is declared with a context bound rather than an implicit parameter.
22+
given Value[b.Position] = b.positionIsValue
23+
24+
def start(): AnyValue =
25+
AnyValue(base.startPosition)
26+
27+
def end(): AnyValue =
28+
AnyValue(base.endPosition)
29+
30+
def after(p: AnyValue): AnyValue =
31+
AnyValue(base.positionAfter(p.unsafelyUnwrappedAs[b.Position]))
32+
33+
def at(p: AnyValue): b.Element =
34+
base.at(p.unsafelyUnwrappedAs[b.Position])
35+
36+
new AnyCollection[b.Element](
37+
_start = start,
38+
_end = end,
39+
_after = after,
40+
_at = at
41+
)
42+
43+
}
44+
45+
given anyCollectionIsCollection[T](using tIsValue: Value[T]): Collection[AnyCollection[T]] with {
46+
47+
type Element = T
48+
type Position = AnyValue
49+
50+
extension (self: AnyCollection[T]) {
51+
52+
def startPosition =
53+
self._start()
54+
55+
def endPosition =
56+
self._end()
57+
58+
def positionAfter(p: Position) =
59+
self._after(p)
60+
61+
def at(p: Position) =
62+
self._at(p)
63+
64+
}
65+
66+
}

tests/pos/hylolib-cb/AnyValue.scala

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package hylo
2+
3+
/** A wrapper around an object providing a reference API. */
4+
private final class Ref[T](val value: T) {
5+
6+
override def toString: String =
7+
s"Ref($value)"
8+
9+
}
10+
11+
/** A type-erased value.
12+
*
13+
* An `AnyValue` forwards its operations to a wrapped value, hiding its implementation.
14+
*/
15+
final class AnyValue private (
16+
private val wrapped: AnyRef,
17+
private val _copy: (AnyRef) => AnyValue,
18+
private val _eq: (AnyRef, AnyRef) => Boolean,
19+
private val _hashInto: (AnyRef, Hasher) => Hasher
20+
) {
21+
22+
/** Returns a copy of `this`. */
23+
def copy(): AnyValue =
24+
_copy(this.wrapped)
25+
26+
/** Returns `true` iff `this` and `other` have an equivalent value. */
27+
def eq(other: AnyValue): Boolean =
28+
_eq(this.wrapped, other.wrapped)
29+
30+
/** Hashes the salient parts of `this` into `hasher`. */
31+
def hashInto(hasher: Hasher): Hasher =
32+
_hashInto(this.wrapped, hasher)
33+
34+
/** Returns the value wrapped in `this` as an instance of `T`. */
35+
def unsafelyUnwrappedAs[T]: T =
36+
wrapped.asInstanceOf[Ref[T]].value
37+
38+
/** Returns a textual description of `this`. */
39+
override def toString: String =
40+
wrapped.toString
41+
42+
}
43+
44+
object AnyValue {
45+
46+
/** Creates an instance wrapping `wrapped`. */
47+
def apply[T](using Value[T])(wrapped: T): AnyValue =
48+
def copy(a: AnyRef): AnyValue =
49+
AnyValue(a.asInstanceOf[Ref[T]].value.copy())
50+
51+
def eq(a: AnyRef, b: AnyRef): Boolean =
52+
a.asInstanceOf[Ref[T]].value `eq` b.asInstanceOf[Ref[T]].value
53+
54+
def hashInto(a: AnyRef, hasher: Hasher): Hasher =
55+
a.asInstanceOf[Ref[T]].value.hashInto(hasher)
56+
57+
new AnyValue(Ref(wrapped), copy, eq, hashInto)
58+
59+
}
60+
61+
given anyValueIsValue: Value[AnyValue] with {
62+
63+
extension (self: AnyValue) {
64+
65+
def copy(): AnyValue =
66+
self.copy()
67+
68+
def eq(other: AnyValue): Boolean =
69+
self `eq` other
70+
71+
def hashInto(hasher: Hasher): Hasher =
72+
self.hashInto(hasher)
73+
74+
}
75+
76+
}

0 commit comments

Comments
 (0)