Skip to content

Commit ff068c6

Browse files
committed
New capture-vars syntax, "higher-kinds" style
1 parent 0667639 commit ff068c6

29 files changed

+227
-153
lines changed

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -534,12 +534,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
534534
TypeApply(capsInternalDot(nme.capsOf), tp :: Nil)
535535

536536
// Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]`
537-
def makeCapsBound()(using Context): TypeBoundsTree =
538-
TypeBoundsTree(
539-
Select(scalaDot(nme.caps), tpnme.CapSet),
540-
makeRetaining(
541-
Select(scalaDot(nme.caps), tpnme.CapSet),
542-
Nil, tpnme.retainsCap))
537+
def makeCapsBound(refsL: List[Tree] = Nil, refsU: List[Tree] = Nil)(using Context): TypeBoundsTree =
538+
val lower = refsL match
539+
case Nil => Select(scalaDot(nme.caps), tpnme.CapSet)
540+
case refsL => makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refsL, tpnme.retains)
541+
val upper =
542+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refsU, if refsU.isEmpty then tpnme.retainsCap else tpnme.retains)
543+
TypeBoundsTree(lower, upper)
543544

544545
def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef =
545546
DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs)

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

+108-55
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ object Parsers {
227227
def isNumericLit = numericLitTokens contains in.token
228228
def isTemplateIntro = templateIntroTokens contains in.token
229229
def isDclIntro = dclIntroTokens contains in.token
230+
def isDclIntroNext = dclIntroTokens contains in.lookahead.token
230231
def isStatSeqEnd = in.isNestedEnd || in.token == EOF || in.token == RPAREN
231232
def mustStartStat = mustStartStatTokens contains in.token
232233

@@ -1591,7 +1592,7 @@ object Parsers {
15911592
case _ => None
15921593
}
15931594

1594-
/** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`]
1595+
/** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [`.` `rd`] -- under captureChecking
15951596
* | [ { SimpleRef `.` } SimpleRef `.` ] id
15961597
*/
15971598
def captureRef(): Tree =
@@ -1618,9 +1619,13 @@ object Parsers {
16181619

16191620
/** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking
16201621
*/
1621-
def captureSet(): List[Tree] = inBraces {
1622-
if in.token == RBRACE then Nil else commaSeparated(captureRef)
1623-
}
1622+
def captureSet(): List[Tree] =
1623+
if in.token != LBRACE then
1624+
syntaxError(em"expected '{' to start capture set", in.offset)
1625+
Nil
1626+
else inBraces {
1627+
if in.token == RBRACE then Nil else commaSeparated(captureRef)
1628+
}
16241629

16251630
def capturesAndResult(core: () => Tree): Tree =
16261631
if Feature.ccEnabled && in.token == LBRACE && canStartCaptureSetContentsTokens.contains(in.lookahead.token)
@@ -1634,9 +1639,9 @@ object Parsers {
16341639
* | InfixType
16351640
* FunType ::= (MonoFunType | PolyFunType)
16361641
* MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type
1637-
* | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions
1642+
* | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions and captureChecking
16381643
* PolyFunType ::= TypTypeParamClause '=>' Type
1639-
* | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions
1644+
* | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions and captureChecking
16401645
* FunTypeArgs ::= InfixType
16411646
* | `(' [ FunArgType {`,' FunArgType } ] `)'
16421647
* | '(' [ TypedFunParam {',' TypedFunParam } ')'
@@ -1879,7 +1884,7 @@ object Parsers {
18791884
if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil
18801885

18811886
/** InfixType ::= RefinedType {id [nl] RefinedType}
1882-
* | RefinedType `^` // under capture checking
1887+
* | RefinedType `^` -- under captureChecking
18831888
*/
18841889
def infixType(inContextBound: Boolean = false): Tree = infixTypeRest(inContextBound)(refinedType())
18851890

@@ -1910,6 +1915,12 @@ object Parsers {
19101915
|| !canStartInfixTypeTokens.contains(ahead.token)
19111916
|| ahead.lineOffset > 0
19121917

1918+
inline def gobbleHat(): Boolean =
1919+
if Feature.ccEnabled && isIdent(nme.UPARROW) then
1920+
in.nextToken()
1921+
true
1922+
else false
1923+
19131924
def refinedTypeRest(t: Tree): Tree = {
19141925
argumentStart()
19151926
if in.isNestedStart then
@@ -2166,35 +2177,45 @@ object Parsers {
21662177
atSpan(startOffset(t), startOffset(id)) { Select(t, id.name) }
21672178
}
21682179

2169-
/** ArgTypes ::= Type {`,' Type}
2170-
* | NamedTypeArg {`,' NamedTypeArg}
2171-
* NamedTypeArg ::= id `=' Type
2180+
/** ArgTypes ::= TypeArg {‘,’ TypeArg}
2181+
* | NamedTypeArg {‘,’ NamedTypeArg}
2182+
* TypeArg ::= Type
2183+
* | CaptureSet -- under captureChecking
2184+
* NamedTypeArg ::= id ‘=’ TypeArg
21722185
* NamesAndTypes ::= NameAndType {‘,’ NameAndType}
2173-
* NameAndType ::= id ':' Type
2186+
* NameAndType ::= id ‘:’ Type
21742187
*/
21752188
def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] =
2176-
def argType() =
2177-
val t = typ()
2189+
inline def wildCardCheck(inline gen: Tree): Tree =
2190+
val t = gen
21782191
if wildOK then t else rejectWildcardType(t)
21792192

2180-
def namedArgType() =
2193+
def argType() = wildCardCheck(typ())
2194+
2195+
def typeArg() = wildCardCheck:
2196+
if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then // is this a capture set and not a refinement type?
2197+
// This case is ambiguous w.r.t. an Object literal {}. But since CC is enabled, we probably expect it to designate the empty set
2198+
concreteCapsType(captureSet())
2199+
else typ()
2200+
2201+
def namedTypeArg() =
21812202
atSpan(in.offset):
21822203
val name = ident()
21832204
accept(EQUALS)
2184-
NamedArg(name.toTypeName, argType())
2205+
NamedArg(name.toTypeName, typeArg())
21852206

2186-
def namedElem() =
2207+
def nameAndType() =
21872208
atSpan(in.offset):
21882209
val name = ident()
21892210
acceptColon()
21902211
NamedArg(name, argType())
21912212

2192-
if namedOK && isIdent && in.lookahead.token == EQUALS then
2193-
commaSeparated(() => namedArgType())
2213+
if namedOK && (isIdent && in.lookahead.token == EQUALS) then
2214+
commaSeparated(() => namedTypeArg())
21942215
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then
2195-
commaSeparated(() => namedElem())
2216+
commaSeparated(() => nameAndType()) // TODO: can capture-set variables occur here?
21962217
else
2197-
commaSeparated(() => argType())
2218+
commaSeparated(() => typeArg())
21982219
end argTypes
21992220

22002221
def paramTypeOf(core: () => Tree): Tree =
@@ -2238,7 +2259,7 @@ object Parsers {
22382259
PostfixOp(t, Ident(tpnme.raw.STAR))
22392260
else t
22402261

2241-
/** TypeArgs ::= `[' Type {`,' Type} `]'
2262+
/** TypeArgs ::= `[' TypeArg {`,' TypeArg} `]'
22422263
* NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]'
22432264
*/
22442265
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] =
@@ -2252,25 +2273,34 @@ object Parsers {
22522273
else
22532274
inBraces(refineStatSeq())
22542275

2255-
/** TypeBounds ::= [`>:' Type] [`<:' Type]
2256-
* | `^` -- under captureChecking
2276+
/** TypeBounds ::= [`>:' TypeBound ] [`<:' TypeBound ]
2277+
* TypeBound ::= Type
2278+
* | CaptureSet -- under captureChecking
22572279
*/
2258-
def typeBounds(): TypeBoundsTree =
2280+
def typeBounds(isCapParamOrMem: Boolean = false): TypeBoundsTree =
22592281
atSpan(in.offset):
2260-
if in.isIdent(nme.UPARROW) && Feature.ccEnabled then
2261-
in.nextToken()
2262-
makeCapsBound()
2263-
else
2264-
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))
2282+
TypeBoundsTree(bound(SUPERTYPE, isCapParamOrMem), bound(SUBTYPE, isCapParamOrMem))
22652283

2266-
private def bound(tok: Int): Tree =
2267-
if (in.token == tok) { in.nextToken(); toplevelTyp() }
2284+
private def bound(tok: Int, isCapParamOrMem: Boolean = false): Tree =
2285+
if (in.token == tok) then
2286+
in.nextToken()
2287+
if Feature.ccEnabled && (in.token == LBRACE && !isDclIntroNext) then
2288+
capsBound(captureSet(), isLowerBound = tok == SUPERTYPE)
2289+
else toplevelTyp()
2290+
else if Feature.ccEnabled && isCapParamOrMem then
2291+
capsBound(Nil, isLowerBound = tok == SUPERTYPE) // FIXME: should we avoid the CapSet^{} lower bound and make it Nothing?
22682292
else EmptyTree
22692293

2294+
private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree =
2295+
if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet
2296+
Select(scalaDot(nme.caps), tpnme.CapSet)
2297+
else
2298+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, if refs.isEmpty then tpnme.retainsCap else tpnme.retains)
2299+
22702300
/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds]
22712301
*/
2272-
def typeAndCtxBounds(pname: TypeName): Tree = {
2273-
val t = typeBounds()
2302+
def typeAndCtxBounds(pname: TypeName, isCapParamOrMem: Boolean = false): Tree = {
2303+
val t = typeBounds(isCapParamOrMem)
22742304
val cbs = contextBounds(pname)
22752305
if (cbs.isEmpty) t
22762306
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
@@ -3387,7 +3417,7 @@ object Parsers {
33873417
* | opaque
33883418
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased |
33893419
* inline | transparent | infix |
3390-
* mut -- under cc
3420+
* mut -- under captureChecking
33913421
*/
33923422
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
33933423
@tailrec
@@ -3476,22 +3506,25 @@ object Parsers {
34763506
recur(numLeadParams, firstClause = true, prevIsTypeClause = false)
34773507
end typeOrTermParamClauses
34783508

3479-
34803509
/** ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’
3481-
* ClsTypeParam ::= {Annotation} [‘+’ | ‘-’]
3482-
* id [HkTypeParamClause] TypeAndCtxBounds
3510+
* ClsTypeParam ::= {Annotation} [‘+’ | ‘-’]
3511+
* id [HkTypeParamClause] TypeAndCtxBounds
3512+
* | {Annotation} id [`^`] TypeAndCtxBounds -- under captureChecking
34833513
*
34843514
* DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
3485-
* DefTypeParam ::= {Annotation}
3486-
* id [HkTypeParamClause] TypeAndCtxBounds
3515+
* DefTypeParam ::= {Annotation}
3516+
* id [HkTypeParamClause] TypeAndCtxBounds
3517+
* | {Annotation} id [`^`] TypeAndCtxBounds -- under captureChecking
34873518
*
34883519
* TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
3489-
* TypTypeParam ::= {Annotation}
3490-
* (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds
3520+
* TypTypeParam ::= {Annotation}
3521+
* (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds
3522+
* | {Annotation} (id | ‘_’) [`^`] TypeAndCtxBounds -- under captureChecking
34913523
*
34923524
* HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
3493-
* HkTypeParam ::= {Annotation} [‘+’ | ‘-’]
3494-
* (id | ‘_’) [HkTypeParamClause] TypeBounds
3525+
* HkTypeParam ::= {Annotation} [‘+’ | ‘-’]
3526+
* (id | ‘_’) [HkTypePamClause] TypeBounds
3527+
* | {Annotation} (id | ‘_’) [`^`] TypeBounds -- under captureChecking
34953528
*/
34963529
def typeParamClause(paramOwner: ParamOwner): List[TypeDef] = inBracketsWithCommas {
34973530

@@ -3516,11 +3549,17 @@ object Parsers {
35163549
in.nextToken()
35173550
WildcardParamName.fresh().toTypeName
35183551
else ident().toTypeName
3552+
val isCap = gobbleHat()
3553+
if isCap && mods.isOneOf(Covariant | Contravariant) then
3554+
syntaxError(em"capture parameters cannot have `+/-` variance annotations") // TODO we might want to allow those
3555+
if isCap && in.token == LBRACKET then
3556+
syntaxError(em"capture parameters do not take type parameters")
3557+
in.nextToken()
35193558
val hkparams = typeParamClauseOpt(ParamOwner.Hk)
35203559
val bounds =
3521-
if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name)
3522-
else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name)
3523-
else typeBounds()
3560+
if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name, isCap)
3561+
else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name, isCap)
3562+
else typeBounds(isCap)
35243563
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
35253564
}
35263565
}
@@ -4042,15 +4081,26 @@ object Parsers {
40424081
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
40434082
}
40444083

4045-
/** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type]
4084+
/** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ TypeDefRHS ]
4085+
* | id [`^`] TypeAndCtxBounds [‘=’ TypeDefRHS ] -- under captureChecking
4086+
* TypeDefRHS ::= Type
4087+
* | CaptureSet -- under captureChecking
40464088
*/
4047-
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
4089+
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = { // FIXME: ^-qualified members should automatically receive the CapSet interval!
4090+
4091+
def typeDefRHS(): Tree =
4092+
if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then
4093+
concreteCapsType(captureSet())
4094+
else toplevelTyp()
4095+
40484096
newLinesOpt()
40494097
atSpan(start, nameStart) {
40504098
val nameIdent = typeIdent()
4099+
val isCapDef = gobbleHat()
4100+
if isCapDef && in.token == LBRACKET then syntaxError(em"capture-set member declarations cannot have type parameters")
40514101
val tname = nameIdent.name.asTypeName
4052-
val tparams = typeParamClauseOpt(ParamOwner.Hk)
4053-
val vparamss = funParamClauses()
4102+
val tparams = if !isCapDef then typeParamClauseOpt(ParamOwner.Hk) else Nil
4103+
val vparamss = if !isCapDef then funParamClauses() else Nil
40544104

40554105
def makeTypeDef(rhs: Tree): Tree = {
40564106
val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
@@ -4063,12 +4113,12 @@ object Parsers {
40634113
in.token match {
40644114
case EQUALS =>
40654115
in.nextToken()
4066-
makeTypeDef(toplevelTyp())
4116+
makeTypeDef(typeDefRHS())
40674117
case SUBTYPE | SUPERTYPE =>
4068-
typeAndCtxBounds(tname) match
4118+
typeAndCtxBounds(tname, isCapDef) match
40694119
case bounds: TypeBoundsTree if in.token == EQUALS =>
40704120
val eqOffset = in.skipToken()
4071-
var rhs = toplevelTyp()
4121+
var rhs = typeDefRHS()
40724122
rhs match {
40734123
case mtt: MatchTypeTree =>
40744124
bounds match {
@@ -4086,17 +4136,20 @@ object Parsers {
40864136
makeTypeDef(rhs)
40874137
case bounds => makeTypeDef(bounds)
40884138
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
4089-
makeTypeDef(typeAndCtxBounds(tname))
4139+
makeTypeDef(typeAndCtxBounds(tname, isCapDef))
40904140
case _ if (staged & StageKind.QuotedPattern) != 0
40914141
|| sourceVersion.enablesNewGivens && in.isColon =>
4092-
makeTypeDef(typeAndCtxBounds(tname))
4142+
makeTypeDef(typeAndCtxBounds(tname, isCapDef))
40934143
case _ =>
40944144
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token))
40954145
return EmptyTree // return to avoid setting the span to EmptyTree
40964146
}
40974147
}
40984148
}
40994149

4150+
private def concreteCapsType(refs: List[Tree]): Tree =
4151+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains)
4152+
41004153
/** TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
41014154
* | [‘case’] ‘object’ ObjectDef
41024155
* | ‘enum’ EnumDef

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

+1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
227227
case MatchIsNotPartialFunctionID // errorNumber: 211
228228
case OnlyFullyDependentAppliedConstructorTypeID // errorNumber: 212
229229
case PointlessAppliedConstructorTypeID // errorNumber: 213
230+
case ExpectedCaptureBoundOrEqualsID // errorNumber: 214
230231

231232
def errorNumber = ordinal - 1
232233

compiler/src/dotty/tools/dotc/reporting/messages.scala

+17
Original file line numberDiff line numberDiff line change
@@ -1923,6 +1923,23 @@ class ExpectedTypeBoundOrEquals(found: Token)(using Context)
19231923
|"""
19241924
}
19251925

1926+
class ExpectedCaptureBoundOrEquals(found: Token)(using Context)
1927+
extends SyntaxMsg(ExpectedCaptureBoundOrEqualsID) {
1928+
def msg(using Context) = i"${hl("=")}, ${hl(">:")}, or ${hl("<:")} expected, but ${Tokens.showToken(found)} found"
1929+
1930+
def explain(using Context) =
1931+
i"""Capture parameters and abstract captures may be constrained by a capture bound.
1932+
|Such capture bounds limit the concrete values of the capture variables and possibly
1933+
|reveal more information about the members of such captures.
1934+
|
1935+
|A lower type bound ${hl("B >: A")} expresses that the capture variable ${hl("B")}
1936+
|refers to a super capture of capture ${hl("A")}.
1937+
|
1938+
|An upper capture bound ${hl("T <: A")} declares that capture variable ${hl("T")}
1939+
|refers to a subcapture of ${hl("A")}.
1940+
|"""
1941+
}
1942+
19261943
class ClassAndCompanionNameClash(cls: Symbol, other: Symbol)(using Context)
19271944
extends NamingMsg(ClassAndCompanionNameClashID) {
19281945
def msg(using Context) =

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

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import annotation.threadUnsafe
3737
import scala.util.control.NonFatal
3838
import dotty.tools.dotc.inlines.Inlines
3939
import scala.annotation.tailrec
40+
import dotty.tools.dotc.cc.isRetains
4041

4142
object Applications {
4243
import tpd.*

0 commit comments

Comments
 (0)