Skip to content

Commit ff07e02

Browse files
committed
New capture-vars syntax, "higher-kinds" style
1 parent 412c40a commit ff07e02

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

@@ -1601,7 +1602,7 @@ object Parsers {
16011602
case _ => None
16021603
}
16031604

1604-
/** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`]
1605+
/** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [`.` `rd`] -- under captureChecking
16051606
* | [ { SimpleRef `.` } SimpleRef `.` ] id
16061607
*/
16071608
def captureRef(): Tree =
@@ -1628,9 +1629,13 @@ object Parsers {
16281629

16291630
/** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking
16301631
*/
1631-
def captureSet(): List[Tree] = inBraces {
1632-
if in.token == RBRACE then Nil else commaSeparated(captureRef)
1633-
}
1632+
def captureSet(): List[Tree] =
1633+
if in.token != LBRACE then
1634+
syntaxError(em"expected '{' to start capture set", in.offset)
1635+
Nil
1636+
else inBraces {
1637+
if in.token == RBRACE then Nil else commaSeparated(captureRef)
1638+
}
16341639

16351640
def capturesAndResult(core: () => Tree): Tree =
16361641
if Feature.ccEnabled && in.token == LBRACE && canStartCaptureSetContentsTokens.contains(in.lookahead.token)
@@ -1644,9 +1649,9 @@ object Parsers {
16441649
* | InfixType
16451650
* FunType ::= (MonoFunType | PolyFunType)
16461651
* MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type
1647-
* | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions
1652+
* | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions and captureChecking
16481653
* PolyFunType ::= TypTypeParamClause '=>' Type
1649-
* | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions
1654+
* | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions and captureChecking
16501655
* FunTypeArgs ::= InfixType
16511656
* | `(' [ FunArgType {`,' FunArgType } ] `)'
16521657
* | '(' [ TypedFunParam {',' TypedFunParam } ')'
@@ -1889,7 +1894,7 @@ object Parsers {
18891894
if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil
18901895

18911896
/** InfixType ::= RefinedType {id [nl] RefinedType}
1892-
* | RefinedType `^` // under capture checking
1897+
* | RefinedType `^` -- under captureChecking
18931898
*/
18941899
def infixType(inContextBound: Boolean = false): Tree = infixTypeRest(inContextBound)(refinedType())
18951900

@@ -1920,6 +1925,12 @@ object Parsers {
19201925
|| !canStartInfixTypeTokens.contains(ahead.token)
19211926
|| ahead.lineOffset > 0
19221927

1928+
inline def gobbleHat(): Boolean =
1929+
if Feature.ccEnabled && isIdent(nme.UPARROW) then
1930+
in.nextToken()
1931+
true
1932+
else false
1933+
19231934
def refinedTypeRest(t: Tree): Tree = {
19241935
argumentStart()
19251936
if in.isNestedStart then
@@ -2176,35 +2187,45 @@ object Parsers {
21762187
atSpan(startOffset(t), startOffset(id)) { Select(t, id.name) }
21772188
}
21782189

2179-
/** ArgTypes ::= Type {`,' Type}
2180-
* | NamedTypeArg {`,' NamedTypeArg}
2181-
* NamedTypeArg ::= id `=' Type
2190+
/** ArgTypes ::= TypeArg {‘,’ TypeArg}
2191+
* | NamedTypeArg {‘,’ NamedTypeArg}
2192+
* TypeArg ::= Type
2193+
* | CaptureSet -- under captureChecking
2194+
* NamedTypeArg ::= id ‘=’ TypeArg
21822195
* NamesAndTypes ::= NameAndType {‘,’ NameAndType}
2183-
* NameAndType ::= id ':' Type
2196+
* NameAndType ::= id ‘:’ Type
21842197
*/
21852198
def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] =
2186-
def argType() =
2187-
val t = typ()
2199+
inline def wildCardCheck(inline gen: Tree): Tree =
2200+
val t = gen
21882201
if wildOK then t else rejectWildcardType(t)
21892202

2190-
def namedArgType() =
2203+
def argType() = wildCardCheck(typ())
2204+
2205+
def typeArg() = wildCardCheck:
2206+
if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then // is this a capture set and not a refinement type?
2207+
// This case is ambiguous w.r.t. an Object literal {}. But since CC is enabled, we probably expect it to designate the empty set
2208+
concreteCapsType(captureSet())
2209+
else typ()
2210+
2211+
def namedTypeArg() =
21912212
atSpan(in.offset):
21922213
val name = ident()
21932214
accept(EQUALS)
2194-
NamedArg(name.toTypeName, argType())
2215+
NamedArg(name.toTypeName, typeArg())
21952216

2196-
def namedElem() =
2217+
def nameAndType() =
21972218
atSpan(in.offset):
21982219
val name = ident()
21992220
acceptColon()
22002221
NamedArg(name, argType())
22012222

2202-
if namedOK && isIdent && in.lookahead.token == EQUALS then
2203-
commaSeparated(() => namedArgType())
2223+
if namedOK && (isIdent && in.lookahead.token == EQUALS) then
2224+
commaSeparated(() => namedTypeArg())
22042225
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then
2205-
commaSeparated(() => namedElem())
2226+
commaSeparated(() => nameAndType()) // TODO: can capture-set variables occur here?
22062227
else
2207-
commaSeparated(() => argType())
2228+
commaSeparated(() => typeArg())
22082229
end argTypes
22092230

22102231
def paramTypeOf(core: () => Tree): Tree =
@@ -2248,7 +2269,7 @@ object Parsers {
22482269
PostfixOp(t, Ident(tpnme.raw.STAR))
22492270
else t
22502271

2251-
/** TypeArgs ::= `[' Type {`,' Type} `]'
2272+
/** TypeArgs ::= `[' TypeArg {`,' TypeArg} `]'
22522273
* NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]'
22532274
*/
22542275
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] =
@@ -2262,25 +2283,34 @@ object Parsers {
22622283
else
22632284
inBraces(refineStatSeq())
22642285

2265-
/** TypeBounds ::= [`>:' Type] [`<:' Type]
2266-
* | `^` -- under captureChecking
2286+
/** TypeBounds ::= [`>:' TypeBound ] [`<:' TypeBound ]
2287+
* TypeBound ::= Type
2288+
* | CaptureSet -- under captureChecking
22672289
*/
2268-
def typeBounds(): TypeBoundsTree =
2290+
def typeBounds(isCapParamOrMem: Boolean = false): TypeBoundsTree =
22692291
atSpan(in.offset):
2270-
if in.isIdent(nme.UPARROW) && Feature.ccEnabled then
2271-
in.nextToken()
2272-
makeCapsBound()
2273-
else
2274-
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))
2292+
TypeBoundsTree(bound(SUPERTYPE, isCapParamOrMem), bound(SUBTYPE, isCapParamOrMem))
22752293

2276-
private def bound(tok: Int): Tree =
2277-
if (in.token == tok) { in.nextToken(); toplevelTyp() }
2294+
private def bound(tok: Int, isCapParamOrMem: Boolean = false): Tree =
2295+
if (in.token == tok) then
2296+
in.nextToken()
2297+
if Feature.ccEnabled && (in.token == LBRACE && !isDclIntroNext) then
2298+
capsBound(captureSet(), isLowerBound = tok == SUPERTYPE)
2299+
else toplevelTyp()
2300+
else if Feature.ccEnabled && isCapParamOrMem then
2301+
capsBound(Nil, isLowerBound = tok == SUPERTYPE) // FIXME: should we avoid the CapSet^{} lower bound and make it Nothing?
22782302
else EmptyTree
22792303

2304+
private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree =
2305+
if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet
2306+
Select(scalaDot(nme.caps), tpnme.CapSet)
2307+
else
2308+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, if refs.isEmpty then tpnme.retainsCap else tpnme.retains)
2309+
22802310
/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds]
22812311
*/
2282-
def typeAndCtxBounds(pname: TypeName): Tree = {
2283-
val t = typeBounds()
2312+
def typeAndCtxBounds(pname: TypeName, isCapParamOrMem: Boolean = false): Tree = {
2313+
val t = typeBounds(isCapParamOrMem)
22842314
val cbs = contextBounds(pname)
22852315
if (cbs.isEmpty) t
22862316
else atSpan((t.span union cbs.head.span).start) { ContextBounds(t, cbs) }
@@ -3397,7 +3427,7 @@ object Parsers {
33973427
* | opaque
33983428
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased |
33993429
* inline | transparent | infix |
3400-
* mut -- under cc
3430+
* mut -- under captureChecking
34013431
*/
34023432
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
34033433
@tailrec
@@ -3486,22 +3516,25 @@ object Parsers {
34863516
recur(numLeadParams, firstClause = true, prevIsTypeClause = false)
34873517
end typeOrTermParamClauses
34883518

3489-
34903519
/** ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’
3491-
* ClsTypeParam ::= {Annotation} [‘+’ | ‘-’]
3492-
* id [HkTypeParamClause] TypeAndCtxBounds
3520+
* ClsTypeParam ::= {Annotation} [‘+’ | ‘-’]
3521+
* id [HkTypeParamClause] TypeAndCtxBounds
3522+
* | {Annotation} id [`^`] TypeAndCtxBounds -- under captureChecking
34933523
*
34943524
* DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
3495-
* DefTypeParam ::= {Annotation}
3496-
* id [HkTypeParamClause] TypeAndCtxBounds
3525+
* DefTypeParam ::= {Annotation}
3526+
* id [HkTypeParamClause] TypeAndCtxBounds
3527+
* | {Annotation} id [`^`] TypeAndCtxBounds -- under captureChecking
34973528
*
34983529
* TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
3499-
* TypTypeParam ::= {Annotation}
3500-
* (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds
3530+
* TypTypeParam ::= {Annotation}
3531+
* (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds
3532+
* | {Annotation} (id | ‘_’) [`^`] TypeAndCtxBounds -- under captureChecking
35013533
*
35023534
* HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
3503-
* HkTypeParam ::= {Annotation} [‘+’ | ‘-’]
3504-
* (id | ‘_’) [HkTypeParamClause] TypeBounds
3535+
* HkTypeParam ::= {Annotation} [‘+’ | ‘-’]
3536+
* (id | ‘_’) [HkTypePamClause] TypeBounds
3537+
* | {Annotation} (id | ‘_’) [`^`] TypeBounds -- under captureChecking
35053538
*/
35063539
def typeParamClause(paramOwner: ParamOwner): List[TypeDef] = inBracketsWithCommas {
35073540

@@ -3526,11 +3559,17 @@ object Parsers {
35263559
in.nextToken()
35273560
WildcardParamName.fresh().toTypeName
35283561
else ident().toTypeName
3562+
val isCap = gobbleHat()
3563+
if isCap && mods.isOneOf(Covariant | Contravariant) then
3564+
syntaxError(em"capture parameters cannot have `+/-` variance annotations") // TODO we might want to allow those
3565+
if isCap && in.token == LBRACKET then
3566+
syntaxError(em"capture parameters do not take type parameters")
3567+
in.nextToken()
35293568
val hkparams = typeParamClauseOpt(ParamOwner.Hk)
35303569
val bounds =
3531-
if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name)
3532-
else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name)
3533-
else typeBounds()
3570+
if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name, isCap)
3571+
else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name, isCap)
3572+
else typeBounds(isCap)
35343573
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
35353574
}
35363575
}
@@ -4052,15 +4091,26 @@ object Parsers {
40524091
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
40534092
}
40544093

4055-
/** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type]
4094+
/** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ TypeDefRHS ]
4095+
* | id [`^`] TypeAndCtxBounds [‘=’ TypeDefRHS ] -- under captureChecking
4096+
* TypeDefRHS ::= Type
4097+
* | CaptureSet -- under captureChecking
40564098
*/
4057-
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
4099+
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = { // FIXME: ^-qualified members should automatically receive the CapSet interval!
4100+
4101+
def typeDefRHS(): Tree =
4102+
if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then
4103+
concreteCapsType(captureSet())
4104+
else toplevelTyp()
4105+
40584106
newLinesOpt()
40594107
atSpan(start, nameStart) {
40604108
val nameIdent = typeIdent()
4109+
val isCapDef = gobbleHat()
4110+
if isCapDef && in.token == LBRACKET then syntaxError(em"capture-set member declarations cannot have type parameters")
40614111
val tname = nameIdent.name.asTypeName
4062-
val tparams = typeParamClauseOpt(ParamOwner.Hk)
4063-
val vparamss = funParamClauses()
4112+
val tparams = if !isCapDef then typeParamClauseOpt(ParamOwner.Hk) else Nil
4113+
val vparamss = if !isCapDef then funParamClauses() else Nil
40644114

40654115
def makeTypeDef(rhs: Tree): Tree = {
40664116
val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
@@ -4073,12 +4123,12 @@ object Parsers {
40734123
in.token match {
40744124
case EQUALS =>
40754125
in.nextToken()
4076-
makeTypeDef(toplevelTyp())
4126+
makeTypeDef(typeDefRHS())
40774127
case SUBTYPE | SUPERTYPE =>
4078-
typeAndCtxBounds(tname) match
4128+
typeAndCtxBounds(tname, isCapDef) match
40794129
case bounds: TypeBoundsTree if in.token == EQUALS =>
40804130
val eqOffset = in.skipToken()
4081-
var rhs = toplevelTyp()
4131+
var rhs = typeDefRHS()
40824132
rhs match {
40834133
case mtt: MatchTypeTree =>
40844134
bounds match {
@@ -4096,17 +4146,20 @@ object Parsers {
40964146
makeTypeDef(rhs)
40974147
case bounds => makeTypeDef(bounds)
40984148
case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF =>
4099-
makeTypeDef(typeAndCtxBounds(tname))
4149+
makeTypeDef(typeAndCtxBounds(tname, isCapDef))
41004150
case _ if (staged & StageKind.QuotedPattern) != 0
41014151
|| sourceVersion.enablesNewGivens && in.isColon =>
4102-
makeTypeDef(typeAndCtxBounds(tname))
4152+
makeTypeDef(typeAndCtxBounds(tname, isCapDef))
41034153
case _ =>
41044154
syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token))
41054155
return EmptyTree // return to avoid setting the span to EmptyTree
41064156
}
41074157
}
41084158
}
41094159

4160+
private def concreteCapsType(refs: List[Tree]): Tree =
4161+
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains)
4162+
41104163
/** TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
41114164
* | [‘case’] ‘object’ ObjectDef
41124165
* | ‘enum’ EnumDef

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

+1
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
225225
case FormatInterpolationErrorID // errorNumber: 209
226226
case ValueClassCannotExtendAliasOfAnyValID // errorNumber: 210
227227
case MatchIsNotPartialFunctionID // errorNumber: 211
228+
case ExpectedCaptureBoundOrEqualsID // errorNumber: 212
228229

229230
def errorNumber = ordinal - 1
230231

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)