From b989a7bf003a3455782c6ad58cbb893a374ef2bf Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 24 Mar 2025 16:55:21 +0100 Subject: [PATCH 1/2] Changes in preparation to make `caps.Capability` stable (#22849) Make `scala.caps` a package instead of an object. The following are still under `scala.caps`, while staying `@experimental`: - The Capability trait, which will be stable in near future - The universal capture reference `cap` -- **now an object**. - The carrier trait for capture set parameters `CapSet` - The `Contains` trait: - Due to `given`s cannot be marked `@experimental`, an experimental `Contains` object was created instead, which contains the `containsImpl` given. - Exclusive capabilities annotations: `Mutable`, `SharedCapability` and `consume` - The _`Exists`_ trait is marked `deprecated`, but is required to build the current version of the compiler. It should be removed in the next version. - The `unsafe` object: - `untrackedCaptures` is moved into `caps.unsafe`. - Deprecated aliases `*` and `Cap` are **removed** The following are moved under the experimental `scala.caps.internal` object: - `capsOf` - `rootCapability`, `reachCapability` and `readOnlyCapability` - `refineOverride` Add documentation for `Capability` and some other experimental annotations. Tests are updated accordingly. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 5 +- .../dotty/tools/dotc/core/Definitions.scala | 17 ++- .../tools/dotc/printing/PlainPrinter.scala | 3 +- library/src/scala/caps.scala | 69 ------------ library/src/scala/caps/package.scala | 106 ++++++++++++++++++ .../immutable/LazyListIterable.scala | 2 +- .../neg-custom-args/captures/capt-wf.scala | 4 +- .../neg-custom-args/captures/try2.scala | 2 +- tests/neg-custom-args/captures/capt1.check | 12 +- .../neg-custom-args/captures/cc-poly-1.check | 4 +- tests/neg-custom-args/captures/cc-this2.check | 2 +- .../captures/exception-definitions.check | 2 +- .../captures/existential-mapping.check | 32 +++--- tests/neg-custom-args/captures/i21313.check | 4 +- tests/neg-custom-args/captures/i21401.check | 2 +- tests/neg-custom-args/captures/i21614.check | 8 +- .../captures/leaked-curried.check | 8 +- .../captures/untracked-captures.scala | 3 +- tests/pos/caps-universal.scala | 2 +- .../stdlibExperimentalDefinitions.scala | 15 ++- 20 files changed, 181 insertions(+), 121 deletions(-) delete mode 100644 library/src/scala/caps.scala create mode 100644 library/src/scala/caps/package.scala diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 2acfc4cf86e3..57c74d90b45d 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -518,6 +518,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def scalaUnit(implicit src: SourceFile): Select = scalaDot(tpnme.Unit) def scalaAny(implicit src: SourceFile): Select = scalaDot(tpnme.Any) + def capsInternalDot(name: Name)(using SourceFile): Select = + Select(Select(scalaDot(nme.caps), nme.internal), name) + def captureRoot(using Context): Select = Select(scalaDot(nme.caps), nme.CAPTURE_ROOT) @@ -525,7 +528,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { Annotated(parent, New(scalaAnnotationDot(annotName), List(refs))) def makeCapsOf(tp: RefTree)(using Context): Tree = - TypeApply(Select(scalaDot(nme.caps), nme.capsOf), tp :: Nil) + TypeApply(capsInternalDot(nme.capsOf), tp :: Nil) // Capture set variable `[C^]` becomes: `[C >: CapSet <: CapSet^{cap}]` def makeCapsBound()(using Context): TypeBoundsTree = diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index a74cade40cda..e44bfcee2cf7 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -993,17 +993,19 @@ class Definitions { @tu lazy val LabelClass: Symbol = requiredClass("scala.util.boundary.Label") @tu lazy val BreakClass: Symbol = requiredClass("scala.util.boundary.Break") - @tu lazy val CapsModule: Symbol = requiredModule("scala.caps") + @tu lazy val CapsModule: Symbol = requiredPackage("scala.caps") @tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("cap") @tu lazy val Caps_Capability: TypeSymbol = CapsModule.requiredType("Capability") @tu lazy val Caps_CapSet: ClassSymbol = requiredClass("scala.caps.CapSet") - @tu lazy val Caps_reachCapability: TermSymbol = CapsModule.requiredMethod("reachCapability") - @tu lazy val Caps_capsOf: TermSymbol = CapsModule.requiredMethod("capsOf") + @tu lazy val CapsInternalModule: Symbol = requiredModule("scala.caps.internal") + @tu lazy val Caps_reachCapability: TermSymbol = CapsInternalModule.requiredMethod("reachCapability") + @tu lazy val Caps_capsOf: TermSymbol = CapsInternalModule.requiredMethod("capsOf") @tu lazy val Caps_Exists: ClassSymbol = requiredClass("scala.caps.Exists") @tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe") @tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure") @tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains") - @tu lazy val Caps_containsImpl: TermSymbol = CapsModule.requiredMethod("containsImpl") + @tu lazy val Caps_ContainsModule: Symbol = requiredModule("scala.caps.Contains") + @tu lazy val Caps_containsImpl: TermSymbol = Caps_ContainsModule.requiredMethod("containsImpl") /** The same as CaptureSet.universal but generated implicitly for references of Capability subtypes */ @tu lazy val universalCSImpliedByCapability = CaptureSet(captureRoot.termRef) @@ -1063,7 +1065,7 @@ class Definitions { @tu lazy val UncheckedStableAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedStable") @tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance") @tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures") - @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.untrackedCaptures") + @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.unsafe.untrackedCaptures") @tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") @tu lazy val LanguageFeatureMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.languageFeature") @@ -2087,7 +2089,12 @@ class Definitions { */ @tu lazy val ccExperimental: Set[Symbol] = Set( CapsModule, CapsModule.moduleClass, PureClass, + Caps_Capability, // TODO: Remove when Capability is stabilized RequiresCapabilityAnnot, + captureRoot, Caps_CapSet, Caps_ContainsTrait, Caps_ContainsModule, Caps_ContainsModule.moduleClass, UseAnnot, + Caps_Exists, + CapsUnsafeModule, CapsUnsafeModule.moduleClass, + CapsInternalModule, CapsInternalModule.moduleClass, RetainsAnnot, RetainsCapAnnot, RetainsByNameAnnot) /** Experimental language features defined in `scala.runtime.stdLibPatches.language.experimental`. diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index e90aeb217362..53e9a2c2098b 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -187,10 +187,11 @@ class PlainPrinter(_ctx: Context) extends Printer { homogenize(tp) match { case tp: TypeType => toTextRHS(tp) + case tp: TermRef if tp.isRootCapability => + toTextCaptureRef(tp) case tp: TermRef if !tp.denotationIsCurrent && !homogenizedView // always print underlying when testing picklers - && !tp.isRootCapability || tp.symbol.is(Module) || tp.symbol.name == nme.IMPORT => toTextRef(tp) ~ ".type" diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala deleted file mode 100644 index c35b3b55e813..000000000000 --- a/library/src/scala/caps.scala +++ /dev/null @@ -1,69 +0,0 @@ -package scala - -import annotation.{experimental, compileTimeOnly, retainsCap} - -@experimental object caps: - - trait Capability extends Any - - /** The universal capture reference */ - val cap: Capability = new Capability() {} - - /** The universal capture reference (deprecated) */ - @deprecated("Use `cap` instead") - val `*`: Capability = cap - - @deprecated("Use `Capability` instead") - type Cap = Capability - - /** Carrier trait for capture set type parameters */ - trait CapSet extends Any - - /** A type constraint expressing that the capture set `C` needs to contain - * the capability `R` - */ - sealed trait Contains[+C >: CapSet <: CapSet @retainsCap, R <: Singleton] - - /** The only implementation of `Contains`. The constraint that `{R} <: C` is - * added separately by the capture checker. - */ - given containsImpl[C >: CapSet <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]() - - /** A wrapper indicating a type variable in a capture argument list of a - * @retains annotation. E.g. `^{x, Y^}` is represented as `@retains(x, capsOf[Y])`. - */ - @compileTimeOnly("Should be be used only internally by the Scala compiler") - def capsOf[CS >: CapSet <: CapSet @retainsCap]: Any = ??? - - /** Reach capabilities x* which appear as terms in @retains annotations are encoded - * as `caps.reachCapability(x)`. When converted to CaptureRef types in capture sets - * they are represented as `x.type @annotation.internal.reachCapability`. - */ - extension (x: Any) def reachCapability: Any = x - - /** A trait to allow expressing existential types such as - * - * (x: Exists) => A ->{x} B - */ - sealed trait Exists extends Capability - - /** This should go into annotations. For now it is here, so that we - * can experiment with it quickly between minor releases - */ - final class untrackedCaptures extends annotation.StaticAnnotation - - /** This should go into annotations. For now it is here, so that we - * can experiment with it quickly between minor releases - */ - final class use extends annotation.StaticAnnotation - - object unsafe: - - extension [T](x: T) - /** A specific cast operation to remove a capture set. - * If argument is of type `T^C`, assume it is of type `T` instead. - * Calls to this method are treated specially by the capture checker. - */ - def unsafeAssumePure: T = x - - end unsafe diff --git a/library/src/scala/caps/package.scala b/library/src/scala/caps/package.scala new file mode 100644 index 000000000000..3705a6137be8 --- /dev/null +++ b/library/src/scala/caps/package.scala @@ -0,0 +1,106 @@ +package scala +package caps + +import annotation.{experimental, compileTimeOnly, retainsCap} + +/** + * Base trait for classes that represent capabilities in the + * [object-capability model](https://en.wikipedia.org/wiki/Object-capability_model). + * + * A capability is a value representing a permission, access right, resource or effect. + * Capabilities are typically passed to code as parameters; they should not be global objects. + * Often, they come with access restrictions such as scoped lifetimes or limited sharing. + * + * An example is the [[scala.util.boundary.Label Label]] class in [[scala.util.boundary]]. + * It represents a capability in the sense that it gives permission to [[scala.util.boundary.break break]] + * to the enclosing boundary represented by the `Label`. It has a scoped lifetime, since breaking to + * a `Label` after the associated `boundary` was exited gives a runtime exception. + * + * [[Capability]] has a formal meaning when + * [[scala.language.experimental.captureChecking Capture Checking]] + * is turned on. + * But even without capture checking, extending this trait can be useful for documenting the intended purpose + * of a class. + */ +@experimental +trait Capability extends Any + +/** The universal capture reference. */ +@experimental +object cap extends Capability + +/** Carrier trait for capture set type parameters */ +@experimental +trait CapSet extends Any + +/** A type constraint expressing that the capture set `C` needs to contain + * the capability `R` + */ +@experimental +sealed trait Contains[+C >: CapSet <: CapSet @retainsCap, R <: Singleton] + +@experimental +object Contains: + /** The only implementation of `Contains`. The constraint that `{R} <: C` is + * added separately by the capture checker. + */ + @experimental + given containsImpl[C >: CapSet <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]() + +/** An annotation on parameters `x` stating that the method's body makes + * use of the reach capability `x*`. Consequently, when calling the method + * we need to charge the deep capture set of the actual argiment to the + * environment. + * + * Note: This should go into annotations. For now it is here, so that we + * can experiment with it quickly between minor releases + */ +@experimental +final class use extends annotation.StaticAnnotation + +/** A trait to allow expressing existential types such as + * + * (x: Exists) => A ->{x} B + */ +@experimental +sealed trait Exists extends Capability + +@experimental +object internal: + + /** A wrapper indicating a type variable in a capture argument list of a + * @retains annotation. E.g. `^{x, Y^}` is represented as `@retains(x, capsOf[Y])`. + */ + @compileTimeOnly("Should be be used only internally by the Scala compiler") + def capsOf[CS >: CapSet <: CapSet @retainsCap]: Any = ??? + + /** Reach capabilities x* which appear as terms in @retains annotations are encoded + * as `caps.reachCapability(x)`. When converted to CaptureRef types in capture sets + * they are represented as `x.type @annotation.internal.reachCapability`. + */ + extension (x: Any) def reachCapability: Any = x + +@experimental +object unsafe: + /** + * Marks the constructor parameter as untracked. + * The capture set of this parameter will not be included in + * the capture set of the constructed object. + * + * @note This should go into annotations. For now it is here, so that we + * can experiment with it quickly between minor releases + */ + final class untrackedCaptures extends annotation.StaticAnnotation + + extension [T](x: T) + /** A specific cast operation to remove a capture set. + * If argument is of type `T^C`, assume it is of type `T` instead. + * Calls to this method are treated specially by the capture checker. + */ + def unsafeAssumePure: T = x + + /** A wrapper around code for which separation checks are suppressed. + */ + def unsafeAssumeSeparate(op: Any): op.type = op + +end unsafe diff --git a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala index 28ce8da104aa..7a63fbb4f248 100644 --- a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala +++ b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala @@ -24,7 +24,7 @@ import scala.language.implicitConversions import scala.runtime.Statics import language.experimental.captureChecking import annotation.unchecked.uncheckedCaptures -import caps.untrackedCaptures +import caps.unsafe.untrackedCaptures /** This class implements an immutable linked list. We call it "lazy" * because it computes its elements only when they are needed. diff --git a/tests/disabled/neg-custom-args/captures/capt-wf.scala b/tests/disabled/neg-custom-args/captures/capt-wf.scala index bfe349747776..302202064ac0 100644 --- a/tests/disabled/neg-custom-args/captures/capt-wf.scala +++ b/tests/disabled/neg-custom-args/captures/capt-wf.scala @@ -1,7 +1,7 @@ // No longer valid class C -type Cap = C @retains(caps.*) -type Top = Any @retains(caps.*) +type Cap = C @retains(caps.cap) +type Top = Any @retains(caps.cap) type T = (x: Cap) => List[String @retains(x)] => Unit // error val x: (x: Cap) => Array[String @retains(x)] = ??? // error diff --git a/tests/disabled/neg-custom-args/captures/try2.scala b/tests/disabled/neg-custom-args/captures/try2.scala index 876dc1ec12f1..43e17d8c9eef 100644 --- a/tests/disabled/neg-custom-args/captures/try2.scala +++ b/tests/disabled/neg-custom-args/captures/try2.scala @@ -5,7 +5,7 @@ import annotation.ability @ability erased val canThrow: * = ??? class CanThrow[E <: Exception] extends Retains[canThrow.type] -type Top = Any @retains(caps.*) +type Top = Any @retains(caps.cap) infix type throws[R, E <: Exception] = (erased CanThrow[E]) ?=> R diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index acf8faa7a969..15fe32fc7653 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -36,15 +36,15 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:34:16 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^^^^^^^^^ - | Type variable X of method h cannot be instantiated to () -> (ex$15: caps.Exists) -> C^{ex$15} since - | the part C^{ex$15} of that type captures the root capability `cap`. + | Type variable X of method h cannot be instantiated to () -> (ex$15: scala.caps.Exists) -> C^{ex$15} since + | the part C^{ex$15} of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/capt1.scala:34:30 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^ - | reference (x : C^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> (ex$15: caps.Exists) -> C^{ex$15} + | reference (x : C^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> (ex$15: scala.caps.Exists) -> C^{ex$15} -- Error: tests/neg-custom-args/captures/capt1.scala:36:13 ------------------------------------------------------------- 36 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error | ^^^^^^^^^^^^^^^^^^^^^^^ - | Type variable X of method h cannot be instantiated to box () ->{x} (ex$20: caps.Exists) -> C^{ex$20} since - | the part C^{ex$20} of that type captures the root capability `cap`. + |Type variable X of method h cannot be instantiated to box () ->{x} (ex$20: scala.caps.Exists) -> C^{ex$20} since + |the part C^{ex$20} of that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/cc-poly-1.check b/tests/neg-custom-args/captures/cc-poly-1.check index ea486f55a61f..0ac35b532916 100644 --- a/tests/neg-custom-args/captures/cc-poly-1.check +++ b/tests/neg-custom-args/captures/cc-poly-1.check @@ -1,12 +1,12 @@ -- [E057] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-1.scala:12:6 ------------------------------------- 12 | f[Any](D()) // error | ^ - | Type argument Any does not conform to upper bound caps.CapSet^ + | Type argument Any does not conform to upper bound scala.caps.CapSet^ | | longer explanation available when compiling with `-explain` -- [E057] Type Mismatch Error: tests/neg-custom-args/captures/cc-poly-1.scala:13:6 ------------------------------------- 13 | f[String](D()) // error | ^ - | Type argument String does not conform to upper bound caps.CapSet^ + | Type argument String does not conform to upper bound scala.caps.CapSet^ | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/cc-this2.check b/tests/neg-custom-args/captures/cc-this2.check index 6cb3010d6174..dc61fe2e0396 100644 --- a/tests/neg-custom-args/captures/cc-this2.check +++ b/tests/neg-custom-args/captures/cc-this2.check @@ -2,7 +2,7 @@ -- Error: tests/neg-custom-args/captures/cc-this2/D_2.scala:3:8 -------------------------------------------------------- 3 | this: D^ => // error | ^^ - |reference (caps.cap : caps.Capability) captured by this self type is not included in the allowed capture set {} of pure base class class C + | reference cap captured by this self type is not included in the allowed capture set {} of pure base class class C -- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this2/D_2.scala:2:6 ----------------------------------- 2 |class D extends C: // error | ^ diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index 3f2b15f312b9..67f0f3b72cbb 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/exception-definitions.scala:3:8 ----------------------------------------------- 3 | self: Err^ => // error | ^^^^ - |reference (caps.cap : caps.Capability) captured by this self type is not included in the allowed capture set {} of pure base class class Throwable + |reference cap captured by this self type is not included in the allowed capture set {} of pure base class class Throwable -- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:12 ---------------------------------------------- 7 | val x = c // error | ^ diff --git a/tests/neg-custom-args/captures/existential-mapping.check b/tests/neg-custom-args/captures/existential-mapping.check index 30836bc427cf..43f4e97e6a2a 100644 --- a/tests/neg-custom-args/captures/existential-mapping.check +++ b/tests/neg-custom-args/captures/existential-mapping.check @@ -5,84 +5,84 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:9:25 --------------------------- 9 | val _: (x: C^) -> C = x1 // error | ^^ - | Found: (x1 : (x: C^) -> (ex$3: caps.Exists) -> C^{ex$3}) + | Found: (x1 : (x: C^) -> (ex$3: scala.caps.Exists) -> C^{ex$3}) | Required: (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:12:20 -------------------------- 12 | val _: C^ -> C = x2 // error | ^^ - | Found: (x2 : C^ -> (ex$7: caps.Exists) -> C^{ex$7}) + | Found: (x2 : C^ -> (ex$7: scala.caps.Exists) -> C^{ex$7}) | Required: C^ -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:15:30 -------------------------- 15 | val _: A^ -> (x: C^) -> C = x3 // error | ^^ - | Found: (x3 : A^ -> (x: C^) -> (ex$11: caps.Exists) -> C^{ex$11}) + | Found: (x3 : A^ -> (x: C^) -> (ex$11: scala.caps.Exists) -> C^{ex$11}) | Required: A^ -> (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:18:25 -------------------------- 18 | val _: A^ -> C^ -> C = x4 // error | ^^ - | Found: (x4 : A^ -> C^ -> (ex$19: caps.Exists) -> C^{ex$19}) + | Found: (x4 : A^ -> C^ -> (ex$19: scala.caps.Exists) -> C^{ex$19}) | Required: A^ -> C^ -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:21:30 -------------------------- 21 | val _: A^ -> (x: C^) -> C = x5 // error | ^^ - | Found: (x5 : A^ -> (x: C^) -> (ex$27: caps.Exists) -> C^{ex$27}) + | Found: (x5 : A^ -> (x: C^) -> (ex$27: scala.caps.Exists) -> C^{ex$27}) | Required: A^ -> (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:24:30 -------------------------- 24 | val _: A^ -> (x: C^) => C = x6 // error | ^^ - | Found: (x6 : A^ -> (ex$36: caps.Exists) -> (x: C^) ->{ex$36} (ex$35: caps.Exists) -> C^{ex$35}) - | Required: A^ -> (ex$39: caps.Exists) -> (x: C^) ->{ex$39} C + | Found: (x6 : A^ -> (ex$36: scala.caps.Exists) -> (x: C^) ->{ex$36} (ex$35: scala.caps.Exists) -> C^{ex$35}) + | Required: A^ -> (ex$39: scala.caps.Exists) -> (x: C^) ->{ex$39} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:27:25 -------------------------- 27 | val _: (x: C^) => C = y1 // error | ^^ - | Found: (y1 : (x: C^) => (ex$41: caps.Exists) -> C^{ex$41}) + | Found: (y1 : (x: C^) => (ex$41: scala.caps.Exists) -> C^{ex$41}) | Required: (x: C^) => C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:30:20 -------------------------- 30 | val _: C^ => C = y2 // error | ^^ - | Found: (y2 : C^ => (ex$45: caps.Exists) -> C^{ex$45}) + | Found: (y2 : C^ => (ex$45: scala.caps.Exists) -> C^{ex$45}) | Required: C^ => C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:33:30 -------------------------- 33 | val _: A^ => (x: C^) => C = y3 // error | ^^ - | Found: (y3 : A^ => (ex$50: caps.Exists) -> (x: C^) ->{ex$50} (ex$49: caps.Exists) -> C^{ex$49}) - | Required: A^ => (ex$53: caps.Exists) -> (x: C^) ->{ex$53} C + | Found: (y3 : A^ => (ex$50: scala.caps.Exists) -> (x: C^) ->{ex$50} (ex$49: scala.caps.Exists) -> C^{ex$49}) + | Required: A^ => (ex$53: scala.caps.Exists) -> (x: C^) ->{ex$53} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:36:25 -------------------------- 36 | val _: A^ => C^ => C = y4 // error | ^^ - | Found: (y4 : A^ => (ex$56: caps.Exists) -> C^ ->{ex$56} (ex$55: caps.Exists) -> C^{ex$55}) - | Required: A^ => (ex$59: caps.Exists) -> C^ ->{ex$59} C + | Found: (y4 : A^ => (ex$56: scala.caps.Exists) -> C^ ->{ex$56} (ex$55: scala.caps.Exists) -> C^{ex$55}) + | Required: A^ => (ex$59: scala.caps.Exists) -> C^ ->{ex$59} C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:39:30 -------------------------- 39 | val _: A^ => (x: C^) -> C = y5 // error | ^^ - | Found: (y5 : A^ => (x: C^) -> (ex$61: caps.Exists) -> C^{ex$61}) + | Found: (y5 : A^ => (x: C^) -> (ex$61: scala.caps.Exists) -> C^{ex$61}) | Required: A^ => (x: C^) -> C | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:42:30 -------------------------- 42 | val _: A^ => (x: C^) => C = y6 // error | ^^ - | Found: (y6 : A^ => (ex$70: caps.Exists) -> (x: C^) ->{ex$70} (ex$69: caps.Exists) -> C^{ex$69}) - | Required: A^ => (ex$73: caps.Exists) -> (x: C^) ->{ex$73} C + | Found: (y6 : A^ => (ex$70: scala.caps.Exists) -> (x: C^) ->{ex$70} (ex$69: scala.caps.Exists) -> C^{ex$69}) + | Required: A^ => (ex$73: scala.caps.Exists) -> (x: C^) ->{ex$73} C | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21313.check b/tests/neg-custom-args/captures/i21313.check index 37b944a97d68..f76f4bc6871e 100644 --- a/tests/neg-custom-args/captures/i21313.check +++ b/tests/neg-custom-args/captures/i21313.check @@ -5,7 +5,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21313.scala:15:12 --------------------------------------- 15 | ac1.await(src2) // error | ^^^^ - | Found: (src2 : Source[Int, caps.CapSet^{ac2}]^?) - | Required: Source[Int, caps.CapSet^{ac1}]^ + | Found: (src2 : Source[Int, scala.caps.CapSet^{ac2}]^?) + | Required: Source[Int, scala.caps.CapSet^{ac1}]^ | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21401.check b/tests/neg-custom-args/captures/i21401.check index e7483e10bfa6..05378bf57f23 100644 --- a/tests/neg-custom-args/captures/i21401.check +++ b/tests/neg-custom-args/captures/i21401.check @@ -21,7 +21,7 @@ -- Error: tests/neg-custom-args/captures/i21401.scala:17:52 ------------------------------------------------------------ 17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error | ^^^^^^^^^^^^^^^^^^^^^^^^ - |Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> (ex$20: caps.Exists) -> Boxed[box IO^{ex$20}] since + |Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> (ex$20: scala.caps.Exists) -> Boxed[box IO^{ex$20}] since |the part box IO^{ex$20} of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i21401.scala:18:21 ------------------------------------------------------------ 18 | val y: IO^{x*} = x.unbox // error diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index f4967253455f..2d1de6c57a0b 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -8,10 +8,10 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:15:12 --------------------------------------- 15 | files.map(new Logger(_)) // error, Q: can we improve the error message? | ^^^^^^^^^^^^^ - | Found: (_$1: box File^{files*}) ->{files*} (ex$16: caps.Exists) -> box Logger{val f: File^{_$1}}^{ex$16} - | Required: (_$1: box File^{files*}) => box Logger{val f: File^?}^? + |Found: (_$1: box File^{files*}) ->{files*} (ex$16: scala.caps.Exists) -> box Logger{val f: File^{_$1}}^{ex$16} + |Required: (_$1: box File^{files*}) => box Logger{val f: File^?}^? | - | Note that the universal capability `cap` - | cannot be included in capture set ? + |Note that the universal capability `cap` + |cannot be included in capture set ? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index be11aedd74ae..d6b9c46f71e0 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,10 +1,10 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ---------------------------------------------------- 14 | () => () => io // error | ^^ - | reference (io : Cap^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> () ->{io} (ex$7: caps.Exists) -> Cap^{ex$7} + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$7: scala.caps.Exists) -> Cap^{ex$7} -- Error: tests/neg-custom-args/captures/leaked-curried.scala:17:20 ---------------------------------------------------- 17 | () => () => io // error | ^^ - | reference (io : Cap^) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> () ->{io} (ex$15: caps.Exists) -> Cap^{ex$15} + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$15: scala.caps.Exists) -> Cap^{ex$15} diff --git a/tests/pos-custom-args/captures/untracked-captures.scala b/tests/pos-custom-args/captures/untracked-captures.scala index 7a090a5dd24f..f02dee607c01 100644 --- a/tests/pos-custom-args/captures/untracked-captures.scala +++ b/tests/pos-custom-args/captures/untracked-captures.scala @@ -1,4 +1,5 @@ -import caps.untrackedCaptures +import caps.unsafe.untrackedCaptures + class LL[+A] private (@untrackedCaptures lazyState: () => LL.State[A]^): private val res = lazyState() diff --git a/tests/pos/caps-universal.scala b/tests/pos/caps-universal.scala index 014955caaa87..3451866ed8f3 100644 --- a/tests/pos/caps-universal.scala +++ b/tests/pos/caps-universal.scala @@ -3,7 +3,7 @@ import annotation.retains val id: Int -> Int = (x: Int) => x val foo: Int => Int = id -val bar: (Int -> Int) @retains(caps.*) = foo +val bar: (Int -> Int) @retains(caps.cap) = foo diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 0715bedea201..3e7a4868b0cb 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -32,8 +32,19 @@ val experimentalDefinitionInLibrary = Set( "scala.annotation.retainsCap", "scala.annotation.retainsArg", "scala.Pure", - "scala.caps", - "scala.caps$", + "scala.caps.CapSet", + "scala.caps.Capability", + "scala.caps.Contains", + "scala.caps.Contains$", + "scala.caps.Contains$.containsImpl", + "scala.caps.Exists", + "scala.caps.internal", + "scala.caps.internal$", + "scala.caps.cap", + "scala.caps.cap$", + "scala.caps.unsafe", + "scala.caps.unsafe$", + "scala.caps.use", //// New feature: into "scala.annotation.into", From 2ee0c7e50b88357bb1cc29520345a2768aa62f20 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 9 Apr 2025 19:16:49 +0200 Subject: [PATCH 2/2] Mitigate change in status of scala.caps `scala.caps`` was an object until 3.6, it is a package from 3.7. Without special handling this would cause a TypeError to be thrown if a build has several versions of the Scala standard library on the classpath. This was the case for 29 projects in the open CB. These projects should be updated. But until that's the case we issue a warning instead of a hard failure. --- .../src/dotty/tools/dotc/core/SymbolLoaders.scala | 14 +++++++++++++- tests/neg/i22890.check | 7 +++++++ tests/neg/i22890/Test_2.scala | 3 +++ tests/neg/i22890/caps_1.java | 5 +++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i22890.check create mode 100644 tests/neg/i22890/Test_2.scala create mode 100644 tests/neg/i22890/caps_1.java diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 39297697f29a..6a9d70a19f49 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -78,7 +78,7 @@ object SymbolLoaders { * and give them `completer` as type. */ def enterPackage(owner: Symbol, pname: TermName, completer: (TermSymbol, ClassSymbol) => PackageLoader)(using Context): Symbol = { - val preExisting = owner.info.decls lookup pname + val preExisting = owner.info.decls.lookup(pname) if (preExisting != NoSymbol) // Some jars (often, obfuscated ones) include a package and // object with the same name. Rather than render them unusable, @@ -95,6 +95,18 @@ object SymbolLoaders { s"Resolving package/object name conflict in favor of object ${preExisting.fullName}. The package will be inaccessible.") return NoSymbol } + else if pname == nme.caps && owner == defn.ScalaPackageClass then + // `scala.caps`` was an object until 3.6, it is a package from 3.7. Without special handling + // this would cause a TypeError to be thrown below if a build has several versions of the + // Scala standard library on the classpath. This was the case for 29 projects in OpenCB. + // These projects should be updated. But until that's the case we issue a warning instead + // of a hard failure. + report.warning( + em"""$owner contains object and package with same name: $pname. + |This indicates that there are several versions of the Scala standard library on the classpath. + |The build should be reconfigured so that only one version of the standard library is on the classpath.""") + owner.info.decls.openForMutations.unlink(preExisting) + owner.info.decls.openForMutations.unlink(preExisting.moduleClass) else throw TypeError( em"""$owner contains object and package with same name: $pname diff --git a/tests/neg/i22890.check b/tests/neg/i22890.check new file mode 100644 index 000000000000..f5e37b10dac2 --- /dev/null +++ b/tests/neg/i22890.check @@ -0,0 +1,7 @@ +-- [E161] Naming Error: tests/neg/i22890/caps_1.java:3:0 --------------------------------------------------------------- +3 |class caps { // error: caps is already defined as package caps + |^ + |caps is already defined as package scala.caps +package scala contains object and package with same name: caps. +This indicates that there are several versions of the Scala standard library on the classpath. +The build should be reconfigured so that only one version of the standard library is on the classpath. diff --git a/tests/neg/i22890/Test_2.scala b/tests/neg/i22890/Test_2.scala new file mode 100644 index 000000000000..4545d5ab94ae --- /dev/null +++ b/tests/neg/i22890/Test_2.scala @@ -0,0 +1,3 @@ +@main def Test = + println("hello") +// nopos-warn diff --git a/tests/neg/i22890/caps_1.java b/tests/neg/i22890/caps_1.java new file mode 100644 index 000000000000..9bc804829932 --- /dev/null +++ b/tests/neg/i22890/caps_1.java @@ -0,0 +1,5 @@ +package scala; + +class caps { // error: caps is already defined as package caps + static public void foo() {} +} \ No newline at end of file