Skip to content

Commit 57cf1db

Browse files
committed
Make consume a soft modifier
Internally, it is still represented as an annotation
1 parent 426edbd commit 57cf1db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+247
-228
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
559559
def makeOnlyAnnot(qid: Tree)(using Context) =
560560
New(AppliedTypeTree(ref(defn.OnlyCapabilityAnnot.typeRef), qid :: Nil), Nil :: Nil)
561561

562+
def makeConsumeAnnot()(using Context): Tree =
563+
New(ref(defn.ConsumeAnnot.typeRef), Nil :: Nil)
564+
562565
def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef =
563566
DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs)
564567

compiler/src/dotty/tools/dotc/cc/SepCheck.scala

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import Capabilities.*
2222
*
2323
* - Hidden sets of arguments must not be referred to in the same application
2424
* - Hidden sets of (result-) types must not be referred to alter in the same scope.
25-
* - Returned hidden sets can only refer to @consume parameters.
25+
* - Returned hidden sets can only refer to consume parameters.
2626
* - If returned hidden sets refer to an encloding this, the reference must be
27-
* from a @consume method.
27+
* from a consume method.
2828
* - Consumed entities cannot be used subsequently.
2929
* - Entitites cannot be consumed in a loop.
3030
*/
@@ -422,7 +422,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
422422
def consumeError(ref: Capability, loc: SrcPos, pos: SrcPos)(using Context): Unit =
423423
report.error(
424424
em"""Separation failure: Illegal access to $ref, which was passed to a
425-
|@consume parameter or was used as a prefix to a @consume method on line ${loc.line + 1}
425+
|consume parameter or was used as a prefix to a consume method on line ${loc.line + 1}
426426
|and therefore is no longer available.""",
427427
pos)
428428

@@ -433,7 +433,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
433433
def consumeInLoopError(ref: Capability, pos: SrcPos)(using Context): Unit =
434434
report.error(
435435
em"""Separation failure: $ref appears in a loop, therefore it cannot
436-
|be passed to a @consume parameter or be used as a prefix of a @consume method call.""",
436+
|be passed to a consume parameter or be used as a prefix of a consume method call.""",
437437
pos)
438438

439439
// ------------ Checks -----------------------------------------------------
@@ -587,16 +587,16 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
587587

588588
/** Check validity of consumed references `refsToCheck`. The references are consumed
589589
* because they are hidden in a Fresh result type or they are referred
590-
* to in an argument to a @consume parameter or in a prefix of a @consume method --
590+
* to in an argument to a consume parameter or in a prefix of a consume method --
591591
* which one applies is determined by the role parameter.
592592
*
593593
* This entails the following checks:
594594
* - The reference must be defined in the same as method or class as
595595
* the access.
596596
* - If the reference is to a term parameter, that parameter must be
597-
* marked as @consume as well.
597+
* marked as consume as well.
598598
* - If the reference is to a this type of the enclosing class, the
599-
* access must be in a @consume method.
599+
* access must be in a consume method.
600600
*
601601
* References that extend caps.Sharable are excluded from checking.
602602
* As a side effect, add all checked references with the given position `pos`
@@ -631,7 +631,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
631631
then
632632
report.error(
633633
em"""Separation failure: $descr non-local this of class ${ref.cls}.
634-
|The access must be in a @consume method to allow this.""",
634+
|The access must be in a consume method to allow this.""",
635635
pos)
636636
case _ =>
637637

