Skip to content

Commit 2655768

Browse files
authored
Avoid cycles when loading standard library under cc (#19603)
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. Partial fix of #19586
2 parents 5850d2d + c5dcba5 commit 2655768

File tree

14 files changed

+51
-33
lines changed

14 files changed

+51
-33
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)
@@ -348,6 +352,14 @@ extension (cls: ClassSymbol)
348352

349353
extension (sym: Symbol)
350354

355+
/** This symbol is one of `retains` or `retainsCap` */
356+
def isRetains(using Context): Boolean =
357+
sym == defn.RetainsAnnot || sym == defn.RetainsCapAnnot
358+
359+
/** This symbol is one of `retains`, `retainsCap`, or`retainsByName` */
360+
def isRetainsLike(using Context): Boolean =
361+
isRetains || sym == defn.RetainsByNameAnnot
362+
351363
/** A class is pure if:
352364
* - one its base types has an explicitly declared self type with an empty capture set
353365
* - 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
@@ -1061,6 +1061,7 @@ class Definitions {
10611061
@tu lazy val ReachCapabilityAnnot = requiredClass("scala.annotation.internal.reachCapability")
10621062
@tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability")
10631063
@tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains")
1064+
@tu lazy val RetainsCapAnnot: ClassSymbol = requiredClass("scala.annotation.retainsCap")
10641065
@tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName")
10651066
@tu lazy val PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary")
10661067

@@ -2011,7 +2012,7 @@ class Definitions {
20112012
@tu lazy val ccExperimental: Set[Symbol] = Set(
20122013
CapsModule, CapsModule.moduleClass, PureClass,
20132014
CapabilityAnnot, RequiresCapabilityAnnot,
2014-
RetainsAnnot, RetainsByNameAnnot)
2015+
RetainsAnnot, RetainsCapAnnot, RetainsByNameAnnot)
20152016

20162017
// ----- primitive value class machinery ------------------------------------------
20172018

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

+1
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,7 @@ object StdNames {
584584
val releaseFence : N = "releaseFence"
585585
val retains: N = "retains"
586586
val retainsByName: N = "retainsByName"
587+
val retainsCap: N = "retainsCap"
587588
val rootMirror : N = "rootMirror"
588589
val run: N = "run"
589590
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
@@ -4031,7 +4031,7 @@ object Types extends TypeUtils {
40314031
mapOver(tp)
40324032
case AnnotatedType(parent, ann) if ann.refersToParamOf(thisLambdaType) =>
40334033
val parent1 = mapOver(parent)
4034-
if ann.symbol == defn.RetainsAnnot || ann.symbol == defn.RetainsByNameAnnot then
4034+
if ann.symbol.isRetainsLike then
40354035
range(
40364036
AnnotatedType(parent1, CaptureSet.empty.toRegularAnnotation(ann.symbol)),
40374037
AnnotatedType(parent1, CaptureSet.universal.toRegularAnnotation(ann.symbol)))
@@ -5321,10 +5321,10 @@ object Types extends TypeUtils {
53215321
else if (clsd.is(Module)) givenSelf
53225322
else if (ctx.erasedTypes) appliedRef
53235323
else givenSelf.dealiasKeepAnnots match
5324-
case givenSelf1 @ AnnotatedType(tp, ann) if ann.symbol == defn.RetainsAnnot =>
5325-
givenSelf1.derivedAnnotatedType(tp & appliedRef, ann)
5324+
case givenSelf1 @ AnnotatedType(tp, ann) if ann.symbol.isRetains =>
5325+
givenSelf1.derivedAnnotatedType(AndType.make(tp, appliedRef), ann)
53265326
case _ =>
5327-
AndType(givenSelf, appliedRef)
5327+
AndType.make(givenSelf, appliedRef)
53285328
}
53295329
selfTypeCache.nn
53305330
}

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$",

tests/semanticdb/metac.expect

+2-2
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ Diagnostics => 3 entries
399399
Synthetics => 2 entries
400400

401401
Symbols:
402-
example/Anonymous# => class Anonymous extends Object { self: Anonymous & Anonymous => +9 decls }
402+
example/Anonymous# => class Anonymous extends Object { self: Anonymous => +9 decls }
403403
example/Anonymous#Bar# => trait Bar extends Object { self: Bar => +2 decls }
404404
example/Anonymous#Bar#`<init>`(). => primary ctor <init> (): Bar
405405
example/Anonymous#Bar#bar(). => abstract method bar => String
@@ -3414,7 +3414,7 @@ local1 => selfparam self: B
34143414
local2 => selfparam self: B & C1
34153415
selfs/B# => class B extends Object { self: B => +1 decls }
34163416
selfs/B#`<init>`(). => primary ctor <init> (): B
3417-
selfs/C1# => class C1 extends B { self: C1 & C1 => +1 decls }
3417+
selfs/C1# => class C1 extends B { self: C1 => +1 decls }
34183418
selfs/C1#`<init>`(). => primary ctor <init> (): C1
34193419
selfs/C2# => class C2 extends B { self: B & C2 => +1 decls }
34203420
selfs/C2#`<init>`(). => primary ctor <init> (): C2

0 commit comments

Comments
 (0)