Skip to content

Commit 0f1c4c7

Browse files
committed
Allow context bounds in type declarations
Expand them to deferred givens
1 parent 6403fc0 commit 0f1c4c7

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
@@ -3908,51 +3908,54 @@ object Parsers {
39083908
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
39093909
}
39103910

3911-
/** TypeDef ::= id [TypeParamClause] {FunParamClause} TypeBounds [‘=’ Type]
3911+
/** TypeDef ::= id [TypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type]
39123912
*/
39133913
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
39143914
newLinesOpt()
39153915
atSpan(start, nameStart) {
39163916
val nameIdent = typeIdent()
3917+
val tname = nameIdent.name.asTypeName
39173918
val tparams = typeParamClauseOpt(ParamOwner.Type)
39183919
val vparamss = funParamClauses()
3920+
39193921
def makeTypeDef(rhs: Tree): Tree = {
39203922
val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
39213923
val tdef = TypeDef(nameIdent.name.toTypeName, rhs1)
39223924
if (nameIdent.isBackquoted)
39233925
tdef.pushAttachment(Backquoted, ())
39243926
finalizeDef(tdef, mods, start)
39253927
}
3928+
39263929
in.token match {
39273930
case EQUALS =>
39283931
in.nextToken()
39293932
makeTypeDef(toplevelTyp())
39303933
case SUBTYPE | SUPERTYPE =>
3931-
val bounds = typeBounds()
3932-
if (in.token == EQUALS) {
3933-
val eqOffset = in.skipToken()
3934-
var rhs = toplevelTyp()
3935-
rhs match {
3936-
case mtt: MatchTypeTree =>
3937-
bounds match {
3938-
case TypeBoundsTree(EmptyTree, upper, _) =>
3939-
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
3940-
case _ =>
3941-
syntaxError(em"cannot combine lower bound and match type alias", eqOffset)
3942-
}
3943-
case _ =>
3944-
if mods.is(Opaque) then
3945-
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
3946-
else
3947-
syntaxError(em"cannot combine bound and alias", eqOffset)
3948-
}
3949-
makeTypeDef(rhs)
3950-
}
3951-
else makeTypeDef(bounds)
3934+
typeAndCtxBounds(tname) match
3935+
case bounds: TypeBoundsTree if in.token == EQUALS =>
3936+
val eqOffset = in.skipToken()
3937+
var rhs = toplevelTyp()
3938+
rhs match {
3939+
case mtt: MatchTypeTree =>
3940+
bounds match {
3941+
case TypeBoundsTree(EmptyTree, upper, _) =>
3942+
rhs = MatchTypeTree(upper, mtt.selector, mtt.cases)
3943+
case _ =>
3944+
syntaxError(em"cannot combine lower bound and match type alias", eqOffset)
3945+
}
3946+
case _ =>
3947+
if mods.is(Opaque) then
3948+
rhs = TypeBoundsTree(bounds.lo, bounds.hi, rhs)
3949+
else
3950+
syntaxError(em"cannot combine bound and alias", eqOffset)
3951+
}
3952+
makeTypeDef(rhs)
3953+
case bounds => makeTypeDef(bounds)
39523954
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
3953-
makeTypeDef(typeBounds())
3954-
case _ if (staged & StageKind.QuotedPattern) != 0 =>
3955-
makeTypeDef(typeBounds())
3955+
makeTypeDef(typeAndCtxBounds(tname))
3956+
case _ if (staged & StageKind.QuotedPattern) != 0
3957+
|| in.featureEnabled(Feature.modularity) && in.isColon =>
3958+
makeTypeDef(typeAndCtxBounds(tname))
39563959
case _ =>
39573960
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token))
39583961
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
@@ -128,6 +128,7 @@ parsercombinators-givens.scala
128128
parsercombinators-givens-2.scala
129129
parsercombinators-arrow.scala
130130
hylolib-deferred-given
131+
hylolib-cb
131132

132133

133134

docs/_docs/internals/syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ PatDef ::= ids [‘:’ Type] [‘=’ Expr]
450450
DefDef ::= DefSig [‘:’ Type] [‘=’ Expr] DefDef(_, name, paramss, tpe, expr)
451451
| ‘this’ TypelessClauses [DefImplicitClause] ‘=’ ConstrExpr DefDef(_, <init>, vparamss, EmptyTree, expr | Block)
452452
DefSig ::= id [DefParamClauses] [DefImplicitClause]
453-
TypeDef ::= id [TypeParamClause] {FunParamClause} TypeBounds TypeDefTree(_, name, tparams, bound
453+
TypeDef ::= id [TypeParamClause] {FunParamClause} TypeAndCtxBounds TypeDefTree(_, name, tparams, bound
454454
[‘=’ Type]
455455
456456
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)