Skip to content

Commit 95d23a4

Browse files
committed
Go back to original no cap in box/unbox restrictions
We go back to the original lifetime restriction that box/unbox cannot apply to universal capture sets, and drop the later restriction that type variable instantiations may not deeply capture cap. The original restriction is proven to be sound and is probably expressive enough when we add reach capabilities. This required some changes in tests and also in the standard library. The original restriction is in place for source <= 3.2 and >= 3.5. Source 3.3 and 3.4 use the alternative restriction on type variable instances. Some neg tests have not been brought forward to 3.4. They are all in tests/neg-customargs/captures and start with //> using options -source 3.4 We need to look at these tests one-by-one and analyze whether the new 3.5 behavior is correct.
1 parent 844537a commit 95d23a4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+310
-167
lines changed

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ object ccConfig:
3232
* previous global retriction that `cap` can't be boxed or unboxed.
3333
*/
3434
def allowUniversalInBoxed(using Context) =
35-
Feature.sourceVersion.isAtLeast(SourceVersion.`3.3`)
35+
Feature.sourceVersion.stable == SourceVersion.`3.3`
36+
|| Feature.sourceVersion.stable == SourceVersion.`3.4`
37+
//|| Feature.sourceVersion.stable == SourceVersion.`3.5` // drop `//` if you want to test with the sealed type params strategy
3638
end ccConfig
3739

3840

scala2-library-cc/src/scala/collection/Iterator.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ object Iterator extends IterableFactory[Iterator] {
10081008
def newBuilder[A]: Builder[A, Iterator[A]] =
10091009
new ImmutableBuilder[A, Iterator[A]](empty[A]) {
10101010
override def addOne(elem: A): this.type = { elems = elems ++ single(elem); this }
1011-
}
1011+
}.asInstanceOf // !!! CC unsafe op
10121012

