Skip to content
Closed
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
20 changes: 17 additions & 3 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -553,10 +553,13 @@ extension (sym: Symbol)
* - or it is a value class
* - or it is an exception
* - or it is one of Nothing, Null, or String
* Arrays are not pure under strict mutability even though their self type is declared pure
* in Arrays.scala.
*/
def isPureClass(using Context): Boolean = sym match
case cls: ClassSymbol =>
cls.pureBaseClass.isDefined || defn.pureSimpleClasses.contains(cls)
(cls.pureBaseClass.isDefined || defn.pureSimpleClasses.contains(cls))
&& !(cls == defn.ArrayClass && ccConfig.strictMutability)
case _ =>
false

Expand Down Expand Up @@ -588,8 +591,8 @@ extension (sym: Symbol)
&& !defn.isPolymorphicAfterErasure(sym)
&& !defn.isTypeTestOrCast(sym)

/** It's a parameter accessor that is not annotated @constructorOnly or @uncheckedCaptures
* and that is not a consume accessor.
/** It's a parameter accessor for a parameter that that is not annotated
* @constructorOnly or @uncheckedCaptures and that is not a consume parameter.
*/
def isRefiningParamAccessor(using Context): Boolean =
sym.is(ParamAccessor)
Expand All @@ -600,6 +603,17 @@ extension (sym: Symbol)
&& !param.hasAnnotation(defn.ConsumeAnnot)
}

/** It's a parameter accessor that is tracked for capture checking. Excluded are
* accessors for parameters annotated with constructorOnly or @uncheckedCaptures.
*/
def isTrackedParamAccessor(using Context): Boolean =
sym.is(ParamAccessor)
&& {
val param = sym.owner.primaryConstructor.paramNamed(sym.name)
!param.hasAnnotation(defn.ConstructorOnlyAnnot)
&& !param.hasAnnotation(defn.UntrackedCapturesAnnot)
}

def hasTrackedParts(using Context): Boolean =
!CaptureSet.ofTypeDeeply(sym.info).isAlwaysEmpty

Expand Down
28 changes: 24 additions & 4 deletions compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -666,13 +666,33 @@ object CaptureSet:
then i" under-approximating the result of mapping $ref to $mapped"
else ""

private def capImpliedByCapability(parent: Type)(using Context): Capability =
if parent.derivesFromStateful then GlobalCap.readOnly else GlobalCap
private def capImpliedByCapability(parent: Type, sym: Symbol, variance: Int)(using Context): Capability =
// Since standard library classes are not compiled with separation checking,
// they treat Array as a Pure class. That means, no effort is made to distinguish
// between exclusive and read-only arrays. To compensate in code compiled under
// strict mutability, we treat contravariant arrays in signatures of stdlib
// members as read-only (so all arrays may be passed to them), and co- and
// invariant arrays as exclusive.
// TODO This scheme should also apply whenever code under strict mutability interfaces
// with code compiled without. To do that we will need to store in the Tasty format
// a flag whether code was compiled with separation checking on. This will have
// to wait until 3.10.
def isArrayFromScalaPackage =
parent.classSymbol == defn.ArrayClass
&& ccConfig.strictMutability
&& variance >= 0
&& sym.isContainedIn(defn.ScalaPackageClass)
if parent.derivesFromStateful && !isArrayFromScalaPackage
then GlobalCap.readOnly
else GlobalCap

/* The same as {cap} but generated implicitly for references of Capability subtypes.
* @param parent the type to which the capture set will be attached
* @param sym the symbol carrying that type
* @param variance the variance in which `parent` appears in the type of `sym`
*/
class CSImpliedByCapability(parent: Type)(using @constructorOnly ctx: Context)
extends Const(SimpleIdentitySet(capImpliedByCapability(parent)))
class CSImpliedByCapability(parent: Type, sym: Symbol, variance: Int)(using @constructorOnly ctx: Context)
extends Const(SimpleIdentitySet(capImpliedByCapability(parent, sym, variance)))

