I’ve released the first version of Allow Experimental, a small Scala 3 compiler plugin:
The motivation is to separate two ideas that Scala’s normal @experimental mechanism deliberately couples:
-
an API is experimental;
-
a consumer intentionally accepts the risk of using that API internally.
For example:
import scala.annotation.experimental
import io.github.dmytromitin.allowexperimental.allowExperimental
@experimental
def provider(): Int = 1
@allowExperimental
def allowed(): Int =
provider()
def ordinaryCaller(): Int =
allowed()
allowed may use the experimental API in its implementation, but callers of allowed do not themselves become experimental. A direct unmarked call to provider() still fails with the normal Scala experimental-use diagnostic.
The release currently supports exactly Scala 3.3.8, 3.8.4, and 3.9.0. The compiler plugin is full-crossed because it depends on compiler internals.
ThisBuild / scalaVersion := "3.9.0" // 3.3.8, 3.8.4
libraryDependencies ++= Seq(
"com.github.dmytromitin" %% "allow-experimental-annotation" % "0.1.0" % Provided,
compilerPlugin(("com.github.dmytromitin" % "allow-experimental-plugin" % "0.1.0").cross(CrossVersion.full)),
)
One practical use case is macro implementations: a public inline macro frontend can delegate to a private non-inline @allowExperimental implementation that uses an experimental compiler/reflection API, without leaking that requirement to downstream users.
The scope is intentionally conservative rather than a general replacement for scalacOptions += "-experimental". Public inline permission owners, experimental signatures/types, constructors, and several other placements remain unsupported.
0.1.0 is available from Maven Central and the release is at Github.
Feedback on the semantics, implementation approach, and useful real-world cases would be very welcome.
Cross-posted at Reddit