Skip to content

Commit a8a7213

Browse files
committed
Avoid cycles when loading standard library under cc
Two measures - Introduce a new annotation @retainsCap that's equivalent to @retains(cap) but that does not need to load Seq to analyze a vararg argument. - Don't use `&` when computing self types of classes with explicitly declared capturing self types. Fixes #19568
1 parent f15bbda commit a8a7213

File tree

13 files changed

+49
-31
lines changed

13 files changed

+49
-31
lines changed

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

+16-4
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,14 @@ extension (tree: Tree)
9898
tree.putAttachment(Captures, refs)
9999
refs
100100

101-
/** The arguments of a @retains or @retainsByName annotation */
101+
/** The arguments of a @retains, @retainsCap or @retainsByName annotation */
102102
def retainedElems(using Context): List[Tree] = tree match
103-
case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems
104-
case _ => Nil
103+
case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) =>
104+
elems
105+
case _ =>
106+
if tree.symbol.maybeOwner == defn.RetainsCapAnnot
107+
then ref(defn.captureRoot.termRef) :: Nil
108+
else Nil
105109

106110
extension (tp: Type)
107111

@@ -207,7 +211,7 @@ extension (tp: Type)
207211
def dropAllRetains(using Context): Type = // TODO we should drop retains from inferred types before unpickling
208212
val tm = new TypeMap:
209213
def apply(t: Type) = t match
210-
case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot =>
214+
case AnnotatedType(parent, annot) if annot.symbol.isRetains =>
211215
apply(parent)
212216
case _ =>
213217
mapOver(t)
@@ -326,6 +330,14 @@ extension (cls: ClassSymbol)
326330

327331
extension (sym: Symbol)
328332

