Skip to content

Commit 192eee5

Browse files
committed
Special handling of experimental.captureChecking import
The question is, how do we introduce capture checking safely. There are two conflicting requirements: - Capture checking should not leak into standard Scala. It should be meaningful only in code that has capture checking explicitly enabled. - We need to be able to build up libraries that express capture information and that can be consumed from other code. This needs to start with the standard library itself. So far, everything related to capture checking was marked experimental. This means all code that refers to a capture checking abstraction in any way whatsoever needs to be declared experimental. That clearly does not work for the new use cases. But fortunately, capture checking has some properties that enable a different scheme. Specifically, a file compiled under capture checking looks like a completely normal component (both Tasty and binary) to any other file that uses it and that is not compiled with captureChecking. Only when the consumer is also compiled with capture checking, the capturing types of the original file will be revealed. The same holds for binaries. Capture checking has no effect at all on the binaries that get generated and all types and annotations needed for capture checking are erased. This allows the following more flexible scheme: - We can turn capture checking on with a setting or language import in any source file. The sources do not have to be @experimental. - If capture checking is turned on, a number of annotations and other symbols that are normally experimental are also made available. The important property is that capture checking in one component cannot poison other normal components. Like @experimental itself, the whole scheme is transitive. With the new scheme we do not need a special exemption for the dotty package anymore, so that part is dropped.
1 parent 881524b commit 192eee5

File tree

5 files changed

+27
-3
lines changed

5 files changed

+27
-3
lines changed

compiler/src/dotty/tools/dotc/config/Feature.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,11 @@ object Feature:
136136
if !isExperimentalEnabled then
137137
report.error(em"Experimental $which may only be used with a nightly or snapshot version of the compiler$note", srcPos)
138138

139+
private def ccException(sym: Symbol)(using Context): Boolean =
140+
ccEnabled && defn.ccExperimental.contains(sym)
141+
139142
def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) =
140-
if !isExperimentalEnabled then
143+
if !isExperimentalEnabled && !ccException(sym) then
141144
val symMsg =
142145
if sym.hasAnnotation(defn.ExperimentalAnnot) then
143146
i"$sym is marked @experimental"

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,14 @@ class Definitions {
19431943
case Some(pkgs) => pkgs.contains(sym.owner)
19441944
case none => false
19451945

1946+
/** Experimental definitions that can nevertheless be accessed from a stable
1947+
* compiler if capture checking is enabled.
1948+
*/
1949+
@tu lazy val ccExperimental: Set[Symbol] = Set(
1950+
CapsModule, CapsModule.moduleClass, PureClass,
1951+
CapabilityAnnot, RequiresCapabilityAnnot,
1952+
RetainsAnnot, RetainsByNameAnnot, WithPureFunsAnnot)
1953+
19461954
// ----- primitive value class machinery ------------------------------------------
19471955

19481956
class PerRun[T](generate: Context ?=> T) {

compiler/src/dotty/tools/dotc/core/TypeErasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ object TypeErasure {
410410
val candidates = takeUntil(tp2superclasses)(!_.is(Trait))
411411

412412
// Candidates st "no other common superclass or trait derives from S"
413-
// Also, drop `PairClass` since it is not valid after erasue
413+
// Also, drop `PairClass` since it is not valid after erasure
414414
val minimums = candidates.filter { cand =>
415415
cand != defn.PairClass
416416
&& candidates.forall(x => !x.derivesFrom(cand) || x.eq(cand))

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,9 @@ object Checking {
779779
for case imp @ Import(qual, selectors) <- trees do
780780
def isAllowedImport(sel: untpd.ImportSelector) =
781781
val name = Feature.experimental(sel.name)
782-
name == Feature.scala2macros || name == Feature.erasedDefinitions
782+
name == Feature.scala2macros
783+
|| name == Feature.erasedDefinitions
784+
|| name == Feature.captureChecking
783785

784786
languageImport(qual) match
785787
case Some(nme.experimental)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala.runtime
2+
3+
import language.experimental.captureChecking
4+
5+
object test:
6+
type T = Pure
7+
8+
class Foo extends Object, Pure:
9+
val x: Pure = ???
10+
def foo() = ()
11+

0 commit comments

Comments
 (0)