Skip to content

Commit aba63cd

Browse files
committed
Allow context bounds in type declarations
Expand them to givens with deferred summons # Conflicts: # compiler/src/dotty/tools/dotc/ast/Desugar.scala
1 parent 73deadc commit aba63cd

File tree

12 files changed

+103
-86
lines changed

12 files changed

+103
-86
lines changed

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

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -226,44 +226,48 @@ object desugar {
226226
private def defDef(meth: DefDef, isPrimaryConstructor: Boolean = false)(using Context): Tree =
227227
addDefaultGetters(elimContextBounds(meth, isPrimaryConstructor))
228228

229+
private def desugarContextBounds(
230+
tname: TypeName, rhs: Tree,
231+
evidenceBuf: ListBuffer[ValDef],
232+
flags: FlagSet,
233+
freshName: => TermName)(using Context): Tree = rhs match
234+
case ContextBounds(tbounds, cxbounds) =>
235+
var useParamName = Feature.enabled(Feature.modularity)
236+
for bound <- cxbounds do
237+
val evidenceName = bound match
238+
case ContextBoundTypeTree(_, _, ownName) if !ownName.isEmpty => ownName
239+
case _ if useParamName => tname.toTermName
240+
case _ => freshName
241+
useParamName = false
242+
val evidenceParam = ValDef(evidenceName, bound, EmptyTree).withFlags(flags)
243+
evidenceParam.pushAttachment(ContextBoundParam, ())
244+
evidenceBuf += evidenceParam
245+
tbounds
246+
case LambdaTypeTree(tparams, body) =>
247+
cpy.LambdaTypeTree(rhs)(tparams,
248+
desugarContextBounds(tname, body, evidenceBuf, flags, freshName))
249+
case _ =>
250+
rhs
251+
end desugarContextBounds
252+
229253
private def elimContextBounds(meth: DefDef, isPrimaryConstructor: Boolean)(using Context): DefDef =
230254
val DefDef(_, paramss, tpt, rhs) = meth
231255
val evidenceParamBuf = ListBuffer[ValDef]()
232256
var seenContextBounds: Int = 0
233-
234-
def desugarContextBounds(tparam: TypeDef, rhs: Tree): Tree = rhs match
235-
case ContextBounds(tbounds, cxbounds) =>
236-
val iflag = if Feature.sourceVersion.isAtLeast(`future`) then Given else Implicit
237-
val flags = if isPrimaryConstructor then iflag | LocalParamAccessor else iflag | Param
238-
var useParamName = Feature.enabled(Feature.modularity)
239-
for bound <- cxbounds do
240-
val paramName =
241-
bound match
242-
case ContextBoundTypeTree(_, _, ownName) if !ownName.isEmpty =>
243-
ownName
244-
case _ if useParamName =>
245-
tparam.name.toTermName
246-
case _ =>
247-
seenContextBounds += 1 // Start at 1 like FreshNameCreator.
248-
ContextBoundParamName(EmptyTermName, seenContextBounds)
249-
// Just like with `makeSyntheticParameter` on nameless parameters of
250-
// using clauses, we only need names that are unique among the
251-
// parameters of the method since shadowing does not affect
252-
// implicit resolution in Scala 3.
253-
useParamName = false
254-
val evidenceParam = ValDef(paramName, bound, EmptyTree).withFlags(flags)
255-
evidenceParam.pushAttachment(ContextBoundParam, ())
256-
evidenceParamBuf += evidenceParam
257-
tbounds
258-
case LambdaTypeTree(tparams, body) =>
259-
cpy.LambdaTypeTree(rhs)(tparams, desugarContextBounds(tparam, body))
260-
case _ =>
261-
rhs
262-
end desugarContextBounds
257+
def freshName =
258+
seenContextBounds += 1 // Start at 1 like FreshNameCreator.
259+
ContextBoundParamName(EmptyTermName, seenContextBounds)
260+
// Just like with `makeSyntheticParameter` on nameless parameters of
261+
// using clauses, we only need names that are unique among the
262+
// parameters of the method since shadowing does not affect
263+
// implicit resolution in Scala 3.
263264

264265
val paramssNoContextBounds =
266+
val iflag = if Feature.sourceVersion.isAtLeast(`future`) then Given else Implicit
267+
val flags = if isPrimaryConstructor then iflag | LocalParamAccessor else iflag | Param
265268
mapParamss(paramss) {
266-
tparam => cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam, tparam.rhs))
269+
tparam => cpy.TypeDef(tparam)(rhs =
270+
desugarContextBounds(tparam.name, tparam.rhs, evidenceParamBuf, flags, freshName))
267271
}(identity)
268272

