Skip to content

Commit 555f67c

Browse files
oderskyKordyjan
authored andcommitted
Allow context bounds in type declarations
Expand them to deferred givens [Cherry-picked d923cac]
1 parent 81679fa commit 555f67c

20 files changed

+1355
-33
lines changed

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

+12-3
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)
@@ -492,6 +493,14 @@ object desugar {
492493
Apply(fn, params.map(refOfDef))
493494
}
494495

496+
def typeDef(tdef: TypeDef)(using Context): Tree =
497+
val evidenceBuf = new mutable.ListBuffer[ValDef]
498+
val result = desugarContextBounds(
499+
tdef, evidenceBuf,
500+
(tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags,
501+
inventGivenOrExtensionName, Nil)
502+
if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList)
503+
495504
/** The expansion of a class definition. See inline comments for what is involved */
496505
def classDef(cdef: TypeDef)(using Context): Tree = {
497506
val impl @ Template(constr0, _, self, _) = cdef.rhs: @unchecked
@@ -1426,7 +1435,7 @@ object desugar {
14261435
case tree: TypeDef =>
14271436
if (tree.isClassDef) classDef(tree)
14281437
else if (ctx.mode.isQuotedPattern) quotedPatternTypeDef(tree)
1429-
else tree
1438+
else typeDef(tree)
14301439
case tree: DefDef =>
14311440
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef
14321441
else defDef(tree)

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

+28-25
Original file line numberDiff line numberDiff line change
@@ -3930,51 +3930,54 @@ object Parsers {
39303930
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
39313931
}
39323932

3933-
/** TypeDef ::= id [TypeParamClause] {FunParamClause} TypeBounds [‘=’ Type]
3933+
/** TypeDef ::= id [TypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type]
39343934
*/
39353935
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
39363936
newLinesOpt()
39373937
atSpan(start, nameStart) {
39383938
val nameIdent = typeIdent()
3939+
val tname = nameIdent.name.asTypeName
39393940
val tparams = typeParamClauseOpt(ParamOwner.Type)
39403941
val vparamss = funParamClauses()
3942+
39413943
def makeTypeDef(rhs: Tree): Tree = {
39423944
val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
39433945
val tdef = TypeDef(nameIdent.name.toTypeName, rhs1)
39443946
if (nameIdent.isBackquoted)
39453947
tdef.pushAttachment(Backquoted, ())
39463948
finalizeDef(tdef, mods, start)
39473949
}
3950+
39483951
in.token match {
39493952
case EQUALS =>
39503953
in.nextToken()
39513954
makeTypeDef(toplevelTyp())
39523955
case SUBTYPE | SUPERTYPE =>
3953-
val bounds = typeBounds()
3954-
if (in.token == EQUALS) {
3955-
val eqOffset = in.skipToken()
3956-
var rhs = toplevelTyp()
3957-
rhs match {
3958-
case mtt: MatchTypeTree =>
3959-
bounds match {
3960-
case TypeBoundsTree(EmptyTree, upper, _) =>
3961-
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
3962-
case _ =>
3963-
syntaxError(em"cannot combine lower bound and match type alias", eqOffset)
3964-
}
3965-
case _ =>
3966-
if mods.is(Opaque) then
3967-
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
3968-
else
3969-
syntaxError(em"cannot combine bound and alias", eqOffset)
3970-
}
3971-
makeTypeDef(rhs)
3972-
}
3973-
else makeTypeDef(bounds)
3956+
typeAndCtxBounds(tname) match
3957+
case bounds: TypeBoundsTree if in.token == EQUALS =>
3958+
val eqOffset = in.skipToken()
3959+
var rhs = toplevelTyp()
3960+
rhs match {
3961+
case mtt: MatchTypeTree =>
3962+
bounds match {
3963+
case TypeBoundsTree(EmptyTree, upper, _) =>
3964+
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
3965+
case _ =>
3966+
syntaxError(em"cannot combine lower bound and match type alias", eqOffset)
3967+
}
3968+
case _ =>
3969+
if mods.is(Opaque) then
3970+
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
3971+
else
3972+
syntaxError(em"cannot combine bound and alias", eqOffset)
3973+
}
3974+
makeTypeDef(rhs)
3975+
case bounds => makeTypeDef(bounds)
39743976
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
3975-
makeTypeDef(typeBounds())
3976-
case _ if (staged & StageKind.QuotedPattern) != 0 =>
3977-
makeTypeDef(typeBounds())
3977+
makeTypeDef(typeAndCtxBounds(tname))
3978+
case _ if (staged & StageKind.QuotedPattern) != 0
3979+
|| in.featureEnabled(Feature.modularity) && in.isColon =>
3980+
makeTypeDef(typeAndCtxBounds(tname))
39783981
case _ =>
39793982
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token))
39803983
return EmptyTree // return to avoid setting the span to EmptyTree

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

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ parsercombinators-givens.scala
129129
parsercombinators-givens-2.scala
130130
parsercombinators-arrow.scala
131131
hylolib-deferred-given
132+
hylolib-cb
132133

133134

134135

docs/_docs/internals/syntax.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ PatDef ::= ids [‘:’ Type] [‘=’ Expr]
457457
DefDef ::= DefSig [‘:’ Type] [‘=’ Expr] DefDef(_, name, paramss, tpe, expr)
458458
| ‘this’ TypelessClauses [DefImplicitClause] ‘=’ ConstrExpr DefDef(_, <init>, vparamss, EmptyTree, expr | Block)
459459
DefSig ::= id [DefParamClauses] [DefImplicitClause]
460-
TypeDef ::= id [TypeParamClause] {FunParamClause} TypeBounds TypeDefTree(_, name, tparams, bound
460+
TypeDef ::= id [TypeParamClause] {FunParamClause} TypeAndCtxBounds TypeDefTree(_, name, tparams, bound
461461
[‘=’ Type]
462462
463463
TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef

tests/pos/deferredSummon.scala

+19-2
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

+10
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

+18
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
+66
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

+76
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)