Skip to content

Commit bac3769

Browse files
committed
Do not go inside boxes for freezing
1 parent 8e033af commit bac3769

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -800,14 +800,30 @@ class CheckCaptures extends Recheck, SymTransformer:
800800
capt.println(i"rechecking unsafeAssumePure of $arg with $pt: $argType")
801801
super.recheckFinish(argType, tree, pt)
802802

803-
/** Recheck `caps.freeze(...)` */
803+
/** Recheck `caps.freeze(...)`.
804+
*
805+
* Capture-check the body of a `freeze` operation and transforms its
806+
* result to be immutable.
807+
*
808+
* `freeze` takes an operation of type `-> T`, where `T` is generic.
809+
* It leverages the fact that the operation is pure, therefore the returned
810+
* mutable types in `T` must be freshly created. It is thus safe to freeze the
811+
* mutable parts in `T` into its immutable version. */
804812
def applyFreeze(tree: Apply)(using Context): Type =
805813
val arg :: Nil = tree.args: @unchecked
806814
def imm = new TypeMap:
807815
def apply(t: Type) = t match
808-
case t @ CapturingType(parent, _)
809-
if parent.derivesFromMutable && variance > 0 =>
810-
t.derivedCapturingType(apply(parent), CaptureSet.emptyOfMutable)
816+
case t @ CapturingType(parent, refs) =>
817+
// If the capturing type is boxed, we skip it. Since it could capture
818+
// existing values of a mutable type without charging anything to the
819+
// operation passed to `freeze`.
820+
val boxed = t.isBoxed
821+
val parent1 = if boxed then parent else apply(parent)
822+
val refs1 =
823+
if !boxed && parent.derivesFromMutable && variance > 0 then
824+
CaptureSet.emptyOfMutable
825+
else refs
826+
t.derivedCapturingType(parent1, refs1)
811827
case _ =>
812828
mapOver(t)
813829
val opProto = // () ?-> <?>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/freeze-boxes.scala:22:4 ----------------------------------
2+
21 | val xs: Box[Ref^{}] = freeze:
3+
22 | Box(a) // error
4+
| ^
5+
| Found: Box[Ref^{a.rd}]^'s1
6+
| Required: Box[Ref^{}]
7+
|
8+
| Note that capability a.rd is not included in capture set {}.
9+
|
10+
| longer explanation available when compiling with `-explain`
11+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/freeze-boxes.scala:36:4 ----------------------------------
12+
36 | a // error, sanity check
13+
| ^
14+
| Found: () ?->{a} Ref^{a}
15+
| Required: () ?-> <?>
16+
|
17+
| Note that capability a is not included in capture set {}.
18+
|
19+
| longer explanation available when compiling with `-explain`
20+
-- Error: tests/neg-custom-args/captures/freeze-boxes.scala:31:6 -------------------------------------------------------
21+
31 | par(() => a.set(42), () => println(b.get)) // error
22+
| ^^^^^^^^^^^^^^^
23+
|Separation failure: argument of type () ->{a} Unit
24+
|to method par: (op1: () => Unit, op2: () => Unit): Unit
25+
|corresponds to capture-polymorphic formal parameter op1 of type () => Unit
26+
|and hides capabilities {a}.
27+
|Some of these overlap with the captures of the second argument with type () ->{b.rd} Unit.
28+
|
29+
| Hidden set of current argument : {a}
30+
| Hidden footprint of current argument : {a}
31+
| Capture set of second argument : {b.rd}
32+
| Footprint set of second argument : {b.rd, a.rd}
33+
| The two sets overlap at : {a}
34+
|
35+
|where: => refers to a fresh root capability created in method test2 when checking argument to parameter op1 of method par
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import language.experimental.captureChecking
2+
import language.experimental.separationChecking
3+
import caps.{Mutable, freeze}
4+
5+
// A mutable ref and its immutable version
6+
class Ref extends Mutable:
7+
private var data: Int = 0
8+
def get: Int = data
9+
update def set(x: Int): Unit = data = x
10+
def allocRef(): Ref^ = Ref()
11+
type IRef = Ref^{}
12+
13+
// Boxes
14+
case class Box[+T](unbox: T)
15+
16+
// Parallelism
17+
def par(op1: () => Unit, op2: () => Unit): Unit = ()
18+
19+
def test1(): Unit =
20+
val a = allocRef()
21+
val xs: Box[Ref^{}] = freeze:
22+
Box(a) // error
23+
val b = xs.unbox
24+
par(() => a.set(42), () => println(b.get))
25+
26+
def test2(): Unit =
27+
val a = allocRef()
28+
val xs = freeze:
29+
Box(a)
30+
val b = xs.unbox
31+
par(() => a.set(42), () => println(b.get)) // error
32+
33+
def test3(): Unit =
34+
val a = allocRef()
35+
val b = freeze:
36+
a // error, sanity check
37+
par(() => a.set(42), () => println(b.get))

0 commit comments

Comments
 (0)