/** A special capture set that gets added to the types of symbols that were not
* themselves capture checked, in order to admit arbitrary corresponding capture
Expand Down
26 changes: 2 additions & 24 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1410,9 +1410,7 @@ class CheckCaptures extends Recheck, SymTransformer:

// (3) Capture set of self type includes capture sets of parameters
for param <- cls.paramGetters do
if !param.hasAnnotation(defn.ConstructorOnlyAnnot)
&& !param.hasAnnotation(defn.UntrackedCapturesAnnot)
then
if param.isTrackedParamAccessor then
withCapAsRoot: // OK? We need this here since self types use `cap` instead of `fresh`
checkSubset(param.termRef.captureSet, thisSet, param.srcPos)

Expand Down Expand Up @@ -1581,7 +1579,7 @@ class CheckCaptures extends Recheck, SymTransformer:
* where local capture roots are instantiated to root variables.
*/
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, notes: List[Note])(using Context): Type =
try testAdapted(actual, expected, tree, notes: List[Note])(err.typeMismatch)
try testAdapted(actual, expected, tree, notes)(err.typeMismatch)
catch case ex: AssertionError =>
println(i"error while checking $tree: $actual against $expected")
throw ex
Expand Down Expand Up @@ -2148,26 +2146,6 @@ class CheckCaptures extends Recheck, SymTransformer:
checker.traverse(tree.nuType)
end checkTypeParam

/** Under the unsealed policy: Arrays are like vars, check that their element types
* do not contains `cap` (in fact it would work also to check on array creation
* like we do under sealed).
*/
def checkArraysAreSealedIn(tp: Type, pos: SrcPos)(using Context): Unit =
val check = new TypeTraverser:
def traverse(t: Type): Unit =
t match
case AppliedType(tycon, arg :: Nil) if tycon.typeSymbol == defn.ArrayClass =>
if !(pos.span.isSynthetic && ctx.reporter.errorsReported)
&& !arg.typeSymbol.name.is(WildcardParamName)
then
disallowBadRootsIn(arg, NoSymbol, "Array", "have element type", "", pos)
traverseChildren(t)
case defn.RefinedFunctionOf(rinfo: MethodType) =>
traverse(rinfo)
case _ =>
traverseChildren(t)
check.traverse(tp)

/** Check that no uses refer to reach capabilities of parameters of enclosing
* methods or classes.
*/
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/Mutability.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import config.Printers.capt
import config.Feature
import ast.tpd.Tree
import typer.ProtoTypes.LhsProto
import StdNames.nme

/** Handling mutability and read-only access
*/
Expand Down Expand Up @@ -59,6 +60,7 @@ object Mutability:
&& !sym.field.hasAnnotation(defn.UntrackedCapturesAnnot)
else true
)
|| ccConfig.strictMutability && sym.name == nme.update && sym == defn.Array_update

/** A read-only member is a lazy val or a method that is not an update method. */
def isReadOnlyMember(using Context): Boolean =
Expand Down
12 changes: 11 additions & 1 deletion compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ object Setup:
case _ => false
case _ => None

def patchArrayClass()(using Context): Unit =
if ccConfig.strictMutability then
val arrayCls = defn.ArrayClass.asClass
arrayCls.info match
case oldInfo: ClassInfo if !arrayCls.derivesFrom(defn.Caps_Mutable) =>
arrayCls.info = oldInfo.derivedClassInfo(
declaredParents = oldInfo.declaredParents :+ defn.Caps_Mutable.typeRef)
arrayCls.invalidateBaseDataCache()
case _ =>

end Setup
import Setup.*

Expand Down Expand Up @@ -425,7 +435,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
then
normalizeCaptures(mapOver(t)) match
case t1 @ CapturingType(_, _) => t1
case t1 => CapturingType(t1, CaptureSet.CSImpliedByCapability(t1), boxed = false)
case t1 => CapturingType(t1, CaptureSet.CSImpliedByCapability(t1, sym, variance), boxed = false)
else normalizeCaptures(mapFollowingAliases(t))