10131013
/** Creates iterator that produces the results of some element computation a number of times.
10141014
*
@@ -1160,7 +1160,7 @@ object Iterator extends IterableFactory[Iterator] {
11601160
@tailrec def merge(): Unit =
11611161
if (current.isInstanceOf[ConcatIterator[_]]) {
11621162
val c = current.asInstanceOf[ConcatIterator[A]]
1163-
current = c.current
1163+
current = c.current.asInstanceOf // !!! CC unsafe op
11641164
currentHasNextChecked = c.currentHasNextChecked
11651165
if (c.tail != null) {
11661166
if (last == null) last = c.last

scala2-library-cc/src/scala/collection/SeqView.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,14 @@ object SeqView {
186186
}
187187

188188
@SerialVersionUID(3L)
189-
class Sorted[A, B >: A] private (private[this] var underlying: SomeSeqOps[A]^,
189+
class Sorted[A, B >: A] private (underlying: SomeSeqOps[A]^,
190190
private[this] val len: Int,
191191
ord: Ordering[B])
192192
extends SeqView[A] {
193193
outer: Sorted[A, B]^ =>
194194

195+
private var myUnderlying: SomeSeqOps[A]^{underlying} = underlying
196+
195197
// force evaluation immediately by calling `length` so infinite collections
196198
// hang on `sorted`/`sortWith`/`sortBy` rather than on arbitrary method calls
197199
def this(underlying: SomeSeqOps[A]^, ord: Ordering[B]) = this(underlying, underlying.length, ord)
@@ -221,10 +223,10 @@ object SeqView {
221223
val res = {
222224
val len = this.len
223225
if (len == 0) Nil
224-
else if (len == 1) List(underlying.head)
226+
else if (len == 1) List(myUnderlying.head)
225227
else {
226228
val arr = new Array[Any](len) // Array[Any] =:= Array[AnyRef]
227-
underlying.copyToArray(arr)
229+
myUnderlying.copyToArray(arr)
228230
java.util.Arrays.sort(arr.asInstanceOf[Array[AnyRef]], ord.asInstanceOf[Ordering[AnyRef]])
229231
// casting the Array[AnyRef] to Array[A] and creating an ArraySeq from it
230232
// is safe because:
@@ -238,12 +240,12 @@ object SeqView {
238240
}
239241
}
240242
evaluated = true
241-
underlying = null
243+
myUnderlying = null
242244
res
243245
}
244246

245247
private[this] def elems: SomeSeqOps[A]^{this} = {
246-
val orig = underlying
248+
val orig = myUnderlying
247249
if (evaluated) _sorted else orig
248250
}
249251

scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import scala.language.implicitConversions
2424
import scala.runtime.Statics
2525
import language.experimental.captureChecking
2626
import annotation.unchecked.uncheckedCaptures
27+
import caps.untrackedCaptures
2728

2829
/** This class implements an immutable linked list. We call it "lazy"
2930
* because it computes its elements only when they are needed.
@@ -245,14 +246,16 @@ import annotation.unchecked.uncheckedCaptures
245246
* @define evaluatesAllElements This method evaluates all elements of the collection.
246247
*/
247248
@SerialVersionUID(3L)
248-
final class LazyListIterable[+A] private(private[this] var lazyState: () => LazyListIterable.State[A]^)
249+
final class LazyListIterable[+A] private(@untrackedCaptures lazyState: () => LazyListIterable.State[A]^)
249250
extends AbstractIterable[A]
250251
with Iterable[A]
251252
with IterableOps[A, LazyListIterable, LazyListIterable[A]]
252253
with IterableFactoryDefaults[A, LazyListIterable]
253254
with Serializable {
254255
import LazyListIterable._
255256

257+
private var myLazyState = lazyState
258+
256259
@volatile private[this] var stateEvaluated: Boolean = false
257260
@inline private def stateDefined: Boolean = stateEvaluated
258261
private[this] var midEvaluation = false
@@ -264,11 +267,11 @@ final class LazyListIterable[+A] private(private[this] var lazyState: () => Lazy
264267
throw new RuntimeException("self-referential LazyListIterable or a derivation thereof has no more elements")
265268
}
266269
midEvaluation = true
267-
val res = try lazyState() finally midEvaluation = false
270+
val res = try myLazyState() finally midEvaluation = false
268271
// if we set it to `true` before evaluating, we may infinite loop
269272
// if something expects `state` to already be evaluated
270273
stateEvaluated = true
271-
lazyState = null // allow GC
274+
myLazyState = null // allow GC
272275
res
273276
}
274277

