diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 8073540d6b24..1b51110fa345 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -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) @@ -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) @@ -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 diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index 2a5cb91b45d3..ee0cad4d4d03 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/cc/RetainingType.scala b/compiler/src/dotty/tools/dotc/cc/RetainingType.scala index 7902b03445fb..efd0e96fd658 100644 --- a/compiler/src/dotty/tools/dotc/cc/RetainingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/RetainingType.scala @@ -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}") diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 44e832a20d34..9ab41859f170 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -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) => @@ -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) => @@ -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) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 4bc427ee0687..58c80ba58aa4 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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") @@ -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 ------------------------------------------ diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 253a45ffd7a8..6ab4e0c5e967 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -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" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b4ac059a4fc5..c536623d5249 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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 @@ -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))) @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8c848e6834b0..6892dfdd94ca 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index de9e21aa4146..eb184cb22bd0 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -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) { @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 1ad6e231c0c2..5fc63f4575e3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -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.* @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c0b5ea19ec23..7727c125d1e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 @@ -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) diff --git a/library/src/scala/annotation/retains.scala b/library/src/scala/annotation/retains.scala index 0387840ea8bd..4fa14e635136 100644 --- a/library/src/scala/annotation/retains.scala +++ b/library/src/scala/annotation/retains.scala @@ -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. + diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 12ea8eb26c47..111c8c0bada3 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -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$", diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 3ac3b7a01693..d1eabaa95bf7 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -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#``(). => primary ctor (): Bar example/Anonymous#Bar#bar(). => abstract method bar => String @@ -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#``(). => primary ctor (): B -selfs/C1# => class C1 extends B { self: C1 & C1 => +1 decls } +selfs/C1# => class C1 extends B { self: C1 => +1 decls } selfs/C1#``(). => primary ctor (): C1 selfs/C2# => class C2 extends B { self: B & C2 => +1 decls } selfs/C2#``(). => primary ctor (): C2