Skip to content

Special handling of experimental.captureChecking import #17427

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 6 commits into from
May 30, 2023
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
21 changes: 13 additions & 8 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,21 @@ object Feature:
if !isExperimentalEnabled then
report.error(em"Experimental $which may only be used with a nightly or snapshot version of the compiler$note", srcPos)

private def ccException(sym: Symbol)(using Context): Boolean =
ccEnabled && defn.ccExperimental.contains(sym)

def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) =
if !isExperimentalEnabled then
val symMsg =
if sym.hasAnnotation(defn.ExperimentalAnnot) then
i"$sym is marked @experimental"
else if sym.owner.hasAnnotation(defn.ExperimentalAnnot) then
i"${sym.owner} is marked @experimental"
else
i"$sym inherits @experimental"
report.error(em"$symMsg and therefore may only be used in an experimental scope.", srcPos)
val experimentalSym =
if sym.hasAnnotation(defn.ExperimentalAnnot) then sym
else if sym.owner.hasAnnotation(defn.ExperimentalAnnot) then sym.owner
else NoSymbol
if !ccException(experimentalSym) then
val symMsg =
if experimentalSym.exists
then i"$experimentalSym is marked @experimental"
else i"$sym inherits @experimental"
report.error(em"$symMsg and therefore may only be used in an experimental scope.", srcPos)

