Skip to content

Commit

Permalink
Avoid cycles when loading standard library under cc (#19603)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
odersky authored Feb 4, 2024
2 parents 5850d2d + c5dcba5 commit 2655768
Show file tree
Hide file tree
Showing 14 changed files with 51 additions and 33 deletions.
20 changes: 16 additions & 4 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,14 @@ extension (tree: Tree)
tree.putAttachment(Captures, refs)
refs

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

extension (tp: Type)

Expand Down Expand Up @@ -207,7 +211,7 @@ extension (tp: Type)
def dropAllRetains(using Context): Type = // TODO we should drop retains from inferred types before unpickling
val tm = new TypeMap:
def apply(t: Type) = t match
case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot =>
case AnnotatedType(parent, annot) if annot.symbol.isRetains =>
apply(parent)
case _ =>
mapOver(t)
Expand Down Expand Up @@ -348,6 +352,14 @@ extension (cls: ClassSymbol)

extension (sym: Symbol)

/** This symbol is one of `retains` or `retainsCap` */
def isRetains(using Context): Boolean =
sym == defn.RetainsAnnot || sym == defn.RetainsCapAnnot

/** This symbol is one of `retains`, `retainsCap`, or`retainsByName` */
def isRetainsLike(using Context): Boolean =
isRetains || sym == defn.RetainsByNameAnnot

/** A class is pure if:
* - one its base types has an explicitly declared self type with an empty capture set
* - or it is a value class
Expand Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/cc/CapturingType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ object CapturingType:
case AnnotatedType(parent, ann: CaptureAnnotation)
if isCaptureCheckingOrSetup =>
Some((parent, ann.refs))
case AnnotatedType(parent, ann)
if ann.symbol == defn.RetainsAnnot && isCaptureChecking =>
case AnnotatedType(parent, ann) if ann.symbol.isRetains && isCaptureChecking =>
// There are some circumstances where we cannot map annotated types
// with retains annotations to capturing types, so this second recognizer
// path still has to exist. One example is when checking capture sets
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/RetainingType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object RetainingType:

def unapply(tp: AnnotatedType)(using Context): Option[(Type, List[Tree])] =
val sym = tp.annot.symbol
if sym == defn.RetainsAnnot || sym == defn.RetainsByNameAnnot then
if sym.isRetainsLike then
tp.annot match
case _: CaptureAnnotation =>
assert(ctx.mode.is(Mode.IgnoreCaptures), s"bad retains $tp at ${ctx.phase}")
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
case tp @ CapturingType(parent, refs) =>
if tp.isBoxed then tp else tp.boxed
case tp @ AnnotatedType(parent, ann) =>
if ann.symbol == defn.RetainsAnnot
if ann.symbol.isRetains
then CapturingType(parent, ann.tree.toCaptureSet, boxed = true)
else tp.derivedAnnotatedType(box(parent), ann)
case tp1 @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp1) =>
Expand Down Expand Up @@ -192,7 +192,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:

def apply(tp: Type) =
val tp1 = tp match
case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot =>
case AnnotatedType(parent, annot) if annot.symbol.isRetains =>
// Drop explicit retains annotations
apply(parent)
case tp @ AppliedType(tycon, args) =>
Expand Down Expand Up @@ -283,7 +283,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
t.derivedCapturingType(this(parent), refs)
case t @ AnnotatedType(parent, ann) =>
val parent1 = this(parent)
if ann.symbol == defn.RetainsAnnot then
if ann.symbol.isRetains then
for tpt <- tptToCheck do
checkWellformedLater(parent1, ann.tree, tpt)
CapturingType(parent1, ann.tree.toCaptureSet)
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,7 @@ class Definitions {
@tu lazy val ReachCapabilityAnnot = requiredClass("scala.annotation.internal.reachCapability")
@tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability")
@tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains")
@tu lazy val RetainsCapAnnot: ClassSymbol = requiredClass("scala.annotation.retainsCap")
@tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName")
@tu lazy val PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary")

Expand Down Expand Up @@ -2011,7 +2012,7 @@ class Definitions {
@tu lazy val ccExperimental: Set[Symbol] = Set(
CapsModule, CapsModule.moduleClass, PureClass,
CapabilityAnnot, RequiresCapabilityAnnot,
RetainsAnnot, RetainsByNameAnnot)
RetainsAnnot, RetainsCapAnnot, RetainsByNameAnnot)

// ----- primitive value class machinery ------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ object StdNames {
val releaseFence : N = "releaseFence"
val retains: N = "retains"
val retainsByName: N = "retainsByName"
val retainsCap: N = "retainsCap"
val rootMirror : N = "rootMirror"
val run: N = "run"
val runOrElse: N = "runOrElse"
Expand Down
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import config.Printers.{core, typr, matchTypes}
import reporting.{trace, Message}
import java.lang.ref.WeakReference
import compiletime.uninitialized
import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, RetainingType, isCaptureChecking}
import cc.{CapturingType, CaptureSet, derivedCapturingType, isBoxedCapturing, isCaptureChecking, isRetains, isRetainsLike}
import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap}

import scala.annotation.internal.sharable
Expand Down Expand Up @@ -4031,7 +4031,7 @@ object Types extends TypeUtils {
mapOver(tp)
case AnnotatedType(parent, ann) if ann.refersToParamOf(thisLambdaType) =>
val parent1 = mapOver(parent)
if ann.symbol == defn.RetainsAnnot || ann.symbol == defn.RetainsByNameAnnot then
if ann.symbol.isRetainsLike then
range(
AnnotatedType(parent1, CaptureSet.empty.toRegularAnnotation(ann.symbol)),
AnnotatedType(parent1, CaptureSet.universal.toRegularAnnotation(ann.symbol)))
Expand Down Expand Up @@ -5321,10 +5321,10 @@ object Types extends TypeUtils {
else if (clsd.is(Module)) givenSelf
else if (ctx.erasedTypes) appliedRef
else givenSelf.dealiasKeepAnnots match
case givenSelf1 @ AnnotatedType(tp, ann) if ann.symbol == defn.RetainsAnnot =>
givenSelf1.derivedAnnotatedType(tp & appliedRef, ann)
case givenSelf1 @ AnnotatedType(tp, ann) if ann.symbol.isRetains =>
givenSelf1.derivedAnnotatedType(AndType.make(tp, appliedRef), ann)
case _ =>
AndType(givenSelf, appliedRef)
AndType.make(givenSelf, appliedRef)
}
selfTypeCache.nn
}
Expand Down
11 changes: 5 additions & 6 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1765,12 +1765,11 @@ object Parsers {
RefinedTypeTree(rejectWildcardType(t), refinement(indentOK = true))
})
else if Feature.ccEnabled && in.isIdent(nme.UPARROW) && isCaptureUpArrow then
val upArrowStart = in.offset
in.nextToken()
def cs =
if in.token == LBRACE then captureSet()
else atSpan(upArrowStart)(captureRoot) :: Nil
makeRetaining(t, cs, tpnme.retains)
atSpan(t.span.start):
in.nextToken()
if in.token == LBRACE
then makeRetaining(t, captureSet(), tpnme.retains)
else makeRetaining(t, Nil, tpnme.retainsCap)
else
t
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import config.{Config, Feature}

import dotty.tools.dotc.util.SourcePosition
import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef}
import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef}
import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef, isRetains}
import dotty.tools.dotc.parsing.JavaParsers

class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
Expand Down Expand Up @@ -643,7 +643,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
def toTextRetainsAnnot =
try changePrec(GlobalPrec)(toText(arg) ~ "^" ~ toTextCaptureSet(captureSet))
catch case ex: IllegalCaptureRef => toTextAnnot
if annot.symbol.maybeOwner == defn.RetainsAnnot
if annot.symbol.maybeOwner.isRetains
&& Feature.ccEnabled && !printDebug
&& Phases.checkCapturesPhase.exists // might be missing on -Ytest-pickler
then toTextRetainsAnnot
Expand Down
5 changes: 2 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import config.Feature.sourceVersion
import config.SourceVersion.*
import config.MigrationVersion
import printing.Formatting.hlAsKeyword
import cc.isCaptureChecking
import cc.{isCaptureChecking, isRetainsLike}

import collection.mutable
import reporting.*
Expand Down Expand Up @@ -705,8 +705,7 @@ object Checking {
declaredParents =
tp.declaredParents.map(p => transformedParent(apply(p)))
)
case tp @ AnnotatedType(underlying, annot)
if annot.symbol == defn.RetainsAnnot || annot.symbol == defn.RetainsByNameAnnot =>
case tp @ AnnotatedType(underlying, annot) if annot.symbol.isRetainsLike =>
val underlying1 = this(underlying)
val saved = inCaptureSet
inCaptureSet = true
Expand Down
6 changes: 2 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import staging.StagingLevel
import reporting.*
import Nullables.*
import NullOpsDecorator.*
import cc.CheckCaptures
import cc.{CheckCaptures, isRetainsLike}
import config.Config
import config.MigrationVersion

Expand Down Expand Up @@ -2940,9 +2940,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val arg1 = typed(tree.arg, pt)
if (ctx.mode is Mode.Type) {
val cls = annot1.symbol.maybeOwner
if Feature.ccEnabled
&& (cls == defn.RetainsAnnot || cls == defn.RetainsByNameAnnot)
then
if Feature.ccEnabled && cls.isRetainsLike then
CheckCaptures.checkWellformed(arg1, annot1)
if arg1.isType then
assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1)
Expand Down
8 changes: 8 additions & 0 deletions library/src/scala/annotation/retains.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@ package scala.annotation
*/
@experimental
class retains(xs: Any*) extends annotation.StaticAnnotation

/** Equivalent in meaning to `@retains(cap)`, but consumes less bytecode.
*/
@experimental
class retainsCap() extends annotation.StaticAnnotation
// This special case is needed to be able to load standard library modules without
// cyclic reference errors. Specifically, load sequences involving IterableOnce.

Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ val experimentalDefinitionInLibrary = Set(
"scala.annotation.capability",
"scala.annotation.retains",
"scala.annotation.retainsByName",
"scala.annotation.retainsCap",
"scala.Pure",
"scala.caps",
"scala.caps$",
Expand Down
4 changes: 2 additions & 2 deletions tests/semanticdb/metac.expect
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ Diagnostics => 3 entries
Synthetics => 2 entries

Symbols:
example/Anonymous# => class Anonymous extends Object { self: Anonymous & Anonymous => +9 decls }
example/Anonymous# => class Anonymous extends Object { self: Anonymous => +9 decls }
example/Anonymous#Bar# => trait Bar extends Object { self: Bar => +2 decls }
example/Anonymous#Bar#`<init>`(). => primary ctor <init> (): Bar
example/Anonymous#Bar#bar(). => abstract method bar => String
Expand Down Expand Up @@ -3414,7 +3414,7 @@ local1 => selfparam self: B
local2 => selfparam self: B & C1
selfs/B# => class B extends Object { self: B => +1 decls }
selfs/B#`<init>`(). => primary ctor <init> (): B
selfs/C1# => class C1 extends B { self: C1 & C1 => +1 decls }
selfs/C1# => class C1 extends B { self: C1 => +1 decls }
selfs/C1#`<init>`(). => primary ctor <init> (): C1
selfs/C2# => class C2 extends B { self: B & C2 => +1 decls }
selfs/C2#`<init>`(). => primary ctor <init> (): C2
Expand Down

0 comments on commit 2655768

Please sign in to comment.