269273
rhs match
@@ -459,6 +463,13 @@ object desugar {
459463
Apply(fn, params.map(refOfDef))
460464
}
461465

466+
def typeDef(tdef: TypeDef)(using Context): Tree =
467+
val evidenceBuf = new ListBuffer[ValDef]
468+
val result = cpy.TypeDef(tdef)(rhs =
469+
desugarContextBounds(tdef.name, tdef.rhs, evidenceBuf,
470+
(tdef.mods.flags.toTermFlags & AccessFlags) | DeferredGivenFlags, EmptyTermName))
471+
if evidenceBuf.isEmpty then result else Thicket(result :: evidenceBuf.toList)
472+
462473
/** The expansion of a class definition. See inline comments for what is involved */
463474
def classDef(cdef: TypeDef)(using Context): Tree = {
464475
val impl @ Template(constr0, _, self, _) = cdef.rhs: @unchecked
@@ -1409,7 +1420,7 @@ object desugar {
14091420
case tree: TypeDef =>
14101421
if (tree.isClassDef) classDef(tree)
14111422
else if (ctx.mode.isQuotedPattern) quotedPatternTypeDef(tree)
1412-
else tree
1423+
else typeDef(tree)
14131424
case tree: DefDef =>
14141425
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef
14151426
else defDef(tree)

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ object Flags {
570570
val DeferredOrLazyOrMethod: FlagSet = Deferred | Lazy | Method
571571
val DeferredOrTermParamOrAccessor: FlagSet = Deferred | ParamAccessor | TermParam // term symbols without right-hand sides
572572
val DeferredOrTypeParam: FlagSet = Deferred | TypeParam // type symbols without right-hand sides
573+
val DeferredGivenFlags = Deferred | Given | HasDefault
573574
val EnumValue: FlagSet = Enum | StableRealizable // A Scala enum value
574575
val FinalOrInline: FlagSet = Final | Inline
575576
val FinalOrModuleClass: FlagSet = Final | ModuleClass // A module class or a final class

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

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2160,9 +2160,9 @@ object Parsers {
21602160
if (in.token == tok) { in.nextToken(); toplevelTyp() }
21612161
else EmptyTree
21622162

2163-
/** TypeParamBounds ::= TypeBounds {`<%' Type} [`:` ContextBounds]
2163+
/** TypeAndCtxBounds ::= TypeBounds {`<%' Type} [`:` ContextBounds]
21642164
*/
2165-
def typeParamBounds(pname: TypeName): Tree = {
2165+
def typeAndCtxBounds(pname: TypeName): Tree = {
21662166
val t = typeBounds()
21672167
val cbs = contextBounds(pname)
21682168
if (cbs.isEmpty) t
@@ -3378,7 +3378,7 @@ object Parsers {
33783378
}
33793379
else ident().toTypeName
33803380
val hkparams = typeParamClauseOpt(ParamOwner.Type)
3381-
val bounds = if (isAbstractOwner) typeBounds() else typeParamBounds(name)
3381+
val bounds = if (isAbstractOwner) typeBounds() else typeAndCtxBounds(name)
33823382
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
33833383
}
33843384
}
@@ -3889,51 +3889,54 @@ object Parsers {
38893889
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
38903890
}
38913891

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

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2578,7 +2578,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
25782578
val rhs1 = vdef.rhs match {
25792579
case rhs @ Ident(nme.WILDCARD) =>
25802580
rhs.withType(tpt1.tpe)
2581-
case Ident(nme.deferredSummon) if sym.isAllOf(Given | Deferred | HasDefault, butNot = Param) =>
2581+
case Ident(nme.deferredSummon) if sym.isAllOf(DeferredGivenFlags, butNot = Param) =>
25822582
EmptyTree
25832583
case rhs =>
25842584
typedExpr(rhs, tpt1.tpe.widenExpr)
@@ -2848,7 +2848,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
28482848

28492849
val givenImpls =
28502850
cls.thisType.implicitMembers
2851-
.filter(_.symbol.isAllOf(Given | Deferred | HasDefault, butNot = Param))
2851+
.filter(_.symbol.isAllOf(DeferredGivenFlags, butNot = Param))
28522852
.map(givenImpl)
28532853
body ++ givenImpls
28542854
end implementDeferredGivens

docs/_docs/internals/syntax.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ IntoTargetType ::= Type
221221
TypeArgs ::= ‘[’ Types ‘]’ ts
222222
Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>> ds
223223
TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeBoundsTree(lo, hi)
224-
TypeParamBounds ::= TypeBounds [‘:’ ContextBounds] ContextBounds(typeBounds, tps)
224+
TypeAndCtxBounds ::= TypeBounds [‘:’ ContextBounds] ContextBounds(typeBounds, tps)
225225
ContextBounds ::= ContextBound | '{' ContextBound {',' ContextBound} '}'
226226
ContextBound ::= Type ['as' id]
227227
Types ::= Type {‘,’ Type}
@@ -354,7 +354,7 @@ ArgumentPatterns ::= ‘(’ [Patterns] ‘)’
354354
```ebnf
355355
ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’
356356
ClsTypeParam ::= {Annotation} [‘+’ | ‘-’] TypeDef(Modifiers, name, tparams, bounds)
357-
id [HkTypeParamClause] TypeParamBounds Bound(below, above, context)
357+
id [HkTypeParamClause] TypeAndCtxBounds Bound(below, above, context)
358358
359359
TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
360360
TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds
@@ -379,7 +379,7 @@ TypelessClause ::= DefTermParamClause
379379
| UsingParamClause
380380
381381
DefTypeParamClause::= [nl] ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
382-
DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds
382+
DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeAndCtxBounds
383383
DefTermParamClause::= [nl] ‘(’ [DefTermParams] ‘)’
384384
UsingParamClause ::= [nl] ‘(’ ‘using’ (DefTermParams | FunArgTypes) ‘)’
385385
DefImplicitClause ::= [nl] ‘(’ ‘implicit’ DefTermParams ‘)’
@@ -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
@@ -8,11 +8,15 @@ trait A:
88
given Elem is Ord = deferredSummon
99
def foo = summon[Elem is Ord]
1010

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

15-
object Test:
19+
object Test1:
1620
import Inst.given
1721
class C extends A:
1822
type Elem = Int
@@ -21,9 +25,22 @@ object Test:
2125
given A:
2226
type Elem = Int
2327

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

2743

2844

2945

46+

tests/pos/hylolib-extract.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ trait Value[Self]
77
trait Collection[Self]:
88

99
/** The type of the elements in the collection. */
10-
type Element
11-
given elementIsValue: Value[Element] = deferredSummon
10+
type Element: Value
1211

1312
class BitArray
1413

tests/pos/hylolib/AnyCollection.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,7 @@ object AnyCollection {
4545
given anyCollectionIsCollection[T](using tIsValue: Value[T]): Collection[AnyCollection[T]] with {
4646

4747
type Element = T
48-
//given elementIsValue: Value[Element] = tIsValue
49-
5048
type Position = AnyValue
51-
given positionIsValue: Value[Position] = anyValueIsValue
5249

5350
extension (self: AnyCollection[T]) {
5451

tests/pos/hylolib/BitArray.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,7 @@ given bitArrayPositionIsValue: Value[BitArray.Position] with {
338338
given bitArrayIsCollection: Collection[BitArray] with {
339339

340340
type Element = Boolean
341-
//given elementIsValue: Value[Boolean] = booleanIsValue
342-
343341
type Position = BitArray.Position
344-
given positionIsValue: Value[BitArray.Position] = bitArrayPositionIsValue
345342

346343
extension (self: BitArray) {
347344

tests/pos/hylolib/Collection.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ package hylo
55
trait Collection[Self] {
66

77
/** The type of the elements in the collection. */
8-
type Element
9-
given elementIsValue: Value[Element] = deferredSummon
8+
type Element: Value
109

1110
/** The type of a position in the collection. */
12-
type Position
13-
given positionIsValue: Value[Position]
11+
type Position: Value as positionIsValue
1412

1513
extension (self: Self) {
1614

0 commit comments

Comments
 (0)