/** Check that experimental compiler options are only set for snapshot or nightly compiler versions. */
def checkExperimentalSettings(using Context): Unit =
Expand Down
30 changes: 21 additions & 9 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,8 @@ class Definitions {
@tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg")
@tu lazy val Caps_SealedAnnot: ClassSymbol = requiredClass("scala.caps.Sealed")

@tu lazy val PureClass: Symbol = requiredClass("scala.Pure")

// Annotation base classes
@tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation")
@tu lazy val StaticAnnotationClass: ClassSymbol = requiredClass("scala.annotation.StaticAnnotation")
Expand Down Expand Up @@ -1944,6 +1946,14 @@ class Definitions {
case Some(pkgs) => pkgs.contains(sym.owner)
case none => false

/** Experimental definitions that can nevertheless be accessed from a stable
* compiler if capture checking is enabled.
*/
@tu lazy val ccExperimental: Set[Symbol] = Set(
CapsModule, CapsModule.moduleClass, PureClass,
CapabilityAnnot, RequiresCapabilityAnnot,
RetainsAnnot, RetainsByNameAnnot, WithPureFunsAnnot)

// ----- primitive value class machinery ------------------------------------------

class PerRun[T](generate: Context ?=> T) {
Expand Down Expand Up @@ -2041,15 +2051,17 @@ class Definitions {
def isValueSubClass(sym1: Symbol, sym2: Symbol): Boolean =
valueTypeEnc(sym2.asClass.name) % valueTypeEnc(sym1.asClass.name) == 0

@tu lazy val specialErasure: SimpleIdentityMap[Symbol, ClassSymbol] =
SimpleIdentityMap.empty[Symbol]
.updated(AnyClass, ObjectClass)
.updated(MatchableClass, ObjectClass)
.updated(AnyValClass, ObjectClass)
.updated(SingletonClass, ObjectClass)
.updated(TupleClass, ProductClass)
.updated(NonEmptyTupleClass, ProductClass)
.updated(PairClass, ObjectClass)
@tu lazy val specialErasure: collection.Map[Symbol, ClassSymbol] =
val m = mutable.Map[Symbol, ClassSymbol]()
Copy link
Contributor

Choose a reason for hiding this comment

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

The map changes from an immutable one to a mutable one. Shall we freeze it after initializing it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can't be changed from outside, since it is declared as a collection.Map. So in that sense it is frozen.

m(AnyClass) = ObjectClass
m(MatchableClass) = ObjectClass
m(PureClass) = ObjectClass
m(AnyValClass) = ObjectClass
m(SingletonClass) = ObjectClass
m(TupleClass) = ProductClass
m(NonEmptyTupleClass) = ProductClass
m(PairClass) = ObjectClass
m

// ----- Initialization ---------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ object TypeErasure {
val candidates = takeUntil(tp2superclasses)(!_.is(Trait))

// Candidates st "no other common superclass or trait derives from S"
// Also, drop `PairClass` since it is not valid after erasue
// Also, drop `PairClass` since it is not valid after erasure
val minimums = candidates.filter { cand =>
cand != defn.PairClass
&& candidates.forall(x => !x.derivesFrom(cand) || x.eq(cand))
Expand Down
14 changes: 8 additions & 6 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,14 @@ class Erasure extends Phase with DenotTransformer {
def assertErased(tp: Type, tree: tpd.Tree = tpd.EmptyTree)(using Context): Unit = {
def isAllowed(cls: Symbol, sourceName: String) =
tp.typeSymbol == cls && ctx.compilationUnit.source.file.name == sourceName
assert(isErasedType(tp) ||
isAllowed(defn.ArrayClass, "Array.scala") ||
isAllowed(defn.TupleClass, "Tuple.scala") ||
isAllowed(defn.NonEmptyTupleClass, "Tuple.scala") ||
isAllowed(defn.PairClass, "Tuple.scala"),
i"The type $tp - ${tp.toString} of class ${tp.getClass} of tree $tree : ${tree.tpe} / ${tree.getClass} is illegal after erasure, phase = ${ctx.phase.prev}")
assert(
isErasedType(tp)
|| isAllowed(defn.ArrayClass, "Array.scala")
|| isAllowed(defn.TupleClass, "Tuple.scala")
|| isAllowed(defn.NonEmptyTupleClass, "Tuple.scala")
|| isAllowed(defn.PairClass, "Tuple.scala")
|| isAllowed(defn.PureClass, "Pure.scala"),
i"The type $tp - ${tp.toString} of class ${tp.getClass} of tree $tree : ${tree.tpe} / ${tree.getClass} is illegal after erasure, phase = ${ctx.phase.prev}")
}
}

Expand Down
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,6 @@ object SymUtils:
self.hasAnnotation(defn.ExperimentalAnnot)
|| isDefaultArgumentOfExperimentalMethod
|| (!self.is(Package) && self.owner.isInExperimentalScope)
|| self.topLevelClass.ownersIterator.exists(p =>
p.is(Package) && p.owner.isRoot && p.name == tpnme.dotty)

/** The declared self type of this class, as seen from `site`, stripping
* all refinements for opaque types.
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,9 @@ object Checking {
for case imp @ Import(qual, selectors) <- trees do
def isAllowedImport(sel: untpd.ImportSelector) =
val name = Feature.experimental(sel.name)
name == Feature.scala2macros || name == Feature.erasedDefinitions
name == Feature.scala2macros
|| name == Feature.erasedDefinitions
|| name == Feature.captureChecking

languageImport(qual) match
case Some(nme.experimental)
Expand Down
9 changes: 9 additions & 0 deletions library/src/scala/Pure.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package scala
import annotation.experimental

/** A marker trait that declares that all inheriting classes are "pure" in the
* sense that their values retain no capabilities including capabilities needed
* to perform effects. This has formal meaning only under capture checking.
*/
@experimental trait Pure:
this: Pure =>
6 changes: 0 additions & 6 deletions library/src/scala/caps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,3 @@ import annotation.experimental
*/
@deprecated("The Sealed annotation should not be directly used in source code.\nUse the `sealed` modifier on type parameters instead.")
class Sealed extends annotation.Annotation

/** Mixing in this trait forces a trait or class to be pure, i.e.
* have no capabilities retained in its self type.
*/
trait Pure:
this: Pure =>
4 changes: 2 additions & 2 deletions tests/neg-custom-args/captures/selftype.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@annotation.experimental class C(x: () => Unit) extends caps.Pure // error
@annotation.experimental class C(x: () => Unit) extends Pure // error

@annotation.experimental class D(@annotation.constructorOnly x: () => Unit) extends caps.Pure // ok
@annotation.experimental class D(@annotation.constructorOnly x: () => Unit) extends Pure // ok

11 changes: 11 additions & 0 deletions tests/pos-custom-args/no-experimental/cc-experimental.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package scala.runtime

import language.experimental.captureChecking

object test:
type T = Pure

class Foo extends Object, Pure:
val x: Pure = ???
def foo() = ()

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dotty.tools
import language.experimental.captureChecking
object test {

val x = caps.cap
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/backend/jvm/BCodeHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
}
} // end of trait BCPickles

trait BCInnerClassGen extends caps.Pure {
trait BCInnerClassGen extends Pure {

def debugLevel = 3 // 0 -> no debug info; 1-> filename; 2-> lines; 3-> varnames

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import dotty.tools.dotc.report
* @version 1.0
*
*/
trait BCodeIdiomatic extends caps.Pure {
trait BCodeIdiomatic extends Pure {
val int: DottyBackendInterface
final lazy val bTypes = new BTypesFromSymbols[int.type](int)

Expand Down
4 changes: 2 additions & 2 deletions tests/pos-with-compiler-cc/backend/jvm/BTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import scala.tools.asm
* This representation is immutable and independent of the compiler data structures, hence it can
* be queried by concurrent threads.
*/
abstract class BTypes extends caps.Pure {
abstract class BTypes extends Pure {

val int: DottyBackendInterface
import int.given
Expand Down Expand Up @@ -47,7 +47,7 @@ abstract class BTypes extends caps.Pure {
* A BType is either a primitve type, a ClassBType, an ArrayBType of one of these, or a MethodType
* referring to BTypes.
*/
/*sealed*/ trait BType extends caps.Pure { // Not sealed for now due to SI-8546
/*sealed*/ trait BType extends Pure { // Not sealed for now due to SI-8546
final override def toString: String = this match {
case UNIT => "V"
case BOOL => "Z"
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/backend/sjs/ScopedVar.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dotty.tools.backend.sjs

class ScopedVar[A](init: A) extends caps.Pure {
class ScopedVar[A](init: A) extends Pure {
import ScopedVar.Assignment

private[ScopedVar] var value = init
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/ast/Positioned.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import annotation.internal.sharable

/** A base class for things that have positions (currently: modifiers and trees)
*/
abstract class Positioned(implicit @constructorOnly src: SourceFile) extends SrcPos, Product, Cloneable, caps.Pure {
abstract class Positioned(implicit @constructorOnly src: SourceFile) extends SrcPos, Product, Cloneable, Pure {
import Positioned.{ids, nextId, debugId}

private var mySpan: Span = _
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import annotation.retains
* if the mapped function is either a bijection or if it is idempotent
* on capture references (c.f. doc comment on `map` below).
*/
sealed abstract class CaptureSet extends Showable, caps.Pure:
sealed abstract class CaptureSet extends Showable, Pure:
import CaptureSet.*

/** The elements of this capture set. For capture variables,
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object Annotations {
if (tree.symbol.isConstructor) tree.symbol.owner
else tree.tpe.typeSymbol

abstract class Annotation extends Showable, caps.Pure {
abstract class Annotation extends Showable, Pure {

def tree(using Context): Tree

Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/core/CheckRealizable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import annotation.constructorOnly
/** Realizability status */
object CheckRealizable {

sealed abstract class Realizability(val msg: String) extends caps.Pure {
sealed abstract class Realizability(val msg: String) extends Pure {
def andAlso(other: => Realizability): Realizability =
if (this == Realizable) other else this
def mapError(f: Realizability -> Context ?-> Realizability)(using Context): Realizability =
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ object Denotations {
/** A PreDenotation represents a group of single denotations or a single multi-denotation
* It is used as an optimization to avoid forming MultiDenotations too eagerly.
*/
abstract class PreDenotation extends caps.Pure {
abstract class PreDenotation extends Pure {

/** A denotation in the group exists */
def exists: Boolean
Expand Down
4 changes: 2 additions & 2 deletions tests/pos-with-compiler-cc/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ object NameKinds {
@sharable private val uniqueNameKinds = util.HashMap[String, UniqueNameKind]()

/** A class for the info stored in a derived name */
abstract class NameInfo extends caps.Pure {
abstract class NameInfo extends Pure {
def kind: NameKind
def mkString(underlying: TermName): String
def map(f: SimpleName => SimpleName): NameInfo = this
}

/** An abstract base class of classes that define the kind of a derived name info */
abstract class NameKind(val tag: Int) extends caps.Pure { self =>
abstract class NameKind(val tag: Int) extends Pure { self =>

/** The info class defined by this kind */
type ThisInfo <: Info
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object Names {
* in a name table. A derived term name adds a tag, and possibly a number
* or a further simple name to some other name.
*/
abstract class Name extends Designator, Showable, caps.Pure derives CanEqual {
abstract class Name extends Designator, Showable, Pure derives CanEqual {

/** A type for names of the same kind as this name */
type ThisName <: Name
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ object Phases {
final def isTyper(phase: Phase): Boolean = phase.id == typerPhase.id
}

abstract class Phase extends caps.Pure {
abstract class Phase extends Pure {

/** A name given to the `Phase` that can be used to debug the compiler. For
* instance, it is possible to print trees after a given phase using:
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/core/Scopes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ object Scopes {
* or to delete them. These methods are provided by subclass
* MutableScope.
*/
abstract class Scope extends printing.Showable, caps.Pure {
abstract class Scope extends printing.Showable, Pure {

/** The last scope-entry from which all others are reachable via `prev` */
private[dotc] def lastEntry: ScopeEntry | Null
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/core/TypeErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import ast.untpd
import config.Printers.cyclicErrors
import language.experimental.pureFunctions

abstract class TypeError(using creationContext: DetachedContext) extends Exception(""), caps.Pure:
abstract class TypeError(using creationContext: DetachedContext) extends Exception(""), Pure:

/** Convert to message. This takes an additional Context, so that we
* use the context when the message is first produced, i.e. when the TypeError
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ object Types {
*
* Note: please keep in sync with copy in `docs/docs/internals/type-system.md`.
*/
abstract class Type extends Hashable, printing.Showable, caps.Pure {
abstract class Type extends Hashable, printing.Showable, Pure {

// ----- Tests -----------------------------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import annotation.retains

object ClassfileParser {
/** Marker trait for unpicklers that can be embedded in classfiles. */
trait Embedded extends caps.Pure
trait Embedded extends Pure

/** Indicate that there is nothing to unpickle and the corresponding symbols can
* be invalidated. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ object Completion {
/** Temporary data structure representing denotations with the same name introduced in a given scope
* as a member of a type, by a local definition or by an import clause
*/
private case class ScopedDenotations(denots: Seq[SingleDenotation], ctx: DetachedContext) extends caps.Pure
private case class ScopedDenotations(denots: Seq[SingleDenotation], ctx: DetachedContext) extends Pure

/**
* The completion mode: defines what kinds of symbols should be included in the completion
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1554,7 +1554,7 @@ object Scanners {
* InBraces a pair of braces { ... }
* Indented a pair of <indent> ... <outdent> tokens
*/
abstract class Region(val closedBy: Token) extends caps.Pure:
abstract class Region(val closedBy: Token) extends Pure:

/** The region enclosing this one, or `null` for the outermost region */
def outer: Region | Null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object Highlighting {
else mod + super.show
}

case class HighlightBuffer(hl: Highlight)(using DetachedContext) extends caps.Pure {
case class HighlightBuffer(hl: Highlight)(using DetachedContext) extends Pure {
private val buffer = new mutable.ListBuffer[String]

buffer += hl.show
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/printing/Printer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import scala.annotation.internal.sharable

/** The base class of all printers
*/
abstract class Printer extends caps.Pure {
abstract class Printer extends Pure {

private var prec: Precedence = GlobalPrec

Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/profile/AsyncHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import java.util.concurrent.atomic.AtomicInteger
import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.core.Contexts._

sealed trait AsyncHelper extends caps.Pure {
sealed trait AsyncHelper extends Pure {

def newUnboundedQueueFixedThreadPool
(nThreads: Int,
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-with-compiler-cc/dotc/profile/Profiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ case class ProfileRange(start: ProfileSnap, end:ProfileSnap, phase:Phase, purpos
def retainedHeapMB: Double = toMegaBytes(end.heapBytes - start.heapBytes)
}

sealed trait Profiler extends caps.Pure {
sealed trait Profiler extends Pure {

def finished(): Unit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class CapturedVars extends MiniPhase with IdentityDenotTransformer:
override def initContext(ctx: FreshContext): Unit =
Captured = ctx.addLocation(util.ReadOnlySet.empty)

private class RefInfo(using DetachedContext) extends caps.Pure {
private class RefInfo(using DetachedContext) extends Pure {
/** The classes for which a Ref type exists. */
val refClassKeys: collection.Set[Symbol] =
defn.ScalaNumericValueClasses() `union` Set(defn.BooleanClass, defn.ObjectClass)
Expand Down
Loading