def innerApply(t: Type) =
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/cc/ccConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@ object ccConfig:
def newScheme(using ctx: Context): Boolean =
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`)

/** Allow @use annotations */
def allowUse(using Context): Boolean =
Feature.sourceVersion.stable.isAtMost(SourceVersion.`3.7`)


/** Treat arrays as mutable types. Enabled under separation checking */
def strictMutability(using Context): Boolean =
Feature.enabled(Feature.separationChecking)

end ccConfig
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1886,7 +1886,7 @@ object SymDenotations {
myBaseTypeCache.nn
}

private def invalidateBaseDataCache() = {
def invalidateBaseDataCache() = {
baseDataCache.invalidate()
baseDataCache = BaseData.None
}
Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import config.Feature
import reporting.*
import Message.Note
import collection.mutable
import cc.isCaptureChecking

object ErrorReporting {

Expand Down Expand Up @@ -190,7 +191,13 @@ object ErrorReporting {
case _ =>
Nil

errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), notes ++ missingElse))
def badTreeNote =
val span = tree.span
if tree.span.isZeroExtent && isCaptureChecking then
Note(i"\n\nThe error occurred for a synthesized tree: $tree") :: Nil
else Nil

errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), notes ++ missingElse ++ badTreeNote))
}

/** A subtype log explaining why `found` does not conform to `expected` */
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1251,7 +1251,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
typedAhead(parent, tree => inferTypeParams(typedType(tree), pt))
val anon = tpnme.ANON_CLASS
val clsDef = TypeDef(anon, templ1).withFlags(Final | Synthetic)
typed(cpy.Block(tree)(clsDef :: Nil, New(Ident(anon), Nil)), pt)
typed(
cpy.Block(tree)(
clsDef :: Nil,
New(Ident(anon), Nil).withSpan(tree.span)),
pt)
case _ =>
var tpt1 = typedType(tree.tpt)
val tsym = tpt1.tpe.underlyingClassRef(refinementOK = false).typeSymbol
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/typer/TyperPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class TyperPhase(addRootImports: Boolean = true) extends Phase {
val unit = ctx.compilationUnit
try
if !unit.suspended then ctx.profiler.onUnit(ctx.phase, unit):
if ctx.run.nn.ccEnabledSomewhere then cc.Setup.patchArrayClass()
unit.tpdTree = ctx.typer.typedExpr(unit.untpdTree)
typr.println("typed: " + unit.source)
record("retained untyped trees", unit.untpdTree.treeSize)
Expand Down
6 changes: 3 additions & 3 deletions library/src/scala/Array.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ object Array {
case _ => it.iterator.toArray[A]
}

private def slowcopy(src : AnyRef,
private def slowcopy(src : AnyRef^,
srcPos : Int,
dest : AnyRef,
dest : AnyRef^,
destPos : Int,
length : Int): Unit = {
var i = srcPos
Expand Down Expand Up @@ -107,7 +107,7 @@ object Array {
*
* @see `java.lang.System#arraycopy`
*/
def copy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = {
def copy(src: AnyRef^, srcPos: Int, dest: AnyRef^, destPos: Int, length: Int): Unit = {
val srcClass = src.getClass
val destClass = dest.getClass
if (srcClass.isArray && ((destClass eq srcClass) ||
Expand Down
4 changes: 2 additions & 2 deletions library/src/scala/Predef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
package scala

import scala.language.`2.13`
import language.experimental.captureChecking
import scala.language.implicitConversions

import scala.collection.{mutable, immutable, ArrayOps, StringOps}, immutable.WrappedString
import scala.annotation.{experimental, implicitNotFound, publicInBinary, targetName, nowarn }
import scala.annotation.meta.{ companionClass, companionMethod }
Expand Down Expand Up @@ -552,7 +552,7 @@ object Predef extends LowPriorityImplicits {
* `(A => A) <: (A => B)`.
*/
// $ to avoid accidental shadowing (e.g. scala/bug#7788)
implicit def $conforms[A]: A => A = <:<.refl
implicit def $conforms[A]: A -> A = <:<.refl.asInstanceOf

// Extension methods for working with explicit nulls

Expand Down
2 changes: 2 additions & 0 deletions tests/neg-custom-args/captures/boundary.check
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
|
| Note that capability cap is not included in capture set {cap²}.
|
| The error occurred for a synthesized tree: _
|
| where: ^ and cap² refer to the universal root capability
| ^² and cap refer to a fresh root capability created in package <empty>
6 | boundary[Unit]: l2 ?=>
Expand Down
13 changes: 7 additions & 6 deletions tests/neg-custom-args/captures/capt1.check
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@
| Note that capability x is not included in capture set {}.
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:28:40 ----------------------------------------
28 | def m() = if x == null then y else y // error
| ^
| Found: A^{x}
| Required: A
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:27:2 -----------------------------------------
27 | new A: // error
| ^
| Found: A^{x}
| Required: A
|
| Note that capability x is not included in capture set {}.
| Note that capability x is not included in capture set {}.
28 | def m() = if x == null then y else y
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:36:24 ----------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions tests/neg-custom-args/captures/capt1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def h3(x: Cap): A =
F(22) // error

def h4(x: Cap, y: Int): A =
new A:
def m() = if x == null then y else y // error
new A: // error
def m() = if x == null then y else y

def f1(c: Cap): () ->{c} c.type = () => c // ok

Expand Down
4 changes: 2 additions & 2 deletions tests/neg-custom-args/captures/existential-mapping.check
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
-- Error: tests/neg-custom-args/captures/existential-mapping.scala:46:10 -----------------------------------------------
46 | val z2: (x: A^) => Array[C^] = ??? // error
| ^^^^^^^^^^^^^^^^^^^^
| Array[C^] captures the root capability `cap` in invariant position.
| Array[C^]^{cap.rd} captures the root capability `cap` in invariant position.
| This capability cannot be converted to an existential in the result type of a function.
|
| where: ^ refers to the universal root capability
| where: ^ and cap refer to the universal root capability
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/existential-mapping.scala:9:25 ---------------------------
9 | val _: (x: C^) -> C = x1 // error
| ^^
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-custom-args/captures/freeze.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import caps.*

class Arr[T: reflect.ClassTag](len: Int) extends Mutable:
private val arr: Array[T] = new Array[T](len)
private val arr: Array[T]^ = new Array[T](len)
def get(i: Int): T = arr(i)
update def update(i: Int, x: T): Unit = arr(i) = x

Expand Down
4 changes: 2 additions & 2 deletions tests/neg-custom-args/captures/method-uses.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def test3(xs: List[() => Unit]): () ->{xs*} Unit = () =>
def test4(xs: List[() => Unit]) = () => xs.head // error, ok under deferredReaches

def test5(xs: List[() => Unit]) = new:
println(xs.head) // error, ok under deferredReaches // error
println(xs.head) // error, ok under deferredReaches

def test6(xs: List[() => Unit]) =
val x= new { println(xs.head) } // error // error
val x= new { println(xs.head) } // error
x
23 changes: 7 additions & 16 deletions tests/neg-custom-args/captures/mut-iterator4.check
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
-- Error: tests/neg-custom-args/captures/mut-iterator4.scala:9:26 ------------------------------------------------------
9 | update def next() = f(Iterator.this.next()) // error // error
9 | update def next() = f(Iterator.this.next()) // error
| ^^^^^^^^^^^^^
| Read-only method map accesses exclusive capability (Iterator.this : Iterator[T]^);
| method map should be declared an update method to allow this.
|
| where: ^ refers to the universal root capability
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/mut-iterator4.scala:9:47 ---------------------------------
9 | update def next() = f(Iterator.this.next()) // error // error
| ^
|Found: Iterator[U^'s1]^{Iterator.this.rd, f, Iterator.this, cap}
|Required: Iterator[U]^{Iterator.this, f}
|
|Note that capability cap is not included in capture set {Iterator.this, f}.
|
|where: cap is a fresh root capability created in method map when constructing instance Object with (Iterator[U]^{cap².rd}) {...}
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/mut-iterator4.scala:23:34 --------------------------------
23 | update def next() = f(it.next()) // error
| ^
|Found: Iterator[U^'s2]^{it.rd, f, it, cap}
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/mut-iterator4.scala:21:81 --------------------------------
21 |def mappedIterator[T, U](it: Iterator[T]^, f: T => U): Iterator[U]^{it, f} = new Iterator: // error
| ^
|Found: Iterator[U^'s1]^{it.rd, f, it, cap}
|Required: Iterator[U]^{it, f}
|
|Note that capability cap is not included in capture set {it, f}.
|
|where: cap is a fresh root capability created in method mappedIterator when constructing instance Object with (Iterator[U]^{cap².rd}) {...}
22 | def hasNext = it.hasNext
23 | update def next() = f(it.next())
|
| longer explanation available when compiling with `-explain`
Loading
Loading