@@ -755,7 +758,7 @@ final class LazyListIterable[+A] private(private[this] var lazyState: () => Lazy
755758
* The iterator returned by this method mostly preserves laziness;
756759
* a single element ahead of the iterator is evaluated.
757760
*/
758-
override def grouped(size: Int): Iterator[LazyListIterable[A]] = {
761+
override def grouped(size: Int): Iterator[LazyListIterable[A]]^{this} = {
759762
require(size > 0, "size must be positive, but was " + size)
760763
slidingImpl(size = size, step = size)
761764
}
@@ -765,12 +768,12 @@ final class LazyListIterable[+A] private(private[this] var lazyState: () => Lazy
765768
* The iterator returned by this method mostly preserves laziness;
766769
* `size - step max 1` elements ahead of the iterator are evaluated.
767770
*/
768-
override def sliding(size: Int, step: Int): Iterator[LazyListIterable[A]] = {
771+
override def sliding(size: Int, step: Int): Iterator[LazyListIterable[A]]^{this} = {
769772
require(size > 0 && step > 0, s"size=$size and step=$step, but both must be positive")
770773
slidingImpl(size = size, step = step)
771774
}
772775

773-
@inline private def slidingImpl(size: Int, step: Int): Iterator[LazyListIterable[A]] =
776+
@inline private def slidingImpl(size: Int, step: Int): Iterator[LazyListIterable[A]]^{this} =
774777
if (knownIsEmpty) Iterator.empty
775778
else new SlidingIterator[A](this, size = size, step = step)
776779

@@ -996,7 +999,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
996999

9971000
private def filterImpl[A](ll: LazyListIterable[A]^, p: A => Boolean, isFlipped: Boolean): LazyListIterable[A]^{ll, p} = {
9981001
// DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD
999-
var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
1002+
var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
10001003
newLL {
10011004
var elem: A = null.asInstanceOf[A]
10021005
var found = false
@@ -1013,7 +1016,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
10131016

10141017
private def collectImpl[A, B](ll: LazyListIterable[A]^, pf: PartialFunction[A, B]^): LazyListIterable[B]^{ll, pf} = {
10151018
// DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD
1016-
var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
1019+
var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
10171020
newLL {
10181021
val marker = Statics.pfMarker
10191022
val toMarker = anyToMarker.asInstanceOf[A => B] // safe because Function1 is erased
@@ -1032,7 +1035,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
10321035

10331036
private def flatMapImpl[A, B](ll: LazyListIterable[A]^, f: A => IterableOnce[B]^): LazyListIterable[B]^{ll, f} = {
10341037
// DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD
1035-
var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
1038+
var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
10361039
newLL {
10371040
var it: Iterator[B]^{ll, f} = null
10381041
var itHasNext = false
@@ -1056,7 +1059,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
10561059

10571060
private def dropImpl[A](ll: LazyListIterable[A]^, n: Int): LazyListIterable[A]^{ll} = {
10581061
// DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD
1059-
var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
1062+
var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
10601063
var iRef = n // val iRef = new IntRef(n)
10611064
newLL {
10621065
var rest = restRef // var rest = restRef.elem
@@ -1073,7 +1076,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
10731076

10741077
private def dropWhileImpl[A](ll: LazyListIterable[A]^, p: A => Boolean): LazyListIterable[A]^{ll, p} = {
10751078
// DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD
1076-
var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
1079+
var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
10771080
newLL {
10781081
var rest = restRef // var rest = restRef.elem
10791082
while (!rest.isEmpty && p(rest.head)) {
@@ -1086,8 +1089,8 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
10861089

10871090
private def takeRightImpl[A](ll: LazyListIterable[A]^, n: Int): LazyListIterable[A]^{ll} = {
10881091
// DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD
1089-
var restRef: LazyListIterable[A]^{ll*} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
1090-
var scoutRef: LazyListIterable[A]^{ll*} = ll // same situation
1092+
var restRef: LazyListIterable[A]^{ll} = ll // restRef is captured by closure arg to newLL, so A is not recognized as parametric
1093+
var scoutRef: LazyListIterable[A]^{ll} = ll // same situation
10911094
var remainingRef = n // val remainingRef = new IntRef(n)
10921095
newLL {
10931096
var scout = scoutRef // var scout = scoutRef.elem
@@ -1236,33 +1239,35 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
12361239
*/
12371240
def newBuilder[A]: Builder[A, LazyListIterable[A]] = new LazyBuilder[A]
12381241

1239-
private class LazyIterator[+A](private[this] var lazyList: LazyListIterable[A]^) extends AbstractIterator[A] {
1240-
override def hasNext: Boolean = !lazyList.isEmpty
1242+
private class LazyIterator[+A](lazyList: LazyListIterable[A]^) extends AbstractIterator[A] {
1243+
private var myLazyList = lazyList
1244+
override def hasNext: Boolean = !myLazyList.isEmpty
12411245

12421246
override def next(): A =
1243-
if (lazyList.isEmpty) Iterator.empty.next()
1247+
if (myLazyList.isEmpty) Iterator.empty.next()
12441248
else {
1245-
val res = lazyList.head
1246-
lazyList = lazyList.tail
1249+
val res = myLazyList.head
1250+
myLazyList = myLazyList.tail
12471251
res
12481252
}
12491253
}
12501254

1251-
private class SlidingIterator[A](private[this] var lazyList: LazyListIterable[A]^, size: Int, step: Int)
1255+
private class SlidingIterator[A](lazyList: LazyListIterable[A]^, size: Int, step: Int)
12521256
extends AbstractIterator[LazyListIterable[A]] {
1257+
private var myLazyList = lazyList
12531258
private val minLen = size - step max 0
12541259
private var first = true
12551260

12561261
def hasNext: Boolean =
1257-
if (first) !lazyList.isEmpty
1258-
else lazyList.lengthGt(minLen)
1262+
if (first) !myLazyList.isEmpty
1263+
else myLazyList.lengthGt(minLen)
12591264

12601265
def next(): LazyListIterable[A] = {
12611266
if (!hasNext) Iterator.empty.next()
12621267
else {
12631268
first = false
1264-
val list = lazyList
1265-
lazyList = list.drop(step)
1269+
val list = myLazyList
1270+
myLazyList = list.drop(step)
12661271
list.take(size)
12671272
}
12681273
}
@@ -1281,7 +1286,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
12811286
import LazyBuilder._
12821287

12831288
private[this] var next: DeferredState[A] = _
1284-
private[this] var list: LazyListIterable[A] = _
1289+
@uncheckedCaptures private[this] var list: LazyListIterable[A]^ = _
12851290

12861291
clear()
12871292

tests/neg-custom-args/captures/box-adapt-cases.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ def test1(): Unit = {
44
type Id[X] = [T] -> (op: X => T) -> T
55

66
val x: Id[Cap^] = ???
7-
x(cap => cap.use()) // was error, now OK
7+
x(cap => cap.use()) // error, OK under sealed
88
}
99

1010
def test2(io: Cap^): Unit = {

tests/neg-custom-args/captures/capt-test.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ def handle[E <: Exception, R <: Top](op: (CT[E] @retains(caps.cap)) => R)(handl
2020
catch case ex: E => handler(ex)
2121

2222
def test: Unit =
23-
val b = handle[Exception, () => Nothing] { // error
23+
val b = handle[Exception, () => Nothing] {
2424
(x: CanThrow[Exception]) => () => raise(new Exception)(using x)
25-
} {
25+
} { // error
2626
(ex: Exception) => ???
2727
}

tests/neg-custom-args/captures/capt1.check

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,52 @@
1-
-- Error: tests/neg-custom-args/captures/capt1.scala:4:11 --------------------------------------------------------------
2-
4 | () => if x == null then y else y // error
1+
-- Error: tests/neg-custom-args/captures/capt1.scala:6:11 --------------------------------------------------------------
2+
6 | () => if x == null then y else y // error
33
| ^
44
| (x : C^) cannot be referenced here; it is not included in the allowed capture set {}
55
| of an enclosing function literal with expected type () -> C
6-
-- Error: tests/neg-custom-args/captures/capt1.scala:7:11 --------------------------------------------------------------
7-
7 | () => if x == null then y else y // error
6+
-- Error: tests/neg-custom-args/captures/capt1.scala:9:11 --------------------------------------------------------------
7+
9 | () => if x == null then y else y // error
88
| ^
99
| (x : C^) cannot be referenced here; it is not included in the allowed capture set {}
1010
| of an enclosing function literal with expected type Matchable
11-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:14:2 -----------------------------------------
12-
14 | def f(y: Int) = if x == null then y else y // error
11+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:16:2 -----------------------------------------
12+
16 | def f(y: Int) = if x == null then y else y // error
1313
| ^
1414
| Found: (y: Int) ->{x} Int
1515
| Required: Matchable
16-
15 | f
16+
17 | f
1717
|
1818
| longer explanation available when compiling with `-explain`
19-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:21:2 -----------------------------------------
20-
21 | class F(y: Int) extends A: // error
19+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:23:2 -----------------------------------------
20+
23 | class F(y: Int) extends A: // error
2121
| ^
2222
| Found: A^{x}
2323
| Required: A
24-
22 | def m() = if x == null then y else y
25-
23 | F(22)
24+
24 | def m() = if x == null then y else y
25+
25 | F(22)
2626
|
2727
| longer explanation available when compiling with `-explain`
28-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:26:2 -----------------------------------------
29-
26 | new A: // error
28+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:28:2 -----------------------------------------
29+
28 | new A: // error
3030
| ^
3131
| Found: A^{x}
3232
| Required: A
33-
27 | def m() = if x == null then y else y
33+
29 | def m() = if x == null then y else y
3434
|
3535
| longer explanation available when compiling with `-explain`
36-
-- Error: tests/neg-custom-args/captures/capt1.scala:32:12 -------------------------------------------------------------
37-
32 | val z2 = h[() -> Cap](() => x) // error // error
36+
-- Error: tests/neg-custom-args/captures/capt1.scala:34:12 -------------------------------------------------------------
37+
34 | val z2 = h[() -> Cap](() => x) // error // error
3838
| ^^^^^^^^^^^^
3939
| Sealed type variable X cannot be instantiated to () -> box C^ since
4040
| the part box C^ of that type captures the root capability `cap`.
4141
| This is often caused by a local capability in an argument of method h
4242
| leaking as part of its result.
43-
-- Error: tests/neg-custom-args/captures/capt1.scala:32:30 -------------------------------------------------------------
44-
32 | val z2 = h[() -> Cap](() => x) // error // error
43+
-- Error: tests/neg-custom-args/captures/capt1.scala:34:30 -------------------------------------------------------------
44+
34 | val z2 = h[() -> Cap](() => x) // error // error
4545
| ^
4646
| (x : C^) cannot be referenced here; it is not included in the allowed capture set {}
4747
| of an enclosing function literal with expected type () -> box C^
48-
-- Error: tests/neg-custom-args/captures/capt1.scala:34:12 -------------------------------------------------------------
49-
34 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error
48+
-- Error: tests/neg-custom-args/captures/capt1.scala:36:12 -------------------------------------------------------------
49+
36 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error
5050
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
5151
| Sealed type variable X cannot be instantiated to box () ->{x} Cap since
5252
| the part Cap of that type captures the root capability `cap`.

tests/neg-custom-args/captures/capt1.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//> using options -source 3.4
2+
// (to make sure we use the sealed policy)
13
import annotation.retains
24
class C
35
def f(x: C @retains(caps.cap), y: C): () -> C =
Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:62:8 -------------------------
2-
61 | Result:
3-
62 | Future: // error, type mismatch
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:64:8 -------------------------
2+
63 | Result:
3+
64 | Future: // error, type mismatch
44
| ^
55
| Found: Result.Ok[box Future[box T^?]^{fr, contextual$1}]
66
| Required: Result[Future[T], Nothing]
7-
63 | fr.await.ok
7+
65 | fr.await.ok
88
|--------------------------------------------------------------------------------------------------------------------
99
|Inline stack trace
1010
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
11-
|This location contains code that was inlined from effect-swaps-explicit.scala:39
12-
39 | boundary(Ok(body))
11+
|This location contains code that was inlined from effect-swaps-explicit.scala:41
12+
41 | boundary(Ok(body))
1313
| ^^^^^^^^
1414
--------------------------------------------------------------------------------------------------------------------
1515
|
1616
| longer explanation available when compiling with `-explain`
17-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:72:10 ------------------------
18-
72 | Future: fut ?=> // error: type mismatch
17+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:74:10 ------------------------
18+
74 | Future: fut ?=> // error: type mismatch
1919
| ^
2020
| Found: Future[box T^?]^{fr, lbl}
2121
| Required: Future[box T^?]^?
22-
73 | fr.await.ok
22+
75 | fr.await.ok
2323
|
2424
| longer explanation available when compiling with `-explain`
25-
-- Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:66:15 ---------------------------------------------
26-
66 | Result.make: //lbl ?=> // error, escaping label from Result
25+
-- Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:68:15 ---------------------------------------------
26+
68 | Result.make: //lbl ?=> // error, escaping label from Result
2727
| ^^^^^^^^^^^
2828
|local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^):
2929
| box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result

0 commit comments

Comments
 (0)