Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ title: "Capability Polymorphism"
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/advanced.html
---

```scala sc:nocompile sc-name:preamble
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9/10 of preamble snippets are empty. Is it necessary to include them?
If so maybe instead of adding a dedicated parsing of scalac options for given paths, we can just add import language.experimental.captrueChecking only in the preamble and not add additional options?

At the same time I think all of the modified snippets are explicit disabled with sc:nocompile, is it expected? Are there any CC docs snippets being actually compiled after that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in the PR description, this is to set up the infrastructure. I've tested a handful of code snippets, but most of them have to be adapted to be compilable. That should be done in a separate PR.

Copy link
Contributor Author

@bracevac bracevac Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: I tried putting the experimental language imports in the snippets directly, but I get the same error as in the REPL, i.e., that this import isn't allowed. So we have to pass them as compiler options.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be due to the way how markdown snippets are compiled. Multiple snippets might be squashed to a single targer source that would be compiled. That's why in https://github.com/WojciechMazur/dotty/blob/0c477928e4c438cba594b7251ca8a274e0a064a4/docs/_docs/reference/error-codes/E223.md?plain=1#L19-L32 we've been passing them to scalac options directlly.

Advanced use cases.

## Access Control
Analogously to type parameters, we can lower- and upper-bound capability parameters where the bounds consist of concrete capture sets:
```scala
```scala sc:nocompile sc-compile-with:preamble
def main() =
// We can close over anything branded by the 'trusted' capability, but nothing else
def runSecure[C^ >: {trusted} <: {trusted}](block: () ->{C} Unit): Unit = ...
Expand Down Expand Up @@ -40,15 +43,15 @@ The idea is that every capability derived from the marker capability `trusted` (
passed to `runSecure`. We can enforce this by an explicit capability parameter `C` constraining the possible captures of `block` to the interval `>: {trusted} <: {trusted}`.

Note that since capabilities of function types are covariant, we could have equivalently specified `runSecure`'s signature using implicit capture polymorphism to achieve the same behavior:
```scala
```scala sc:nocompile sc-compile-with:preamble
def runSecure(block: () ->{trusted} Unit): Unit
```

## Capture-safe Lexical Control

Capability members and paths to these members can prevent leakage
of labels for lexically-delimited control operators:
```scala
```scala sc:nocompile sc-compile-with:preamble
trait Label extends Capability:
type Fv^ // the capability set occurring freely in the `block` passed to `boundary` below.

Expand Down
40 changes: 23 additions & 17 deletions docs/_docs/reference/experimental/capture-checking/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ title: "Capture Checking Basics"
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/basics.html
---

```scala sc:nocompile sc-name:preamble
```

## Introduction

Capture checking can be enabled by the language import
```scala
```scala sc:nocompile sc-compile-with:preamble
import language.experimental.captureChecking
```
At present, capture checking is still highly experimental and unstable, and it evolves quickly.
Before trying it out, make sure you have the latest version of Scala.

To get an idea what capture checking can do, let's start with a small example:
```scala
//{
import java.io.FileOutputStream
//}
def usingLogFile[T](op: FileOutputStream => T): T =
val logFile = FileOutputStream("log")
val result = op(logFile)
Expand All @@ -27,7 +33,7 @@ operation's result is returned. This is a typical _try-with-resources_ pattern,
The problem is that `usingLogFile`'s implementation is not entirely safe. One can
undermine it by passing an operation that performs the logging at some later point
after it has terminated. For instance:
```scala
```scala sc:nocompile sc-compile-with:preamble
val later = usingLogFile { file => () => file.write(0) }
later() // crash
```
Expand All @@ -36,7 +42,7 @@ results in an uncaught `IOException`.

Capture checking gives us the mechanism to prevent such errors _statically_. To
prevent unsafe usages of `usingLogFile`, we can declare it like this:
```scala
```scala sc:nocompile sc-compile-with:preamble
def usingLogFile[T](op: FileOutputStream^ => T): T =
// same body as before
```
Expand All @@ -54,13 +60,13 @@ If we now try to define the problematic value `later`, we get a static error:
```
In this case, it was easy to see that the `logFile` capability escapes in the closure passed to `usingLogFile`. But capture checking also works for more complex cases.
For instance, capture checking is able to distinguish between the following safe code:
```scala
```scala sc:nocompile sc-compile-with:preamble
val xs = usingLogFile { f =>
List(1, 2, 3).map { x => f.write(x); x * x }
}
```
and the following unsafe one:
```scala
```scala sc:nocompile sc-compile-with:preamble
val xs = usingLogFile { f =>
LzyList(1, 2, 3).map { x => f.write(x); x * x }
}
Expand Down Expand Up @@ -100,7 +106,7 @@ capability gets its authority from some other, more sweeping capability which it
If `T` is a type, then `T^` is a shorthand for `T^{any}`, meaning `T` can capture arbitrary capabilities.

Here is an example:
```scala
```scala sc:nocompile sc-compile-with:preamble
class FileSystem

class Logger(fs: FileSystem^):
Expand Down Expand Up @@ -155,20 +161,20 @@ capabilities in a method are instead counted in the capture set of the enclosing
## By-Name Parameter Types

