Skip to content

Commit

Permalink
Treat all type parameters and abstract types as sealed
Browse files Browse the repository at this point in the history
This means that no type parameter can be instantiated with a type that
captures cap covariantly or invariantly in its type.

Two exceptions/special cases:

 - Type arguments for isInstanceOf and asInstanceOf are excluded, they can capture cap
   anywhere.
 - Refining variables in class types can still contain cap since they describe what
   comes from the constructor.

Test reclassifications:

 - i15922.scala was moved to pending. Not clear whether this should compile, and what changes would be necessary to get it there.
 - future-traverse.scala was moved to pending. Not clear how to make this compiler.
 - i15749a.scala was moved to neg. The issue description seems to indicate that the test should not compile, but I am not sure what the outcome should be.
  • Loading branch information
odersky committed Nov 12, 2023
1 parent 8f87eef commit 996f70b
Show file tree
Hide file tree
Showing 18 changed files with 58 additions and 75 deletions.
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,13 @@ object CaptureSet:
override def toString = s"Var$id$elems"
end Var

/** Variables that represent refinements of class parameters can have the universal
* capture set, since they represent only what is the result of the constructor.
* Test case: Without that tweak, logger.scala would not compile.
*/
class RefiningVar(directOwner: Symbol)(using Context) extends Var(directOwner):
override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context) = this

/** A variable that is derived from some other variable via a map or filter. */
abstract class DerivedVar(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context)
extends Var(owner, initialElems):
Expand Down
17 changes: 1 addition & 16 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,22 +170,7 @@ object CheckCaptures:
t.dealiasKeepAnnots match
case t: TypeRef =>
if !seen.contains(t) then
capt.println(i"disallow $t, $tp, $what, ${t.isSealed}")
seen += t
t.info match
case TypeBounds(_, hi) if !t.isSealed && !t.symbol.isParametricIn(carrier) =>
if hi.isAny then
val detailStr =
if t eq tp then "variable"
else i"refers to the type variable $t, which"
report.error(
em"""$what cannot $have $tp since
|that type $detailStr is not sealed.
|$addendum""",
pos)
else
traverse(hi)
case _ =>
traverseChildren(t)
case AnnotatedType(_, ann) if ann.symbol == defn.UncheckedCapturesAnnot =>
()
Expand Down Expand Up @@ -557,7 +542,7 @@ class CheckCaptures extends Recheck, SymTransformer:
val polyType = atPhase(thisPhase.prev):
fn.tpe.widen.asInstanceOf[TypeLambda]
for case (arg: TypeTree, formal, pname) <- args.lazyZip(polyType.paramRefs).lazyZip((polyType.paramNames)) do
if formal.isSealed then
if !tree.symbol.isTypeTestOrCast then
def where = if fn.symbol.exists then i" in an argument of ${fn.symbol}" else ""
disallowRootCapabilitiesIn(arg.knownType, NoSymbol,
i"Sealed type variable $pname", "be instantiated to",
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
val getterType =
mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias
RefinedType(core, getter.name,
CapturingType(getterType, CaptureSet.Var(ctx.owner)))
CapturingType(getterType, CaptureSet.RefiningVar(ctx.owner)))
.showing(i"add capture refinement $tp --> $result", capt)
else
core
Expand Down
16 changes: 15 additions & 1 deletion tests/neg-custom-args/captures/capt1.check
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,22 @@
27 | def m() = if x == null then y else y
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg-custom-args/captures/capt1.scala:32:12 -------------------------------------------------------------
32 | val z2 = h[() -> Cap](() => x) // error // error
| ^^^^^^^^^^^^
| Sealed type variable X cannot be instantiated to () -> box C^ since
| the part box C^ of that type captures the root capability `cap`.
| This is often caused by a local capability in an argument of method h
| leaking as part of its result.
-- Error: tests/neg-custom-args/captures/capt1.scala:32:30 -------------------------------------------------------------
32 | val z2 = h[() -> Cap](() => x) // error
32 | val z2 = h[() -> Cap](() => x) // error // error
| ^
| (x : C^) cannot be referenced here; it is not included in the allowed capture set {}
| of an enclosing function literal with expected type () -> box C^
-- Error: tests/neg-custom-args/captures/capt1.scala:34:12 -------------------------------------------------------------
34 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
| Sealed type variable X cannot be instantiated to box () ->{x} Cap since
| the part C^ of that type captures the root capability `cap`.
| This is often caused by a local capability in an argument of method h
| leaking as part of its result.
5 changes: 2 additions & 3 deletions tests/neg-custom-args/captures/capt1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ def h4(x: Cap, y: Int): A =
def foo() =
val x: C @retains(caps.cap) = ???
def h[X](a: X)(b: X) = a
val z2 = h[() -> Cap](() => x) // error
val z2 = h[() -> Cap](() => x) // error // error
(() => C())
val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // ok
val z4 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // what was inferred for z3
val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ object u extends Unit

type Top = Any^

type Wrapper[T] = [X] -> (op: T ->{cap} X) -> X
type Wrapper[+T] = [X] -> (op: T ->{cap} X) -> X

def test =

