Skip to content

Commit a36ca95

Browse files
committed
Change syntax of cap parameters and members
`cap` is now a soft modifier and we abolish separate cap lists.
1 parent 8b9ecf4 commit a36ca95

21 files changed

+334
-52
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
236236

237237
case class Tracked()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Tracked)
238238

239+
case class CaptureParam()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.CaptureParam)
240+
239241
/** Used under pureFunctions to mark impure function types `A => B` in `FunctionWithMods` */
240242
case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure)
241243
}
@@ -534,12 +536,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
534536
TypeApply(capsInternalDot(nme.capsOf), tp :: Nil)
535537

536538
// 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))
539+
def makeCapsBound(refsL: List[Tree] = Nil, refsU: List[Tree] = Nil)(using Context): TypeBoundsTree =
540+
val lower = refsL match
541+
case Nil => Select(scalaDot(nme.caps), tpnme.CapSet)
542+
case refsL => makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refsL, tpnme.retains)
543+
val upper =
544+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refsU, if refsU.isEmpty then tpnme.retainsCap else tpnme.retains)
545+
TypeBoundsTree(lower, upper)
543546

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,9 @@ object Flags {
380380
/** Tracked modifier for class parameter / a class with some tracked parameters */
381381
val (Tracked @ _, _, Dependent @ _) = newFlags(46, "tracked")
382382

383+
/** Cap modifier for capture-set parameters and capture-set members */
384+
val (_, _, CaptureParam @ _) = newFlags(47, "cap")
385+
383386
// ------------ Flags following this one are not pickled ----------------------------------
384387

385388
/** Symbol is not a member of its owner */

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ object StdNames {
444444
val bytes: N = "bytes"
445445
val canEqual_ : N = "canEqual"
446446
val canEqualAny : N = "canEqualAny"
447+
val cap: N = "cap"
447448
val caps: N = "caps"
448449
val capsOf: N = "capsOf"
449450
val captureChecking: N = "captureChecking"

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

Lines changed: 159 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import config.SourceVersion.*
3535
import config.SourceVersion
3636
import dotty.tools.dotc.config.MigrationVersion
3737
import dotty.tools.dotc.util.chaining.*
38+
import dotty.tools.dotc.config.Feature.ccEnabled
3839

3940
object Parsers {
4041

@@ -220,6 +221,10 @@ object Parsers {
220221
def isErased = isIdent(nme.erased) && in.erasedEnabled
221222
// Are we seeing an `erased` soft keyword that will not be an identifier?
222223
def isErasedKw = isErased && in.isSoftModifierInParamModifierPosition
224+
// Are we seeing a `cap` soft keyword for declaring a capture-set member or at the beginning a capture-variable parameter list?
225+
def isCapKw = Feature.ccEnabled && isIdent(nme.cap)
226+
// 'cap type' ?
227+
def isCapTypeKw = isCapKw && in.lookahead.token == TYPE
223228
def isSimpleLiteral =
224229
simpleLiteralTokens.contains(in.token)
225230
|| isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token)
@@ -1917,7 +1922,7 @@ object Parsers {
19171922
refinedTypeRest(atSpan(startOffset(t)) {
19181923
RefinedTypeTree(rejectWildcardType(t), refinement(indentOK = true))
19191924
})
1920-
else if Feature.ccEnabled && in.isIdent(nme.UPARROW) && isCaptureUpArrow then
1925+
else if Feature.ccEnabled && in.isIdent(nme.UPARROW) && isCaptureUpArrow then // TODO remove
19211926
atSpan(t.span.start):
19221927
in.nextToken()
19231928
if in.token == LBRACE
@@ -1972,7 +1977,8 @@ object Parsers {
19721977

19731978
def typeBlockStats(): List[Tree] =
19741979
val tdefs = new ListBuffer[Tree]
1975-
while in.token == TYPE do tdefs += typeBlockStat()
1980+
while (in.token == TYPE) do
1981+
tdefs += typeBlockStat()
19761982
tdefs.toList
19771983

19781984
/** TypeBlockStat ::= ‘type’ {nl} TypeDef
@@ -2173,11 +2179,14 @@ object Parsers {
21732179
* NamesAndTypes ::= NameAndType {‘,’ NameAndType}
21742180
* NameAndType ::= id ':' Type
21752181
*/
2176-
def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] =
2177-
def argType() =
2178-
val t = typ()
2182+
def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] = //TOOD grammar doc
2183+
def withWildCard(gen: => Tree) =
2184+
val t = gen
21792185
if wildOK then t else rejectWildcardType(t)
21802186

2187+
def argType() = withWildCard(typ())
2188+
def argOrCapType() = withWildCard(if in.token == LBRACE then concreteCapsType(captureSet()) else typ())
2189+
21812190
def namedArgType() =
21822191
atSpan(in.offset):
21832192
val name = ident()
@@ -2188,14 +2197,14 @@ object Parsers {
21882197
atSpan(in.offset):
21892198
val name = ident()
21902199
acceptColon()
2191-
NamedArg(name, argType())
2200+
NamedArg(name, argType()) // TODO allow capsets here?
21922201

2193-
if namedOK && isIdent && in.lookahead.token == EQUALS then
2194-
commaSeparated(() => namedArgType())
2202+
if namedOK && isIdent && in.lookahead.token == EQUALS then // TOOD support for named cap args
2203+
commaSeparated(() => namedArgType())
21952204
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then
21962205
commaSeparated(() => namedElem())
21972206
else
2198-
commaSeparated(() => argType())
2207+
commaSeparated(() => argOrCapType())
21992208
end argTypes
22002209

22012210
def paramTypeOf(core: () => Tree): Tree =
@@ -2254,7 +2263,7 @@ object Parsers {
22542263
inBraces(refineStatSeq())
22552264

22562265
/** TypeBounds ::= [`>:' Type] [`<:' Type]
2257-
* | `^` -- under captureChecking
2266+
* | `^` -- under captureChecking TODO remove
22582267
*/
22592268
def typeBounds(): TypeBoundsTree =
22602269
atSpan(in.offset):
@@ -2264,10 +2273,29 @@ object Parsers {
22642273
else
22652274
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))
22662275

2276+
/** CaptureSetBounds ::= [`>:' CaptureSetOrRef ] [`<:' CaptureSetOrRef ] --- under captureChecking
2277+
*/
2278+
def captureSetBounds(): TypeBoundsTree =
2279+
atSpan(in.offset):
2280+
TypeBoundsTree(capsBound(SUPERTYPE), capsBound(SUBTYPE))
2281+
22672282
private def bound(tok: Int): Tree =
22682283
if (in.token == tok) { in.nextToken(); toplevelTyp() }
22692284
else EmptyTree
22702285

2286+
private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree =
2287+
if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet
2288+
Select(scalaDot(nme.caps), tpnme.CapSet)
2289+
else
2290+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, if refs.isEmpty then tpnme.retainsCap else tpnme.retains)
2291+
2292+
private def capsBound(tok: Int): Tree =
2293+
if (in.token == tok) then
2294+
in.nextToken()
2295+
capsBound(captureSet(), isLowerBound = tok == SUPERTYPE)
2296+
else
2297+
capsBound(Nil, isLowerBound = tok == SUPERTYPE)
2298+
22712299
/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds]
22722300
*/
22732301
def typeAndCtxBounds(pname: TypeName): Tree = {
@@ -2277,6 +2305,15 @@ object Parsers {
22772305
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
22782306
}
22792307

2308+
/** CaptureSetAndCtxBounds ::= CaptureSetBounds [`:` ContextBounds] -- under captureChecking
2309+
*/
2310+
def captureSetAndCtxBounds(pname: TypeName): Tree = {
2311+
val t = captureSetBounds()
2312+
val cbs = contextBounds(pname)
2313+
if (cbs.isEmpty) t
2314+
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
2315+
}
2316+
22802317
/** ContextBound ::= Type [`as` id] */
22812318
def contextBound(pname: TypeName): Tree =
22822319
val t = toplevelTyp(inContextBound = true)
@@ -2796,7 +2833,10 @@ object Parsers {
27962833
in.nextToken()
27972834
simpleExprRest(selectorOrMatch(t), location, canApply = true)
27982835
case LBRACKET =>
2799-
val tapp = atSpan(startOffset(t), in.offset) { TypeApply(t, typeArgs(namedOK = true, wildOK = false)) }
2836+
val tapp = atSpan(startOffset(t), in.offset) {
2837+
val args = typeArgs(namedOK = true, wildOK = false)
2838+
TypeApply(t, args)
2839+
}
28002840
simpleExprRest(tapp, location, canApply = true)
28012841
case LPAREN | LBRACE | INDENT if canApply =>
28022842
val app = atSpan(startOffset(t), in.offset) { mkApply(t, argumentExprs()) }
@@ -3320,6 +3360,7 @@ object Parsers {
33203360
case nme.tracked => Mod.Tracked()
33213361
case nme.erased if in.erasedEnabled => Mod.Erased()
33223362
case nme.mut if Feature.ccEnabled => Mod.Mut()
3363+
case nme.cap => Mod.CaptureParam()
33233364
}
33243365
}
33253366

@@ -3388,7 +3429,7 @@ object Parsers {
33883429
* | opaque
33893430
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased |
33903431
* inline | transparent | infix |
3391-
* mut -- under cc
3432+
* mut | cap -- under cc
33923433
*/
33933434
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
33943435
@tailrec
@@ -3477,7 +3518,6 @@ object Parsers {
34773518
recur(numLeadParams, firstClause = true, prevIsTypeClause = false)
34783519
end typeOrTermParamClauses
34793520

3480-
34813521
/** ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’
34823522
* ClsTypeParam ::= {Annotation} [‘+’ | ‘-’]
34833523
* id [HkTypeParamClause] TypeAndCtxBounds
@@ -3502,6 +3542,43 @@ object Parsers {
35023542
in.nextToken()
35033543
ok
35043544

3545+
def ensureNoHKParams() = // for cap params
3546+
if in.token == LBRACKET then
3547+
syntaxError(em"'cap' parameters cannot have type parameters")
3548+
in.nextToken()
3549+
3550+
def ensureNoVariance() = // for cap params
3551+
if isIdent(nme.raw.PLUS) || isIdent(nme.raw.MINUS) then
3552+
syntaxError(em"no `+/-` variance annotation allowed here")
3553+
in.nextToken()
3554+
3555+
def typeOrCapParam(): TypeDef =
3556+
if isCapKw then
3557+
in.nextToken()
3558+
capParam()
3559+
else typeParam()
3560+
3561+
def capParam(): TypeDef = {
3562+
val start = in.offset
3563+
var mods = annotsAsMods() | Param
3564+
if paramOwner.isClass then
3565+
mods |= PrivateLocal
3566+
ensureNoVariance() // TODO: in the future, we might want to support variances on capture params, ruled out for now
3567+
atSpan(start, nameStart) {
3568+
val name =
3569+
if paramOwner.acceptsWildcard && in.token == USCORE then
3570+
in.nextToken()
3571+
WildcardParamName.fresh().toTypeName
3572+
else ident().toTypeName
3573+
ensureNoHKParams()
3574+
val bounds =
3575+
if paramOwner.acceptsCtxBounds then captureSetAndCtxBounds(name)
3576+
else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then captureSetAndCtxBounds(name)
3577+
else captureSetBounds()
3578+
TypeDef(name, bounds).withMods(mods)
3579+
}
3580+
}
3581+
35053582
def typeParam(): TypeDef = {
35063583
val start = in.offset
35073584
var mods = annotsAsMods() | Param
@@ -3525,11 +3602,14 @@ object Parsers {
35253602
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
35263603
}
35273604
}
3528-
commaSeparated(() => typeParam())
3605+
commaSeparated(() => typeOrCapParam())
35293606
}
35303607

35313608
def typeParamClauseOpt(paramOwner: ParamOwner): List[TypeDef] =
3532-
if (in.token == LBRACKET) typeParamClause(paramOwner) else Nil
3609+
if (in.token == LBRACKET)
3610+
typeParamClause(paramOwner)
3611+
else
3612+
Nil
35333613

35343614
/** ContextTypes ::= FunArgType {‘,’ FunArgType}
35353615
*/
@@ -3871,25 +3951,29 @@ object Parsers {
38713951
* | var VarDef
38723952
* | def DefDef
38733953
* | type {nl} TypeDef
3954+
* | cap type {nl} CapDef -- under capture checking
38743955
* | TmplDef
38753956
* EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids)
38763957
*/
3877-
def defOrDcl(start: Int, mods: Modifiers): Tree = in.token match {
3878-
case VAL =>
3879-
in.nextToken()
3880-
patDefOrDcl(start, mods)
3881-
case VAR =>
3882-
val mod = atSpan(in.skipToken()) { Mod.Var() }
3883-
val mod1 = addMod(mods, mod)
3884-
patDefOrDcl(start, mod1)
3885-
case DEF =>
3886-
defDefOrDcl(start, in.skipToken(mods))
3887-
case TYPE =>
3888-
typeDefOrDcl(start, in.skipToken(mods))
3889-
case CASE if inEnum =>
3890-
enumCase(start, mods)
3891-
case _ =>
3892-
tmplDef(start, mods)
3958+
def defOrDcl(start: Int, mods: Modifiers): Tree =
3959+
in.token match {
3960+
case VAL =>
3961+
in.nextToken()
3962+
patDefOrDcl(start, mods)
3963+
case VAR =>
3964+
val mod = atSpan(in.skipToken()) { Mod.Var() }
3965+
val mod1 = addMod(mods, mod)
3966+
patDefOrDcl(start, mod1)
3967+
case DEF =>
3968+
defDefOrDcl(start, in.skipToken(mods))
3969+
case TYPE if mods.is(CaptureParam) =>
3970+
capDefOrDcl(start, in.skipToken(mods))
3971+
case TYPE =>
3972+
typeDefOrDcl(start, in.skipToken(mods))
3973+
case CASE if inEnum =>
3974+
enumCase(start, mods)
3975+
case _ =>
3976+
tmplDef(start, mods)
38933977
}
38943978

38953979
/** PatDef ::= ids [‘:’ Type] [‘=’ Expr]
@@ -4098,6 +4182,43 @@ object Parsers {
40984182
}
40994183
}
41004184

4185+
private def concreteCapsType(refs: List[Tree]): Tree =
4186+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains)
4187+
4188+
/** CapDef ::= id CaptureSetAndCtxBounds [‘=’ CaptureSetOrRef] -- under capture checking
4189+
*/
4190+
def capDefOrDcl(start: Offset, mods: Modifiers): Tree =
4191+
newLinesOpt()
4192+
atSpan(start, nameStart) {
4193+
val nameIdent = typeIdent()
4194+
val tname = nameIdent.name.asTypeName
4195+
if in.token == LBRACKET then syntaxError(em"'cap type' declarations cannot have type parameters")
4196+
4197+
def makeCapDef(refs: List[Tree] | Tree): Tree = {
4198+
val tdef = TypeDef(nameIdent.name.toTypeName,
4199+
refs.match
4200+
case refs: List[Tree] => concreteCapsType(refs)
4201+
case bounds: Tree => bounds)
4202+
4203+
if (nameIdent.isBackquoted)
4204+
tdef.pushAttachment(Backquoted, ())
4205+
finalizeDef(tdef, mods, start)
4206+
}
4207+
4208+
in.token.match
4209+
case EQUALS =>
4210+
in.nextToken()
4211+
makeCapDef(captureSet())
4212+
case SUBTYPE | SUPERTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
4213+
makeCapDef(captureSetAndCtxBounds(tname))
4214+
case _ if (staged & StageKind.QuotedPattern) != 0
4215+
|| sourceVersion.enablesNewGivens && in.isColon =>
4216+
makeCapDef(captureSetAndCtxBounds(tname))
4217+
case _ =>
4218+
syntaxErrorOrIncomplete(ExpectedCaptureBoundOrEquals(in.token))
4219+
return EmptyTree // return to avoid setting the span to EmptyTree
4220+
}
4221+
41014222
/** TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
41024223
* | [‘case’] ‘object’ ObjectDef
41034224
* | ‘enum’ EnumDef
@@ -4691,6 +4812,7 @@ object Parsers {
46914812
* | ‘var’ VarDef
46924813
* | ‘def’ DefDef
46934814
* | ‘type’ {nl} TypeDef
4815+
* | ‘cap’ ‘type’ {nl} CapDef -- under capture checking
46944816
* (in reality we admit class defs and vars and filter them out afterwards in `checkLegal`)
46954817
*/
46964818
def refineStatSeq(): List[Tree] = {
@@ -4716,9 +4838,14 @@ object Parsers {
47164838
fail(em"this kind of definition cannot be a refinement")
47174839

47184840
while
4841+
val mods =
4842+
if isCapTypeKw then // allow `cap type` in refinements
4843+
in.nextToken()
4844+
addMod(Modifiers(), Mod.CaptureParam())
4845+
else Modifiers()
47194846
val dclFound = isDclIntro
47204847
if dclFound then
4721-
stats ++= checkLegal(defOrDcl(in.offset, Modifiers()))
4848+
stats ++= checkLegal(defOrDcl(in.offset, mods))
47224849
var what = "declaration"
47234850
if inFunReturnType then what += " (possible cause: missing `=` in front of current method body)"
47244851
statSepOrEnd(stats, noPrevStat = !dclFound, what)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1212,7 +1212,8 @@ object Scanners {
12121212
&& (softModifierNames.contains(name)
12131213
|| name == nme.erased && erasedEnabled
12141214
|| name == nme.tracked && trackedEnabled
1215-
|| name == nme.mut && Feature.ccEnabled)
1215+
|| name == nme.mut && Feature.ccEnabled
1216+
|| name == nme.cap && Feature.ccEnabled)
12161217

12171218
def isSoftModifierInModifierPosition: Boolean =
12181219
isSoftModifier && inModifierPosition()

0 commit comments

Comments
 (0)