A convention analogous to function types also extends to by-name parameters. In
```scala
```scala sc:nocompile sc-compile-with:preamble
def f(x: => Int): Int
```
the actual argument can refer to arbitrary capabilities. So the following would be OK:
```scala
```scala sc:nocompile sc-compile-with:preamble
f(if p(y) then throw Ex() else 1)
```
On the other hand, if `f` was defined like this
```scala
```scala sc:nocompile sc-compile-with:preamble
def f(x: -> Int): Int
```
the actual argument to `f` could not refer to any capabilities, so the call above would be rejected.
One can also allow specific capabilities like this:
```scala
```scala sc:nocompile sc-compile-with:preamble
def f(x: ->{c} Int): Int
```
Here, the actual argument to `f` is allowed to use the `c` capability but no others.
Expand All @@ -184,7 +190,7 @@ Lazy vals receive special treatment under capture checking, similar to parameter

When a lazy val is declared, its initializer is checked in its own environment (like a method body). The initializer can capture capabilities, and these are tracked separately:

```scala
```scala sc:nocompile sc-compile-with:preamble
def example(console: Console^) =
lazy val x: () -> String =
console.println("Computing x") // console captured by initializer
Expand All @@ -202,7 +208,7 @@ The type system tracks that accessing `x` requires the `console` capability, eve

When accessing a lazy val member through a qualifier, the qualifier is charged to the current capture set, just like calling a parameterless method:

```scala
```scala sc:nocompile sc-compile-with:preamble
trait Container:
lazy val lazyMember: String

Expand All @@ -217,7 +223,7 @@ Accessing `c.lazyMember` can trigger initialization, which may use capabilities

For capture checking purposes, lazy vals behave identically to parameterless methods:

```scala
```scala sc:nocompile sc-compile-with:preamble
trait T:
def methodMember: String
lazy val lazyMember: String
Expand Down Expand Up @@ -250,7 +256,7 @@ A subcapturing relation `C₁ <: C₂` holds if `C₂` _accounts for_ every elem


**Example 1.** Given
```scala
```scala sc:nocompile sc-compile-with:preamble
fs: FileSystem^
ct: CanThrow[Exception]^
l : Logger^{fs}
Expand Down Expand Up @@ -283,7 +289,7 @@ A type extending `SharedCapability` always comes with a capture set. If no captu

This means we could equivalently express the `FileSystem` and `Logger` classes as follows:

```scala
```scala sc:nocompile sc-compile-with:preamble
import caps.SharedCapability

class FileSystem extends SharedCapability
Expand All @@ -307,7 +313,7 @@ can contain only capabilities that are visible at the point where the set is def

We now reconstruct how this principle produced the error in the introductory example, where
`usingLogFile` was declared like this:
```scala
```scala sc:nocompile sc-compile-with:preamble
def usingLogFile[T](op: FileOutputStream^ => T): T = ...
```
The error message was:
Expand Down Expand Up @@ -337,7 +343,7 @@ An analogous restriction applies to the type of a mutable variable.
Another way one could try to undermine capture checking would be to
assign a closure with a local capability to a global variable. Maybe
like this:
```scala
```scala sc:nocompile sc-compile-with:preamble
var loophole: () => Unit = () => ()
usingLogFile { f =>
loophole = () => f.write(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ title: "Checked Exceptions"
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/checked-exceptions.html
---

```scala sc:nocompile sc-name:preamble
```

## Introduction

Scala enables checked exceptions through a language import. Here is an example,
taken from the [safer exceptions page](../canthrow.md), and also described in a
[paper](https://infoscience.epfl.ch/record/290885) presented at the
2021 Scala Symposium.
```scala
```scala sc:nocompile sc-compile-with:preamble
import language.experimental.saferExceptions

class LimitExceeded extends Exception
Expand All @@ -22,11 +25,11 @@ def f(x: Double): Double throws LimitExceeded =
The new `throws` clause expands into an implicit parameter that provides
a `CanThrow` capability. Hence, function `f` could equivalently be written
like this:
```scala
```scala sc:nocompile sc-compile-with:preamble
def f(x: Double)(using CanThrow[LimitExceeded]): Double = ...
```
If the implicit parameter is missing, an error is reported. For instance, the function definition
```scala
```scala sc:nocompile sc-compile-with:preamble
def g(x: Double): Double =
if x < limit then x * x else throw LimitExceeded()
```
Expand All @@ -42,12 +45,12 @@ is rejected with this error message:
```
`CanThrow` capabilities are required by `throw` expressions and are created
by `try` expressions. For instance, the expression
```scala
```scala sc:nocompile sc-compile-with:preamble
try xs.map(f).sum
catch case ex: LimitExceeded => -1
```
would be expanded by the compiler to something like the following:
```scala
```scala sc:nocompile sc-compile-with:preamble
try
erased given ctl: CanThrow[LimitExceeded] = compiletime.erasedValue
xs.map(f).sum
Expand All @@ -58,7 +61,7 @@ erased.)

As with other capability based schemes, one needs to guard against capabilities
that are captured in results. For instance, here is a problematic use case:
```scala
```scala sc:nocompile sc-compile-with:preamble
def escaped(xs: Double*): (() => Double) throws LimitExceeded =
try () => xs.map(f).sum
catch case ex: LimitExceeded => () => -1
Expand Down
Loading
Loading