Skip to content

Commit 53f1117

Browse files
committed
Represent all retains annotations as CompactAnnotations
1 parent 0ed90d5 commit 53f1117

File tree

11 files changed

+88
-86
lines changed

11 files changed

+88
-86
lines changed

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Annotations.Annotation
1414
import CaptureSet.VarState
1515
import Capabilities.*
1616
import Mutability.isStatefulType
17-
import StdNames.nme
17+
import StdNames.{nme, tpnme}
1818
import config.Feature
1919
import NameKinds.TryOwnerName
2020
import typer.ProtoTypes.WildcardSelectionProto
@@ -545,13 +545,23 @@ extension (cls: ClassSymbol)
545545

546546
extension (sym: Symbol)
547547

548-
/** This symbol is one of `retains` or `retainsCap` */
548+
private def inScalaAnnotation(using Context): Boolean =
549+
sym.maybeOwner.name == tpnme.annotation
550+
&& sym.owner.owner == defn.ScalaPackageClass
551+
552+
/** Is this symbol one of `retains` or `retainsCap`?
553+
* Try to avoid cycles by not forcing definition symbols except scala package.
554+
*/
549555
def isRetains(using Context): Boolean =
550-
sym == defn.RetainsAnnot || sym == defn.RetainsCapAnnot
556+
(sym.name == tpnme.retains || sym.name == tpnme.retainsCap)
557+
&& inScalaAnnotation
551558

552-
/** This symbol is one of `retains`, `retainsCap`, or`retainsByName` */
559+
/** Is this symbol one of `retains`, `retainsCap`, or`retainsByName`?
560+
* Try to avoid cycles by not forcing definition symbols except scala package.
561+
*/
553562
def isRetainsLike(using Context): Boolean =
554-
isRetains || sym == defn.RetainsByNameAnnot
563+
(sym.name == tpnme.retains || sym.name == tpnme.retainsCap || sym.name == tpnme.retainsByName)
564+
&& inScalaAnnotation
555565

556566
/** A class is pure if:
557567
* - one its base types has an explicitly declared self type with an empty capture set
@@ -658,7 +668,8 @@ class PathSelectionProto(val select: Select, val pt: Type) extends typer.ProtoTy
658668
def selector(using Context): Symbol = select.symbol
659669

660670
/** Drop retains annotations in the inferred type if CC is not enabled
661-
* or transform them into RetainingTypes if CC is enabled.
671+
* or transform them into RetainingTypes with Nothing as argument if CC is enabled
672+
* (we need to do that to keep by-name status).
662673
*/
663674
class CleanupRetains(using Context) extends TypeMap:
664675
def apply(tp: Type): Type = tp match

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -536,10 +536,6 @@ sealed abstract class CaptureSet extends Showable:
536536
/** More info enabled by -Y flags */
537537
def optionalInfo(using Context): String = ""
538538

539-
/** A regular @retains or @retainsByName annotation with the elements of this set as arguments. */
540-
def toRegularAnnotation(cls: Symbol)(using Context): Annotation =
541-
Annotation(CaptureAnnotation(this, boxed = false)(cls).tree)
542-
543539
override def toText(printer: Printer): Text =
544540
printer.toTextCaptureSet(this) ~~ description
545541

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package cc
55
import core.*
66
import Types.*, Symbols.*, Contexts.*
77
import ast.tpd.*
8-
import Annotations.Annotation
8+
import Annotations.CompactAnnotation
99
import Decorators.i
1010

1111
/** A builder and extractor for annotated types with @retains or @retainsByName annotations
@@ -15,8 +15,7 @@ object RetainingType:
1515

1616
def apply(tp: Type, typeElems: Type, byName: Boolean = false)(using Context): Type =
1717
val annotCls = if byName then defn.RetainsByNameAnnot else defn.RetainsAnnot
18-
val annotTree = New(AppliedType(annotCls.typeRef, typeElems :: Nil), Nil)
19-
AnnotatedType(tp, Annotation(annotTree))
18+
AnnotatedType(tp, CompactAnnotation(annotCls.typeRef.appliedTo(typeElems)))
2019

2120
def unapply(tp: AnnotatedType)(using Context): Option[(Type, Type)] =
2221
val sym = tp.annot.symbol

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ object ccConfig:
5959

6060
/** Not used currently. Handy for trying out new features */
6161
def newScheme(using ctx: Context): Boolean =
62-
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.9`)
62+
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`)
6363