@@ -643,7 +643,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
643643
val (pluralS, singleS) = if badParams.tail.isEmpty then ("", "s") else ("s", "")
644644
report.error(
645645
em"""Separation failure: $descr parameter$pluralS ${paramsStr(badParams.toList)}.
646-
|The parameter$pluralS need$singleS to be annotated with @consume to allow this.""",
646+
|The parameter$pluralS need$singleS to be annotated with consume to allow this.""",
647647
pos)
648648

649649
role match
@@ -769,7 +769,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
769769

770770
/** If `tpe` appears as a (result-) type of a definition, treat its
771771
* hidden set minus its explicitly declared footprint as consumed.
772-
* If `tpe` appears as an argument to a @consume parameter, treat
772+
* If `tpe` appears as an argument to a consume parameter, treat
773773
* its footprint as consumed.
774774
*/
775775
def checkLegalRefs() = role match
@@ -786,7 +786,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
786786
case TypeRole.Argument(arg) =>
787787
if tpe.hasAnnotation(defn.ConsumeAnnot) then
788788
val capts = captures(arg).footprint
789-
checkConsumedRefs(capts, tpe, role, i"argument to @consume parameter with type ${arg.nuType} refers to", pos)
789+
checkConsumedRefs(capts, tpe, role, i"argument to consume parameter with type ${arg.nuType} refers to", pos)
790790
case _ =>
791791

792792
if !tpe.hasAnnotation(defn.UntrackedCapturesAnnot) then
@@ -910,7 +910,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
910910
checkConsumedRefs(
911911
captures(qual).footprint, qual.nuType,
912912
TypeRole.Qualifier(qual, tree.symbol),
913-
i"call prefix of @consume ${tree.symbol} refers to", qual.srcPos)
913+
i"call prefix of consume ${tree.symbol} refers to", qual.srcPos)
914914
case tree: GenericApply =>
915915
traverseChildren(tree)
916916
tree.tpe match

compiler/src/dotty/tools/dotc/cc/Setup.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -758,8 +758,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
758758
&& !(sym.is(Method) && sym.owner.isClass)
759759
then
760760
report.error(
761-
em"""@consume cannot be used here. Only member methods and their term parameters
762-
|can have @consume annotations.""",
761+
em"""consume cannot be used here. Only member methods and their term parameters
762+
|can have a consume modifier.""",
763763
tree.srcPos)
764764
else if annotCls == defn.UseAnnot then
765765
if !ccConfig.allowUse then

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1076,8 +1076,8 @@ class Definitions {
10761076
@tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures")
10771077
@tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.unsafe.untrackedCaptures")
10781078
@tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use")
1079-
@tu lazy val ConsumeAnnot: ClassSymbol = requiredClass("scala.caps.consume")
10801079
@tu lazy val ReserveAnnot: ClassSymbol = requiredClass("scala.caps.reserve")
1080+
@tu lazy val ConsumeAnnot: ClassSymbol = requiredClass("scala.caps.internal.consume")
10811081
@tu lazy val RefineOverrideAnnot: ClassSymbol = requiredClass("scala.caps.internal.refineOverride")
10821082
@tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile")
10831083
@tu lazy val LanguageFeatureMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.languageFeature")

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ object StdNames {
454454
val compiletime : N = "compiletime"
455455
val compose: N = "compose"
456456
val conforms_ : N = "$conforms"
457+
val consume: N = "consume"
457458
val contents: N = "contents"
458459
val copy: N = "copy"
459460
val create: N = "create"

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

Lines changed: 16 additions & 7 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

@@ -216,6 +217,8 @@ object Parsers {
216217
def isPureArrow: Boolean = isPureArrow(nme.PUREARROW) || isPureArrow(nme.PURECTXARROW)
217218
def isErased =
218219
isIdent(nme.erased) && in.erasedEnabled && in.isSoftModifierInParamModifierPosition
220+
def isConsume =
221+
isIdent(nme.consume) && ccEnabled //\&& in.isSoftModifierInParamModifierPosition
219222
def isSimpleLiteral =
220223
simpleLiteralTokens.contains(in.token)
221224
|| isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token)
@@ -3325,11 +3328,15 @@ object Parsers {
33253328
private def addModifier(mods: Modifiers): Modifiers = {
33263329
val tok = in.token
33273330
val name = in.name
3328-
val mod = atSpan(in.skipToken()) { modOfToken(tok, name) }
3329-
3330-
if mods.isOneOf(mod.flags) then
3331-
syntaxError(RepeatedModifier(mod.flags.flagsString, source, mod.span), mod.span)
3332-
addMod(mods, mod)
3331+
if isConsume then
3332+
val consumeAnnot = atSpan(in.skipToken())(makeConsumeAnnot())
3333+
mods.withAddedAnnotation(consumeAnnot)
3334+
else
3335+
val mod = atSpan(in.skipToken()):
3336+
modOfToken(tok, name)
3337+
if mods.isOneOf(mod.flags) then
3338+
syntaxError(RepeatedModifier(mod.flags.flagsString, source, mod.span), mod.span)
3339+
addMod(mods, mod)
33333340
}
33343341

33353342
def addFlag(mods: Modifiers, flag: FlagSet): Modifiers =
@@ -3555,7 +3562,8 @@ object Parsers {
35553562
* UsingParamClause ::= ‘(’ ‘using’ (DefTermParams | ContextTypes) ‘)’
35563563
* DefImplicitClause ::= [nl] ‘(’ ‘implicit’ DefTermParams ‘)’
35573564
* DefTermParams ::= DefTermParam {‘,’ DefTermParam}
3558-
* DefTermParam ::= {Annotation} [‘erased’] [‘inline’] Param
3565+
* DefTermParam ::= {Annotation} TermParamMods Param
3566+
* TermParamMods ::= [‘erased‘] [‘inline’] | [‘consume‘]
35593567
*
35603568
* Param ::= id `:' ParamType [`=' Expr]
35613569
*
@@ -3592,7 +3600,7 @@ object Parsers {
35923600
def param(): ValDef = {
35933601
val start = in.offset
35943602
var mods = impliedMods.withAnnotations(annotations())
3595-
if isErased then
3603+
if isConsume || isErased then
35963604
mods = addModifier(mods)
35973605
if paramOwner.isClass then
35983606
mods = addFlag(modifiers(start = mods), ParamAccessor)
@@ -3664,6 +3672,7 @@ object Parsers {
36643672
var mods = EmptyModifiers
36653673
if in.lookahead.isColon then
36663674
(mods, true)
3675+
else if isConsume then (mods, true)
36673676
else
36683677
if isErased then mods = addModifier(mods)
36693678
val paramsAreNamed =

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1228,7 +1228,8 @@ object Scanners {
12281228
&& (softModifierNames.contains(name)
12291229
|| name == nme.erased && erasedEnabled
12301230
|| name == nme.tracked && trackedEnabled
1231-
|| name == nme.update && Feature.ccEnabled)
1231+
|| name == nme.update && Feature.ccEnabled
1232+
|| name == nme.consume && Feature.ccEnabled)
12321233

12331234
def isSoftModifierInModifierPosition: Boolean =
12341235
isSoftModifier && inModifierPosition()

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import util.SourcePosition
1515
import scala.util.control.NonFatal
1616
import scala.annotation.switch
1717
import config.{Config, Feature}
18-
import ast.tpd
18+
import ast.{tpd, untpd}
1919
import cc.*
2020
import CaptureSet.Mutability
2121
import Capabilities.*
@@ -716,8 +716,12 @@ class PlainPrinter(_ctx: Context) extends Printer {
716716
case _ => literalText(String.valueOf(const.value))
717717
}
718718

719-
/** Usual target for `Annotation#toText`, overridden in RefinedPrinter */
720-
def annotText(annot: Annotation): Text = s"@${annot.symbol.name}"
719+
/** Usual target for `Annotation#toText`, overridden in RefinedPrinter, which also
720+
* looks at trees.
721+
*/
722+
override def annotText(annot: Annotation): Text = annotText(annot.symbol)
723+
724+
protected def annotText(sym: Symbol): Text = s"@${sym.name}"
721725

722726
def toText(annot: Annotation): Text = annot.toText(this)
723727

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
11401140
/** Textual representation of an instance creation expression without the leading `new` */
11411141
protected def constrText(tree: untpd.Tree): Text = toTextLocal(tree).stripPrefix(keywordStr("new ")) // DD
11421142

1143+
override def annotText(annot: Annotation): Text = annotText(annot.symbol, annot.tree)
1144+
11431145
protected def annotText(sym: Symbol, tree: untpd.Tree): Text =
11441146
def recur(t: untpd.Tree): Text = t match
11451147
case Apply(fn, Nil) => recur(fn)
@@ -1152,9 +1154,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
11521154
case New(tpt) => recur(tpt)
11531155
case _ =>
11541156
val annotSym = sym.orElse(tree.symbol.enclosingClass)
1155-
s"@${if annotSym.exists then annotSym.name.toString else t.show}"
1157+
if annotSym.exists then annotText(annotSym) else s"@${t.show}"
11561158
recur(tree)
11571159

1160+
protected override def annotText(sym: Symbol): Text =
1161+
if sym == defn.ConsumeAnnot then "consume" else super.annotText(sym)
1162+
11581163
protected def modText(mods: untpd.Modifiers, sym: Symbol, kw: String, isType: Boolean): Text = { // DD
11591164
val suppressKw = if (enclDefIsClass) mods.isAllOf(LocalParam) else mods.is(Param)
11601165
var flagMask =
@@ -1175,8 +1180,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
11751180
Text(annotTexts, " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw)
11761181
}
11771182

1178-
override def annotText(annot: Annotation): Text = annotText(annot.symbol, annot.tree)
1179-
11801183
def optText(name: Name)(encl: Text => Text): Text =
11811184
if (name.isEmpty) "" else encl(toText(name))
11821185

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,10 @@ class Namer { typer: Typer =>
995995
end if
996996
}
997997

998+
private def normalizeFlags(denot: SymDenotation)(using Context): Unit =
999+
if denot.is(Method) && denot.hasAnnotation(defn.ConsumeAnnot) then
1000+
denot.setFlag(Mutable)
1001+
9981002
/** Intentionally left without `using Context` parameter. We need
9991003
* to pick up the context at the point where the completer was created.
10001004
*/
@@ -1004,6 +1008,7 @@ class Namer { typer: Typer =>
10041008
addInlineInfo(sym)
10051009
denot.info = typeSig(sym)
10061010
invalidateIfClashingSynthetic(denot)
1011+
normalizeFlags(denot)
10071012
Checking.checkWellFormed(sym)
10081013
denot.info = avoidPrivateLeaks(sym)
10091014
}

0 commit comments

Comments
 (0)