Skip to content

Avoid cycles when loading standard library under cc #19603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -326,6 +330,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 @@ -1057,6 +1057,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 @@ -2008,7 +2009,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 @@ -583,6 +583,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 @@ -4025,7 +4025,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 @@ -5315,10 +5315,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