6464
def allowUse(using Context): Boolean =
6565
Feature.sourceVersion.stable.isAtMost(SourceVersion.`3.7`)

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

Lines changed: 44 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -66,32 +66,8 @@ object Annotations {
6666
def mapWith(tm: TypeMap)(using Context): Annotation =
6767
tpd.allArguments(tree) match
6868
case Nil => this
69-
7069
case arg :: Nil if symbol.isRetainsLike =>
71-
// Use a more efficient scheme to map retains and retainsByName annotations:
72-
// 1. Map the type argument to a simple TypeTree instead of tree-mapping
73-
// the original tree. TODO Try to use this scheme for other annotations that
74-
// take only type arguments as well. We should wait until after 3.9 LTS to
75-
// do this, though.
76-
// 2. Sanitize the arguments to prevent compilation time blowup.
77-
// 3. Drop the annotation entirely if CC is not enabled somewhere.
78-
79-
def rebuild(tree: Tree, mappedType: Type): Tree = tree match
80-
case Apply(fn, Nil) => cpy.Apply(tree)(rebuild(fn, mappedType), Nil)
81-
case TypeApply(fn, arg :: Nil) => cpy.TypeApply(tree)(fn, TypeTree(mappedType) :: Nil)
82-
case Block(Nil, expr) => rebuild(expr, mappedType)
83-
84-
if !Feature.ccEnabledSomewhere then
85-
EmptyAnnotation // strip retains-like annotations unless capture checking is enabled
86-
else
87-
val mappedType = sanitize(tm(arg.tpe))
88-
if mappedType `eql` arg.tpe then
89-
this
90-
else if cc.ccConfig.newScheme then
91-
CompactAnnotation(symbol.typeRef.appliedTo(mappedType))
92-
else
93-
derivedAnnotation(rebuild(tree, mappedType))
94-
70+
assert(false, s"unexpected symbol $symbol for ConcreteAnnotation $this in ${ctx.source}, this should be a CompactAnnotation")
9571
case args =>
9672
// Checks if `tm` would result in any change by applying it to types
9773
// inside the annotations' arguments and checking if the resulting types
@@ -184,17 +160,35 @@ object Annotations {
184160
case ConstantType(c) => Some(c)
185161
case _ => None
186162

163+
/** Sanitize @retains arguments to approximate illegal types that could cause a compilation
164+
* time blowup before they are dropped ot detected. This means mapping all all skolems
165+
* (?n: T) to (?n: Any), and mapping all recursive captures that are not on CapSet to `^`.
166+
* Skolems and capturing types on types other than CapSet are not allowed in a
167+
* @retains annotation anyway, so the underlying type does not matter as long as it is also
168+
* illegal. See i24556.scala and i24556a.scala.
169+
*/
170+
private def sanitize(tp: Type)(using Context): Type = tp match
171+
case SkolemType(_) =>
172+
SkolemType(defn.AnyType)
173+
case tp @ AnnotatedType(parent, ann)
174+
if ann.symbol.isRetainsLike && parent.typeSymbol != defn.Caps_CapSet =>
175+
tp.derivedAnnotatedType(parent, ann.derivedClassAnnotation(defn.RetainsCapAnnot))
176+
case tp @ OrType(tp1, tp2) =>
177+
tp.derivedOrType(sanitize(tp1), sanitize(tp2))
178+
case _ =>
179+
tp
180+
187181
override def mapWith(tm: TypeMap)(using Context): Annotation =
188-
def derived(tp: Type) =
189-
if tm.isRange(tp) then EmptyAnnotation else derivedAnnotation(tp)
190-
def sanitizeArg(tp: Type) = tp match
191-
case tp @ AppliedType(tycon, args) =>
192-
tp.derivedAppliedType(tycon, args.mapConserve(sanitize))
182+
val isRetains = symbol.isRetainsLike
183+
if isRetains && !Feature.ccEnabledSomewhere then EmptyAnnotation
184+
else tm(tp) match
185+
case tp1 @ AppliedType(tycon, args) =>
186+
val args1 = if isRetains then args.mapConserve(sanitize) else args
187+
derivedAnnotation(tp1.derivedAppliedType(tycon, args1))
188+
case tp1: TypeRef =>
189+
derivedAnnotation(tp1)
193190
case _ =>
194-
tp
195-
if !symbol.isRetainsLike then derived(tm(tp))
196-
else if Feature.ccEnabledSomewhere then derived(sanitizeArg(tm(tp)))
197-
else EmptyAnnotation // strip retains-like annotations unless capture checking is enabled
191+
EmptyAnnotation
198192

199193
override def refersToParamOf(tl: TermLambda)(using Context): Boolean =
200194
refersToLambdaParam(tp, tl)
@@ -203,25 +197,13 @@ object Annotations {
203197
override def eql(that: Annotation) = that match
204198
case that: CompactAnnotation => this.tp `eql` that.tp
205199
case _ => false
206-
end CompactAnnotation
207200

208-
/** Sanitize @retains arguments to approximate illegal types that could cause a compilation
209-
* time blowup before they are dropped ot detected. This means mapping all all skolems
210-
* (?n: T) to (?n: Any), and mapping all recursive captures that are not on CapSet to `^`.
211-
* Skolems and capturing types on types other than CapSet are not allowed in a
212-
* @retains annotation anyway, so the underlying type does not matter as long as it is also
213-
* illegal. See i24556.scala and i24556a.scala.
214-
*/
215-
private def sanitize(tp: Type)(using Context): Type = tp match
216-
case SkolemType(_) =>
217-
SkolemType(defn.AnyType)
218-
case tp @ AnnotatedType(parent, ann)
219-
if ann.symbol.isRetainsLike && parent.typeSymbol != defn.Caps_CapSet =>
220-
tp.derivedAnnotatedType(parent, ann.derivedClassAnnotation(defn.RetainsCapAnnot))
221-
case tp @ OrType(tp1, tp2) =>
222-
tp.derivedOrType(sanitize(tp1), sanitize(tp2))
223-
case _ =>
224-
tp
201+
object CompactAnnotation:
202+
def apply(tree: Tree)(using Context): CompactAnnotation =
203+
val argTypes = tpd.allArguments(tree).map(_.tpe)
204+
apply(annotClass(tree).typeRef.appliedTo(argTypes))
205+
206+
end CompactAnnotation
225207

226208
private def isLambdaParam(t: Type, tl: TermLambda): Boolean = t match
227209
case TermParamRef(tl1, _) => tl eq tl1
@@ -311,9 +293,12 @@ object Annotations {
311293

312294
object Annotation {
313295

314-
def apply(tree: Tree): Annotation = tree match
315-
case tree: TypeTree => CompactAnnotation(tree.tpe)
316-
case _ => ConcreteAnnotation(tree)
296+
def apply(tree: Tree)(using Context): Annotation = tree match
297+
case tree: TypeTree =>
298+
CompactAnnotation(tree.tpe)
299+
case _ =>
300+
if annotClass(tree).isRetainsLike then CompactAnnotation(tree)
301+
else ConcreteAnnotation(tree)
317302

318303
def apply(cls: ClassSymbol, span: Span)(using Context): Annotation =
319304
apply(cls, Nil, span)
@@ -328,7 +313,9 @@ object Annotations {
328313
apply(atp, arg :: Nil, span)
329314

330315
def apply(atp: Type, args: List[Tree], span: Span)(using Context): Annotation =
331-
apply(New(atp, args).withSpan(span))
316+
if atp.typeSymbol.isRetainsLike && args.isEmpty
317+
then CompactAnnotation(atp)
318+
else apply(New(atp, args).withSpan(span))
332319

333320
/** Create an annotation where the tree is computed lazily. */
334321
def deferred(sym: Symbol)(treeFn: Context ?=> Tree): Annotation =
@@ -370,7 +357,7 @@ object Annotations {
370357
* to indicate that the resulting typemap should drop the annotation
371358
* (in derivedAnnotatedType).
372359
*/
373-
@sharable val EmptyAnnotation = Annotation(EmptyTree)
360+
@sharable val EmptyAnnotation = ConcreteAnnotation(EmptyTree)
374361

375362
def ThrowsAnnotation(cls: ClassSymbol)(using Context): Annotation = {
376363
val tref = cls.typeRef

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4227,8 +4227,10 @@ object Types extends TypeUtils {
42274227
val parent1 = mapOver(parent)
42284228
if ann.symbol.isRetainsLike then
42294229
range(
4230-
AnnotatedType(parent1, CaptureSet.empty.toRegularAnnotation(ann.symbol)),
4231-
AnnotatedType(parent1, CaptureSet.universal.toRegularAnnotation(ann.symbol)))
4230+
AnnotatedType(parent1,
4231+
CompactAnnotation(defn.RetainsAnnot.typeRef.appliedTo(defn.NothingType))),
4232+
AnnotatedType(parent1,
4233+
CompactAnnotation(defn.RetainsCapAnnot.appliedRef)))
42324234
else
42334235
parent1
42344236
case _ => mapOver(tp)

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,10 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
201201
inJavaAnnot = annot.symbol.is(JavaDefined)
202202
if (inJavaAnnot) checkValidJavaAnnotation(annot)
203203
try
204-
val annotCtx = if annot.hasAttachment(untpd.RetainsAnnot)
205-
then ctx.addMode(Mode.InCaptureSet) else ctx
204+
val annotCtx =
205+
if annot.hasAttachment(untpd.RetainsAnnot)
206+
then ctx.addMode(Mode.InCaptureSet)
207+
else ctx
206208
transform(annot)(using annotCtx)
207209
finally inJavaAnnot = saved
208210
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import NameOps.*
1111
import collection.mutable
1212
import reporting.*
1313
import Checking.{checkNoPrivateLeaks, checkNoWildcard}
14-
import cc.CaptureSet
1514
import util.Property
1615
import transform.Splicer
1716

@@ -570,10 +569,14 @@ trait TypeAssigner {
570569
def assignType(tree: untpd.Export)(using Context): Export =
571570
tree.withType(defn.UnitType)
572571

573-
def assignType(tree: untpd.Annotated, arg: Tree, annot: Tree)(using Context): Annotated = {
572+
def assignType(tree: untpd.Annotated, arg: Tree, annotTree: Tree)(using Context): Annotated =
574573
assert(tree.isType) // annotating a term is done via a Typed node, can't use Annotate directly
575-
tree.withType(AnnotatedType(arg.tpe, Annotation(annot)))
576-
}
574+
if annotClass(annotTree).exists then
575+
tree.withType(AnnotatedType(arg.tpe, Annotation(annotTree)))
576+
else
577+
// this can happen if cyclic reference errors occurred when typing the annotation
578+
tree.withType(
579+
errorType(em"Malformed annotation $tree, will be ignored", annotTree.srcPos))
577580

578581
def assignType(tree: untpd.PackageDef, pid: Tree)(using Context): PackageDef =
579582
tree.withType(pid.symbol.termRef)

tests/neg-custom-args/captures/lazyvals.check

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
| Note that capability console is not included in capture set {x}.
1717
|
1818
| longer explanation available when compiling with `-explain`
19-
-- Error: tests/neg-custom-args/captures/lazyvals.scala:16:18 ----------------------------------------------------------
19+
-- Error: tests/neg-custom-args/captures/lazyvals.scala:16:12 ----------------------------------------------------------
2020
16 | val fun3: () ->{x} String = () => x() // error // error
21-
| ^
22-
| (x : () -> String) cannot be tracked since its capture set is empty
21+
| ^^^^^^^^^^^^^^^
22+
| (x : () -> String) cannot be tracked since its capture set is empty

tests/neg-custom-args/captures/spread-problem.check

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
11 | race(src1, src2) // error
88
| ^^^^^^^^^^
99
| Found: (Source[T]^, Source[T]^)
10-
| Required: Seq[Source[T]^]
10+
| Required: Seq[Source[T]^{C}]
11+
|
12+
| where: C is a type variable with constraint >: scala.caps.CapSet and <: scala.caps.CapSet^
1113
|
1214
| longer explanation available when compiling with `-explain`

0 commit comments

Comments
 (0)