Expand Down
11 changes: 7 additions & 4 deletions tests/neg-custom-args/captures/i15772.check
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
| Required: (C^ => Unit) -> Unit
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:33 ---------------------------------------
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:34 ---------------------------------------
33 | val boxed2 : Observe[C]^ = box2(c) // error
| ^^^^^^^
| Found: (C{val arg: C^}^ => Unit) ->? Unit
| Required: Observe[C]^
| ^
| Found: box C^
| Required: box C{val arg: C^?}^?
|
| Note that the universal capability `cap`
| cannot be included in capture set ?
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ----------------------------------------
Expand Down
10 changes: 5 additions & 5 deletions tests/neg-custom-args/captures/i16114.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ def withCap[T](op: Cap^ => T): T = {
def main(fs: Cap^): Unit = {
def badOp(io: Cap^): Unit ->{} Unit = {
val op1: Unit ->{io} Unit = (x: Unit) =>
expect[Cap^] {
expect[Cap^] { // error
io.use()
fs // error (limitation)
}

val op2: Unit ->{fs} Unit = (x: Unit) =>
expect[Cap^] {
expect[Cap^] { // error
fs.use()
io // error (limitation)
}

val op3: Unit ->{io} Unit = (x: Unit) => // ok
expect[Cap^] {
val op3: Unit ->{io} Unit = (x: Unit) =>
expect[Cap^] { // error
io.use()
io
}
Expand All @@ -34,7 +34,7 @@ def main(fs: Cap^): Unit = {
expect[Cap^](io) // error

val op: Unit -> Unit = (x: Unit) =>
expect[Cap^] {
expect[Cap^] { // error
io.use() // error
io // error
}
Expand Down
5 changes: 0 additions & 5 deletions tests/neg-custom-args/captures/levels.check
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
-- Error: tests/neg-custom-args/captures/levels.scala:6:16 -------------------------------------------------------------
6 | private var v: T = init // error
| ^
| Mutable variable v cannot have type T since
| that type variable is not sealed.
-- Error: tests/neg-custom-args/captures/levels.scala:17:13 ------------------------------------------------------------
17 | val _ = Ref[String => String]((x: String) => x) // error
| ^^^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-custom-args/captures/levels.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class CC
def test1(cap1: CC^) =

class Ref[T](init: T):
private var v: T = init // error
private var v: T = init
def setV(x: T): Unit = v = x
def getV: T = v

Expand Down
15 changes: 6 additions & 9 deletions tests/neg-custom-args/captures/outer-var.check
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,10 @@
| cannot be included in outer capture set {p} of variable y which is associated with method test
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:16:65 ------------------------------------
-- Error: tests/neg-custom-args/captures/outer-var.scala:16:53 ---------------------------------------------------------
16 | var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Found: scala.collection.mutable.ListBuffer[box () => Unit]
| Required: scala.collection.mutable.ListBuffer[box () ->? Unit]^?
|
| Note that the universal capability `cap`
| cannot be included in capture set ? of variable finalizeActions
|
| longer explanation available when compiling with `-explain`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Sealed type variable A cannot be instantiated to box () => Unit since
| that type captures the root capability `cap`.
| This is often caused by a local capability in an argument of method apply
| leaking as part of its result.
21 changes: 0 additions & 21 deletions tests/neg-custom-args/captures/sealed-classes.scala

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ def withCap[X](op: (Cap^) => X): X = {
def leaking(c: Cap^): Id[Cap^{c}] = mkId(c)

def test =
val bad = withCap(leaking)
val ll = (c: Cap^) => leaking(c)
val bad1 = withCap(ll) // used to work, now error (?)
val bad2 = withCap(leaking) // used to work, now error (?)
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ class ArrayBuffer[sealed A: ClassTag] extends Buffer[A]:
def at(i: Int): A = ???

class ArrayBufferBAD[A: ClassTag] extends Buffer[A]:
var elems: Array[A] = new Array[A](10) // error // error
var elems: Array[A] = new Array[A](10)
def add(x: A): this.type = ???
def at(i: Int): A = ???

object ArrayBuffer:
def make[A: ClassTag](xs: A*) = new ArrayBuffer: // error
def make[A: ClassTag](xs: A*) = new ArrayBuffer:
elems = xs.toArray
def apply[sealed A: ClassTag](xs: A*) = new ArrayBuffer:
elems = xs.toArray // ok

class EncapsArray[A: ClassTag]:
val x: Array[A] = new Array[A](10) // error
val x: Array[A] = new Array[A](10)



Expand Down
3 changes: 2 additions & 1 deletion tests/pos-custom-args/captures/foreach.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import annotation.unchecked.uncheckedCaptures
def test =
val tasks = new collection.mutable.ArrayBuffer[() => Unit]
val tasks = new collection.mutable.ArrayBuffer[(() => Unit) @uncheckedCaptures]
val _: Unit = tasks.foreach(((task: () => Unit) => task()))
3 changes: 2 additions & 1 deletion tests/pos-custom-args/captures/logger.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ class Pair[+A, +B](x: A, y: B):
def test2(ct: CanThrow[Exception], fs: FileSystem) =
def x: Int ->{ct} String = ???
def y: Logger^{fs} = ???
def p = Pair(x, y)
def p = Pair[Int ->{ct} String, Logger^{fs}](x, y)
def p3 = Pair(x, y)
def f = () => p.fst


Expand Down
4 changes: 2 additions & 2 deletions tests/pos-custom-args/captures/unsafe-unbox.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import annotation.unchecked.uncheckedCaptures
def test =
@uncheckedCaptures
var finalizeActions = collection.mutable.ListBuffer[() => Unit]()
val finalizeActionsInit = collection.mutable.ListBuffer[(() => Unit) @uncheckedCaptures]()
var finalizeActions = finalizeActionsInit
val action = finalizeActions.remove(0)


0 comments on commit 996f70b

Please sign in to comment.