333+
/** This symbol is one of `retains` or `retainsCap` */
334+
def isRetains(using Context): Boolean =
335+
sym == defn.RetainsAnnot || sym == defn.RetainsCapAnnot
336+
337+
/** This symbol is one of `retains`, `retainsCap`, or`retainsByName` */
338+
def isRetainsLike(using Context): Boolean =
339+
isRetains || sym == defn.RetainsByNameAnnot
340+
329341
/** A class is pure if:
330342
* - one its base types has an explicitly declared self type with an empty capture set
331343
* - or it is a value class

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ object CapturingType:
5858
case AnnotatedType(parent, ann: CaptureAnnotation)
5959
if isCaptureCheckingOrSetup =>
6060
Some((parent, ann.refs))
61-
case AnnotatedType(parent, ann)
62-
if ann.symbol == defn.RetainsAnnot && isCaptureChecking =>
61+
case AnnotatedType(parent, ann) if ann.symbol.isRetains && isCaptureChecking =>
6362
// There are some circumstances where we cannot map annotated types
6463
// with retains annotations to capturing types, so this second recognizer
6564
// path still has to exist. One example is when checking capture sets

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ object RetainingType:
2323

2424
def unapply(tp: AnnotatedType)(using Context): Option[(Type, List[Tree])] =
2525
val sym = tp.annot.symbol
26-
if sym == defn.RetainsAnnot || sym == defn.RetainsByNameAnnot then
26+
if sym.isRetainsLike then
2727
tp.annot match
2828
case _: CaptureAnnotation =>
2929
assert(ctx.mode.is(Mode.IgnoreCaptures), s"bad retains $tp at ${ctx.phase}")

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
120120
case tp @ CapturingType(parent, refs) =>
121121
if tp.isBoxed then tp else tp.boxed
122122
case tp @ AnnotatedType(parent, ann) =>
123-
if ann.symbol == defn.RetainsAnnot
123+
if ann.symbol.isRetains
124124
then CapturingType(parent, ann.tree.toCaptureSet, boxed = true)
125125
else tp.derivedAnnotatedType(box(parent), ann)
126126
case tp1 @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp1) =>
@@ -192,7 +192,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
192192

193193
def apply(tp: Type) =
194194
val tp1 = tp match
195-
case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot =>
195+
case AnnotatedType(parent, annot) if annot.symbol.isRetains =>
196196
// Drop explicit retains annotations
197197
apply(parent)
198198
case tp @ AppliedType(tycon, args) =>
@@ -283,7 +283,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
283283
t.derivedCapturingType(this(parent), refs)
284284
case t @ AnnotatedType(parent, ann) =>
285285
val parent1 = this(parent)
286-
if ann.symbol == defn.RetainsAnnot then
286+
if ann.symbol.isRetains then
287287
for tpt <- tptToCheck do
288288
checkWellformedLater(parent1, ann.tree, tpt)
289289
CapturingType(parent1, ann.tree.toCaptureSet)

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,7 @@ class Definitions {
10571057
@tu lazy val ReachCapabilityAnnot = requiredClass("scala.annotation.internal.reachCapability")
10581058
@tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability")
10591059
@tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains")
1060+
@tu lazy val RetainsCapAnnot: ClassSymbol = requiredClass("scala.annotation.retainsCap")
10601061
@tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName")
10611062
@tu lazy val PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary")
10621063

@@ -2008,7 +2009,7 @@ class Definitions {
20082009
@tu lazy val ccExperimental: Set[Symbol] = Set(
20092010
CapsModule, CapsModule.moduleClass, PureClass,
20102011
CapabilityAnnot, RequiresCapabilityAnnot,
2011-
RetainsAnnot, RetainsByNameAnnot)
2012+
RetainsAnnot, RetainsCapAnnot, RetainsByNameAnnot)
20122013

20132014
// ----- primitive value class machinery ------------------------------------------
20142015

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

+1
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,7 @@ object StdNames {
583583
val releaseFence : N = "releaseFence"
584584
val retains: N = "retains"
585585
val retainsByName: N = "retainsByName"
586+
val retainsCap: N = "retainsCap"
586587
val rootMirror : N = "rootMirror"
587588
val run: N = "run"
588589
val runOrElse: N = "runOrElse"

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import config.Printers.{core, typr, matchTypes}
3838
import reporting.{trace, Message}
3939
import java.lang.ref.WeakReference
4040
import compiletime.uninitialized
41-
import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, RetainingType, isCaptureChecking}
41+
import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, isCaptureChecking, isRetains, isRetainsLike}
4242
import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap}
4343

4444
import scala.annotation.internal.sharable
@@ -4025,7 +4025,7 @@ object Types extends TypeUtils {
40254025
mapOver(tp)
40264026
case AnnotatedType(parent, ann) if ann.refersToParamOf(thisLambdaType) =>
40274027
val parent1 = mapOver(parent)
4028-
if ann.symbol == defn.RetainsAnnot || ann.symbol == defn.RetainsByNameAnnot then
4028+
if ann.symbol.isRetainsLike then
40294029
range(
40304030
AnnotatedType(parent1, CaptureSet.empty.toRegularAnnotation(ann.symbol)),
40314031
AnnotatedType(parent1, CaptureSet.universal.toRegularAnnotation(ann.symbol)))
@@ -5315,10 +5315,10 @@ object Types extends TypeUtils {
53155315
else if (clsd.is(Module)) givenSelf
53165316
else if (ctx.erasedTypes) appliedRef
53175317
else givenSelf.dealiasKeepAnnots match
5318-
case givenSelf1 @ AnnotatedType(tp, ann) if ann.symbol == defn.RetainsAnnot =>
5319-
givenSelf1.derivedAnnotatedType(tp & appliedRef, ann)
5318+
case givenSelf1 @ AnnotatedType(tp, ann) if ann.symbol.isRetains =>
5319+
givenSelf1.derivedAnnotatedType(AndType.make(tp, appliedRef), ann)
53205320
case _ =>
5321-
AndType(givenSelf, appliedRef)
5321+
AndType.make(givenSelf, appliedRef)
53225322
}
53235323
selfTypeCache.nn
53245324
}

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

+5-6
Original file line numberDiff line numberDiff line change
@@ -1765,12 +1765,11 @@ object Parsers {
17651765
RefinedTypeTree(rejectWildcardType(t), refinement(indentOK = true))
17661766
})
17671767
else if Feature.ccEnabled && in.isIdent(nme.UPARROW) && isCaptureUpArrow then
1768-
val upArrowStart = in.offset
1769-
in.nextToken()
1770-
def cs =
1771-
if in.token == LBRACE then captureSet()
1772-
else atSpan(upArrowStart)(captureRoot) :: Nil
1773-
makeRetaining(t, cs, tpnme.retains)
1768+
atSpan(t.span.start):
1769+
in.nextToken()
1770+
if in.token == LBRACE
1771+
then makeRetaining(t, captureSet(), tpnme.retains)
1772+
else makeRetaining(t, Nil, tpnme.retainsCap)
17741773
else
17751774
t
17761775
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import config.{Config, Feature}
2727

2828
import dotty.tools.dotc.util.SourcePosition
2929
import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef}
30-
import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef}
30+
import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef, isRetains}
3131
import dotty.tools.dotc.parsing.JavaParsers
3232

3333
class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
@@ -643,7 +643,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
643643
def toTextRetainsAnnot =
644644
try changePrec(GlobalPrec)(toText(arg) ~ "^" ~ toTextCaptureSet(captureSet))
645645
catch case ex: IllegalCaptureRef => toTextAnnot
646-
if annot.symbol.maybeOwner == defn.RetainsAnnot
646+
if annot.symbol.maybeOwner.isRetains
647647
&& Feature.ccEnabled && !printDebug
648648
&& Phases.checkCapturesPhase.exists // might be missing on -Ytest-pickler
649649
then toTextRetainsAnnot

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import config.Feature.sourceVersion
3838
import config.SourceVersion.*
3939
import config.MigrationVersion
4040
import printing.Formatting.hlAsKeyword
41-
import cc.isCaptureChecking
41+
import cc.{isCaptureChecking, isRetainsLike}
4242

4343
import collection.mutable
4444
import reporting.*
@@ -705,8 +705,7 @@ object Checking {
705705
declaredParents =
706706
tp.declaredParents.map(p => transformedParent(apply(p)))
707707
)
708-
case tp @ AnnotatedType(underlying, annot)
709-
if annot.symbol == defn.RetainsAnnot || annot.symbol == defn.RetainsByNameAnnot =>
708+
case tp @ AnnotatedType(underlying, annot) if annot.symbol.isRetainsLike =>
710709
val underlying1 = this(underlying)
711710
val saved = inCaptureSet
712711
inCaptureSet = true

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import staging.StagingLevel
4848
import reporting.*
4949
import Nullables.*
5050
import NullOpsDecorator.*
51-
import cc.CheckCaptures
51+
import cc.{CheckCaptures, isRetainsLike}
5252
import config.Config
5353
import config.MigrationVersion
5454

@@ -2940,9 +2940,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
29402940
val arg1 = typed(tree.arg, pt)
29412941
if (ctx.mode is Mode.Type) {
29422942
val cls = annot1.symbol.maybeOwner
2943-
if Feature.ccEnabled
2944-
&& (cls == defn.RetainsAnnot || cls == defn.RetainsByNameAnnot)
2945-
then
2943+
if Feature.ccEnabled && cls.isRetainsLike then
29462944
CheckCaptures.checkWellformed(arg1, annot1)
29472945
if arg1.isType then
29482946
assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1)

library/src/scala/annotation/retains.scala

+8
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,11 @@ package scala.annotation
1313
*/
1414
@experimental
1515
class retains(xs: Any*) extends annotation.StaticAnnotation
16+
17+
/** Equivalent in meaning to `@retains(cap)`, but consumes less bytecode.
18+
*/
19+
@experimental
20+
class retainsCap() extends annotation.StaticAnnotation
21+
// This special case is needed to be able to load standard library modules without
22+
// cyclic reference errors. Specifically, load sequences involving IterableOnce.
23+

tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ val experimentalDefinitionInLibrary = Set(
5151
"scala.annotation.capability",
5252
"scala.annotation.retains",
5353
"scala.annotation.retainsByName",
54+
"scala.annotation.retainsCap",
5455
"scala.Pure",
5556
"scala.caps",
5657
"scala.caps$",

0 commit comments

Comments
 (0)