Skip to content

Commit 2454760

Browse files
Add Scala 3.8.3 release notes (#1907)
* Add Scala 3.8.3 release notes * Cleanup the code snippets * Shorter coverage exclusions headline * Link to the Tracking Capabilities for Safer Agents paper * Adjust the assumeSafe example to show that it's posible to mark only selective methods as safe * No significant performance boost Co-authored-by: Seth Tisue <seth@tisue.net> --------- Co-authored-by: Seth Tisue <seth@tisue.net>
1 parent bbcae45 commit 2454760

3 files changed

Lines changed: 137 additions & 2 deletions

File tree

_data/scala-releases.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
- category: current_version
22
title: Current 3.8.x release
3-
version: 3.8.2
4-
release_date: February 24, 2026
3+
version: 3.8.3
4+
release_date: March 31, 2026
55
- category: current_version
66
title: Current 3.3.x LTS release
77
version: 3.3.7

_downloads/2026-03-31-3.8.3.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
title: Scala 3.8.3
3+
start: 31 March 2026
4+
layout: downloadpage
5+
release_version: 3.8.3
6+
release_date: "March 31, 2026"
7+
permalink: /download/3.8.3.html
8+
license: <a href="https://www.scala-lang.org/license/">Apache License, Version 2.0</a>
9+
api_docs: https://www.scala-lang.org/api/3.8.3/
10+
---
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
category: release
3+
permalink: /news/3.8.3/
4+
title: "Scala 3.8.3 is now available!"
5+
by: Wojciech Mazur, VirtusLab
6+
---
7+
# Scala 3.8.3 is now available!
8+
9+
## Release highlights
10+
11+
### Local coverage exclusions with `// $COVERAGE-OFF$` blocks ([#24486](https://github.com/scala/scala3/pull/24486))
12+
13+
Coverage-instrumented builds can now disable coverage for a selected region of code, instead of excluding a whole file or class. This is useful for generated code, intentionally defensive branches, or support code that would otherwise distort coverage results.
14+
15+
```scala
16+
//> using scala 3.8.3
17+
//> using options --coverage-out coverage-data
18+
19+
class Parser:
20+
def parse(input: String): Int =
21+
input.toInt
22+
23+
// $COVERAGE-OFF$
24+
def debugFallback(input: String): Int =
25+
if input == "zero" then 0
26+
else -1
27+
// $COVERAGE-ON$
28+
29+
@main def CoverageTest = {
30+
val parser = Parser()
31+
assert(parser.parse("42") == 42)
32+
}
33+
```
34+
35+
Only the code between the markers is skipped by coverage instrumentation. The rest of the file is still measured as usual.
36+
37+
### Safe mode for capability-safe code ([#25307](https://github.com/scala/scala3/pull/25307))
38+
39+
Scala 3.8.3 introduces **safe mode**, a new experimental language subset that can be enabled with `import language.experimental.safe` or `-language:experimental.safe`. As described in the [safe mode reference](https://www.scala-lang.org/api/3.8.3/docs/experimental/capture-checking/safe.html), this is not just “stricter capture checking”: it is a capability-safe subset intended for agent-generated or otherwise untrusted code.
40+
41+
The underlying model is also described in the research paper [Tracking Capabilities for Safer Agents](https://arxiv.org/abs/2603.00991), which proposes using Scala 3 with capture checking as a programming-language-based safety harness for AI agents.
42+
43+
When safe mode is enabled, the compiler rejects unchecked casts and unchecked pattern matches, forbids escape hatches such as `caps.unsafe`, `@unchecked`, and runtime reflection, turns on capture checking with mutation tracking, and restricts access to global APIs unless they are known-safe or explicitly reviewed.
44+
45+
That last point is what makes the feature practical. Safe code is meant to call a restricted set of APIs directly, while effectful or implementation-dependent behavior can still be exposed through wrappers marked `@assumeSafe`. The implementation in [#25307](https://github.com/scala/scala3/pull/25307) makes that boundary explicit: `@assumeSafe` declarations are themselves written outside safe mode, and safe code calls them from within the restricted subset.
46+
47+
48+
```scala
49+
// app.scala
50+
51+
//> using scala 3.8.3
52+
//> using file CheckedMailer.scala
53+
//> using options -experimental
54+
55+
import language.experimental.safe
56+
57+
object PotentiallyUnsafeApp:
58+
val address = EmailAddress("team@scala-lang.org")
59+
CheckedMailer.send(address) // ok
60+
println(address) // error: rejected in safe mode
61+
address.asInstanceOf[String] // error: rejected in safe mode
62+
address match
63+
case EmailAddress(rawAddress) => ??? // error: rejected in safe mode
64+
```
65+
66+
67+
```scala
68+
// CheckedMailer.scala
69+
70+
import scala.caps.assumeSafe
71+
72+
@assumeSafe
73+
object CheckedMailer:
74+
def send(to: EmailAddress) =
75+
scala.Console.out.println(s"Sending message to $to")
76+
77+
opaque type EmailAddress <: String = String
78+
79+
object EmailAddress:
80+
@assumeSafe def apply(value: String): EmailAddress = value
81+
def unapply(value: EmailAddress): Option[String] = Some(value)
82+
```
83+
84+
In the example above, the safe code in `app.scala` can call `CheckedMailer.send`, but the effectful operation is isolated behind an `@assumeSafe` boundary. By contrast, direct calls to `println`, unchecked `asInstanceOf` casts, or `scala.caps.unsafe` helpers are rejected in safe mode.
85+
86+
### Scala 2 JVM optimizer ported to Scala 3 ([#25165](https://github.com/scala/scala3/pull/25165))
87+
88+
Scala 3 now includes the port of the Scala 2 JVM backend optimizer. The optimizer is opt-in: compiler flag `-opt` enables local bytecode optimizations, while `-opt-inline:...` controls which classes and packages may be inlined across call sites. This brings Scala 3 to feature parity with the Scala 2 optimizer and opens the door to performance gains for JVM applications.
89+
90+
Rather than enabling blanket inlining everywhere, it is usually better to start from explicit filters. The `-opt-inline` setting accepts a comma-separated list of patterns; `**` matches all classes, `a.**` matches a package and its subpackages, `<sources>` matches classes compiled in the current run, and a leading `!` excludes matches. The last matching pattern wins.
91+
92+
```scala
93+
//> using scala 3.8.3
94+
//> using options -opt
95+
//> using options "-opt-inline:<sources>,my.app.**,!java.**,!org.example.**"
96+
//> using options "-Wopt:at-inline-failed-summary,no-inline-missing-bytecode"
97+
```
98+
99+
In this configuration, the optimizer may inline code from the current compilation (`<sources>`) and from `my.app` subpackages defined in external dependencies, but not from the JDK or the `org.example` packages. This is often a good starting point for applications. For libraries, the conservative choice is usually to inline only from `<sources>` or from packages you fully control.
100+
101+
The optimizer port also brings additional settings:
102+
103+
- `-Wopt:...` enables optimizer warnings. Available choices are `all` or `at-inline-failed-summary`, `at-inline-failed`, `any-inline-failed`, `no-inline-mixed`, `no-inline-missing-bytecode`, and `no-inline-missing-attribute`
104+
- `-Yopt-specific:...` enables individual optimization passes such as `copy-propagation`, `box-unbox`, `nullness-tracking`, `closure-invocations`, or `redundant-casts`
105+
- `-Yopt-inline-heuristics:default|everything|at-inline-annotated` adjusts how aggressively the compiler chooses call sites for inlining
106+
- `-Yopt-log-inline:<prefix>` logs inliner activity for matching methods
107+
- `-Yopt-trace:<prefix>` traces optimizer progress for matching methods
108+
109+
The `-Wopt` options let you choose between a one-line summary for failed `@inline` calls, detailed per-callsite diagnostics, reporting for heuristic inlining failures, and warnings for cases where inlining could not even be decided because bytecode or Scala inline metadata was unavailable.
110+
111+
> The `-Y...` optimizer flags are primarily intended for debugging and internal use. As with other `-Y` settings, they are not stable user-facing interfaces, and their exact behavior may change between releases.
112+
113+
As with the Scala 2 optimizer, inlining external code comes with a compatibility trade-off: if you compile against one version of a dependency and later run against a different one, any inlined bytecode will not pick up the dependency's runtime bug fixes or behavior changes. In practice, that means aggressive cross-library inlining is best reserved for applications with tightly controlled runtime classpaths. Read more about binary compatibility of optimized code in the [Scala 2 optimizer documentation](https://docs.scala-lang.org/overviews/compiler-options/optimizer.html#binary-compatibility)
114+
115+
The long-term plan is to build on this work in Scala 3.9 by enabling optimizations for the Scala standard library and the compiler itself.
116+
117+
### `-print-lines` is deprecated for removal, but remains accepted as a no-op ([#25330](https://github.com/scala/scala3/pull/25330))
118+
119+
Scala 3.8.3 restores the `-print-lines` flag for compatibility, but only as a deprecated no-op. This avoids breaking existing builds in a patch release while giving users time to remove the setting from their build definitions.
120+
121+
The flag no longer has any effect and is scheduled for removal in Scala 3.9.0.
122+
123+
---
124+
125+
For the complete list of changes and contributor credits, see the [release notes on GitHub](https://github.com/scala/scala3/releases/tag/3.8.3).

0 commit comments

Comments
 (0)