Skip to content

Backport "changes to scala.caps in preparation to make Capability stable" to 3.7.0 #22967

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
Apr 11, 2025
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
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -518,14 +518,17 @@ 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)

def makeRetaining(parent: Tree, refs: List[Tree], annotName: TypeName)(using Context): Annotated =
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 =
Expand Down
17 changes: 12 additions & 5 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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`.
Expand Down
14 changes: 13 additions & 1 deletion compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
69 changes: 0 additions & 69 deletions library/src/scala/caps.scala

This file was deleted.

106 changes: 106 additions & 0 deletions library/src/scala/caps/package.scala
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions tests/disabled/neg-custom-args/captures/capt-wf.scala
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/disabled/neg-custom-args/captures/try2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 6 additions & 6 deletions tests/neg-custom-args/captures/capt1.check
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
4 changes: 2 additions & 2 deletions tests/neg-custom-args/captures/cc-poly-1.check
Original file line number Diff line number Diff line change
@@ -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`
2 changes: 1 addition & 1 deletion tests/neg-custom-args/captures/cc-this2.check
Original file line number Diff line number Diff line change
Expand Up @@ -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
| ^
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-custom-args/captures/exception-definitions.check
Original file line number Diff line number Diff line change
@@ -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
| ^
Expand Down
Loading
Loading