Skip to content

New Syntax for Capture Variables and Explicit Capture Polymorphism v3 #23063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ object desugar {
.withMods(mods & (GivenOrImplicit | Erased | hasDefault | Tracked) | Param)
}

/** Desugar type def (not param): Under x.moduliity this can expand
/** Desugar type def (not param): Under x.modularity this can expand
* context bounds, which are expanded to evidence ValDefs. These will
* ultimately map to deferred givens.
*/
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ object Trees {

val SyntheticUnit: Property.StickyKey[Unit] = Property.StickyKey()

/** Property key for marking capture-set variables and members */
val CaptureVar: Property.StickyKey[Unit] = Property.StickyKey()

/** Trees take a parameter indicating what the type of their `tpe` field
* is. Two choices: `Type` or `Untyped`.
* Untyped trees have type `Tree[Untyped]`.
Expand Down
8 changes: 0 additions & 8 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -533,14 +533,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def makeCapsOf(tp: RefTree)(using Context): Tree =
TypeApply(capsInternalDot(nme.capsOf), tp :: Nil)

// Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]`
def makeCapsBound()(using Context): TypeBoundsTree =
TypeBoundsTree(
Select(scalaDot(nme.caps), tpnme.CapSet),
makeRetaining(
Select(scalaDot(nme.caps), tpnme.CapSet),
Nil, tpnme.retainsCap))

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

Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ object Mode {
*/
val ImplicitExploration: Mode = newMode(12, "ImplicitExploration")

/** We are currently inside a capture set.
* A term name could be a capture variable, so we need to
* check that it is valid to use as type name.
* Since this mode is only used during annotation typing,
* we can reuse the value of `ImplicitExploration` to save bits.
*/
val InCaptureSet: Mode = ImplicitExploration

/** We are currently unpickling Scala2 info */
val Scala2Unpickling: Mode = newMode(13, "Scala2Unpickling")

Expand Down
146 changes: 95 additions & 51 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ object Parsers {
def isNumericLit = numericLitTokens contains in.token
def isTemplateIntro = templateIntroTokens contains in.token
def isDclIntro = dclIntroTokens contains in.token
def isDclIntroNext = dclIntroTokens contains in.lookahead.token
def isStatSeqEnd = in.isNestedEnd || in.token == EOF || in.token == RPAREN
def mustStartStat = mustStartStatTokens contains in.token

Expand Down Expand Up @@ -1591,8 +1592,7 @@ object Parsers {
case _ => None
}

/** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [`.` rd]
* | [ { SimpleRef `.` } SimpleRef `.` ] id `^`
/** CaptureRef ::= { SimpleRef `.` } SimpleRef [`*`] [`.` `rd`] -- under captureChecking
*/
def captureRef(): Tree =

Expand All @@ -1611,22 +1611,17 @@ object Parsers {
in.nextToken()
derived(reachRef, nme.CC_READONLY)
else reachRef
else if isIdent(nme.UPARROW) then
in.nextToken()
atSpan(startOffset(ref)):
convertToTypeId(ref) match
case ref: RefTree => makeCapsOf(ref)
case ref => ref
else ref

recur(simpleRef())
end captureRef

/** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking
*/
def captureSet(): List[Tree] = inBraces {
if in.token == RBRACE then Nil else commaSeparated(captureRef)
}
def captureSet(): List[Tree] =
inBraces {
if in.token == RBRACE then Nil else commaSeparated(captureRef)
}

def capturesAndResult(core: () => Tree): Tree =
if Feature.ccEnabled && in.token == LBRACE && canStartCaptureSetContentsTokens.contains(in.lookahead.token)
Expand All @@ -1640,9 +1635,9 @@ object Parsers {
* | InfixType
* FunType ::= (MonoFunType | PolyFunType)
* MonoFunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type
* | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions
* | (‘->’ | ‘?->’ ) [CaptureSet] Type -- under pureFunctions and captureChecking
* PolyFunType ::= TypTypeParamClause '=>' Type
* | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions
* | TypTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions and captureChecking
* FunTypeArgs ::= InfixType
* | `(' [ FunArgType {`,' FunArgType } ] `)'
* | '(' [ TypedFunParam {',' TypedFunParam } ')'
Expand Down Expand Up @@ -1885,7 +1880,7 @@ object Parsers {
if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil

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

Expand Down Expand Up @@ -1916,6 +1911,12 @@ object Parsers {
|| !canStartInfixTypeTokens.contains(ahead.token)
|| ahead.lineOffset > 0

inline def gobbleHat(): Boolean =
if Feature.ccEnabled && isIdent(nme.UPARROW) then
in.nextToken()
true
else false

def refinedTypeRest(t: Tree): Tree = {
argumentStart()
if in.isNestedStart then
Expand Down Expand Up @@ -2172,35 +2173,45 @@ object Parsers {
atSpan(startOffset(t), startOffset(id)) { Select(t, id.name) }
}

/** ArgTypes ::= Type {`,' Type}
* | NamedTypeArg {`,' NamedTypeArg}
* NamedTypeArg ::= id `=' Type
/** ArgTypes ::= TypeArg {‘,’ TypeArg}
* | NamedTypeArg {‘,’ NamedTypeArg}
* TypeArg ::= Type
* | CaptureSet -- under captureChecking
* NamedTypeArg ::= id ‘=’ TypeArg
* NamesAndTypes ::= NameAndType {‘,’ NameAndType}
* NameAndType ::= id ':' Type
* NameAndType ::= id ‘:’ Type
*/
def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] =
def argType() =
val t = typ()
def wildCardCheck(gen: Tree): Tree =
val t = gen
if wildOK then t else rejectWildcardType(t)

def namedArgType() =
def argType() = wildCardCheck(typ())

def typeArg() = wildCardCheck:
if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then // is this a capture set and not a refinement type?
// This case is ambiguous w.r.t. an Object literal {}. But since CC is enabled, we probably expect it to designate the empty set
concreteCapsType(captureSet())
else typ()

def namedTypeArg() =
atSpan(in.offset):
val name = ident()
accept(EQUALS)
NamedArg(name.toTypeName, argType())
NamedArg(name.toTypeName, typeArg())

def namedElem() =
def nameAndType() =
atSpan(in.offset):
val name = ident()
acceptColon()
NamedArg(name, argType())

if namedOK && isIdent && in.lookahead.token == EQUALS then
commaSeparated(() => namedArgType())
if namedOK && (isIdent && in.lookahead.token == EQUALS) then
commaSeparated(() => namedTypeArg())
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then
commaSeparated(() => namedElem())
commaSeparated(() => nameAndType())
else
commaSeparated(() => argType())
commaSeparated(() => typeArg())
end argTypes

def paramTypeOf(core: () => Tree): Tree =
Expand Down Expand Up @@ -2244,7 +2255,7 @@ object Parsers {
PostfixOp(t, Ident(tpnme.raw.STAR))
else t

/** TypeArgs ::= `[' Type {`,' Type} `]'
/** TypeArgs ::= `[' TypeArg {`,' TypeArg} `]'
* NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]'
*/
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] =
Expand All @@ -2258,21 +2269,28 @@ object Parsers {
else
inBraces(refineStatSeq())

/** TypeBounds ::= [`>:' Type] [`<:' Type]
* | `^` -- under captureChecking
/** TypeBounds ::= [`>:' TypeBound ] [`<:' TypeBound ]
* TypeBound ::= Type
* | CaptureSet -- under captureChecking
*/
def typeBounds(): TypeBoundsTree =
atSpan(in.offset):
if in.isIdent(nme.UPARROW) && Feature.ccEnabled then
in.nextToken()
makeCapsBound()
else
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))
TypeBoundsTree(bound(SUPERTYPE), bound(SUBTYPE))

private def bound(tok: Int): Tree =
if (in.token == tok) { in.nextToken(); toplevelTyp() }
if in.token == tok then
in.nextToken()
if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then
capsBound(captureSet(), isLowerBound = tok == SUPERTYPE)
else toplevelTyp()
else EmptyTree

private def capsBound(refs: List[Tree], isLowerBound: Boolean = false): Tree =
if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet
Select(scalaDot(nme.caps), tpnme.CapSet)
else
concreteCapsType(refs)

/** TypeAndCtxBounds ::= TypeBounds [`:` ContextBounds]
*/
def typeAndCtxBounds(pname: TypeName): Tree = {
Expand Down Expand Up @@ -3397,7 +3415,7 @@ object Parsers {
* | opaque
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased |
* inline | transparent | infix |
* mut -- under cc
* mut -- under captureChecking
*/
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
@tailrec
Expand Down Expand Up @@ -3486,22 +3504,25 @@ object Parsers {
recur(numLeadParams, firstClause = true, prevIsTypeClause = false)
end typeOrTermParamClauses


/** ClsTypeParamClause::= ‘[’ ClsTypeParam {‘,’ ClsTypeParam} ‘]’
* ClsTypeParam ::= {Annotation} [‘+’ | ‘-’]
* id [HkTypeParamClause] TypeAndCtxBounds
* ClsTypeParam ::= {Annotation} [‘+’ | ‘-’]
* id [HkTypeParamClause] TypeAndCtxBounds
* | {Annotation} [‘+’ | ‘-’] id `^` TypeAndCtxBounds -- under captureChecking
*
* DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’
* DefTypeParam ::= {Annotation}
* id [HkTypeParamClause] TypeAndCtxBounds
* DefTypeParam ::= {Annotation}
* id [HkTypeParamClause] TypeAndCtxBounds
* | {Annotation} id `^` TypeAndCtxBounds -- under captureChecking
*
* TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’
* TypTypeParam ::= {Annotation}
* (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds
* TypTypeParam ::= {Annotation}
* (id | ‘_’) [HkTypeParamClause] TypeAndCtxBounds
* | {Annotation} (id | ‘_’) `^` TypeAndCtxBounds -- under captureChecking
*
* HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’
* HkTypeParam ::= {Annotation} [‘+’ | ‘-’]
* (id | ‘_’) [HkTypeParamClause] TypeBounds
* HkTypeParam ::= {Annotation} [‘+’ | ‘-’]
* (id | ‘_’) [HkTypePamClause] TypeBounds
* | {Annotation} [‘+’ | ‘-’] (id | ‘_’) `^` TypeBounds -- under captureChecking
*/
def typeParamClause(paramOwner: ParamOwner): List[TypeDef] = inBracketsWithCommas {

Expand All @@ -3526,12 +3547,18 @@ object Parsers {
in.nextToken()
WildcardParamName.fresh().toTypeName
else ident().toTypeName
val isCap = gobbleHat()
val hkparams = typeParamClauseOpt(ParamOwner.Hk)
val bounds =
if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name)
else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name)
else typeBounds()
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
val res = TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
if isCap then
res.pushAttachment(CaptureVar, ())
// putting the attachment here as well makes post-processing in the typer easier
bounds.pushAttachment(CaptureVar, ())
res
}
}
commaSeparated(() => typeParam())
Expand Down Expand Up @@ -4052,33 +4079,47 @@ object Parsers {
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
}

/** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ Type]
/** TypeDef ::= id [HkTypeParamClause] {FunParamClause} TypeAndCtxBounds [‘=’ TypeDefRHS ]
* | id `^` TypeAndCtxBounds [‘=’ TypeDefRHS ] -- under captureChecking
* TypeDefRHS ::= Type
* | CaptureSet -- under captureChecking
*/
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {

def typeDefRHS(): Tree =
if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then
concreteCapsType(captureSet())
else toplevelTyp()

newLinesOpt()
atSpan(start, nameStart) {
val nameIdent = typeIdent()
val isCapDef = gobbleHat()
val tname = nameIdent.name.asTypeName
val tparams = typeParamClauseOpt(ParamOwner.Hk)
val vparamss = funParamClauses()

def makeTypeDef(rhs: Tree): Tree = {
val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs)
val tdef = TypeDef(nameIdent.name.toTypeName, rhs1)
if (nameIdent.isBackquoted)
if nameIdent.isBackquoted then
tdef.pushAttachment(Backquoted, ())
if isCapDef then
tdef.pushAttachment(CaptureVar, ())
// putting the attachment here as well makes post-processing in the typer easier
rhs.pushAttachment(CaptureVar, ())
finalizeDef(tdef, mods, start)
}

in.token match {
case EQUALS =>
in.nextToken()
makeTypeDef(toplevelTyp())
makeTypeDef(typeDefRHS())
case SUBTYPE | SUPERTYPE =>
typeAndCtxBounds(tname) match
case bounds: TypeBoundsTree if in.token == EQUALS =>
val eqOffset = in.skipToken()
var rhs = toplevelTyp()
var rhs = typeDefRHS()
rhs match {
case mtt: MatchTypeTree =>
bounds match {
Expand Down Expand Up @@ -4107,6 +4148,9 @@ object Parsers {
}
}

private def concreteCapsType(refs: List[Tree]): Tree =
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains)

/** TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
* | [‘case’] ‘object’ ObjectDef
* | ‘enum’ EnumDef
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ class PlainPrinter(_ctx: Context) extends Printer {
homogenize(tp) match
case tp: TermRef if tp.symbol == defn.captureRoot => "cap"
case tp: SingletonType => toTextRef(tp)
case tp: (TypeRef | TypeParamRef) => toText(tp) ~ "^"
case tp: (TypeRef | TypeParamRef) => toText(tp)
case ReadOnlyCapability(tp1) => toTextCaptureRef(tp1) ~ ".rd"
case ReachCapability(tp1) => toTextCaptureRef(tp1) ~ "*"
case MaybeCapability(tp1) => toTextCaptureRef(tp1) ~ "?"
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import annotation.threadUnsafe
import scala.util.control.NonFatal
import dotty.tools.dotc.inlines.Inlines
import scala.annotation.tailrec
import dotty.tools.dotc.cc.isRetains

object Applications {
import tpd.*
Expand Down Expand Up @@ -1116,7 +1117,9 @@ trait Applications extends Compatibility {
val fun2 = Applications.retypeSignaturePolymorphicFn(fun1, methType)
simpleApply(fun2, proto)
case funRef: TermRef =>
val app = ApplyTo(tree, fun1, funRef, proto, pt)
// println(i"typedApply: $funRef, ${tree.args}, ${funRef.symbol.maybeOwner.isRetains}")
val applyCtx = if funRef.symbol.maybeOwner.isRetains then ctx.addMode(Mode.InCaptureSet) else ctx
val app = ApplyTo(tree, fun1, funRef, proto, pt)(using applyCtx)
convertNewGenericArray(
widenEnumCase(
postProcessByNameArgs(funRef, app).computeNullable(),
Expand Down
Loading
Loading