Skip to content

Commit c2848f9

Browse files
committed
Disallow fresh caps from parameter bounds in results
Fixes #24539
1 parent 48f6cca commit c2848f9

File tree

5 files changed

+96
-3
lines changed

5 files changed

+96
-3
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ object Capabilities:
169169
*/
170170
var skolems: immutable.Map[Symbol, TermRef] = immutable.HashMap.empty
171171

172-
//assert(rootId != 10, i"fresh $prefix, ${ctx.owner}")
172+
//assert(rootId != 4, i"fresh $prefix, $origin, ${ctx.owner}")
173173

174174
/** Is this fresh cap (definitely) classified? If that's the case, the
175175
* classifier cannot be changed anymore.

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,19 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
833833
case t =>
834834
foldOver(c, t)
835835

836+
def checkNoTParamBounds(refsToCheck: Refs, descr: => String, pos: SrcPos): Unit =
837+
for ref <- refsToCheck do
838+
ref match
839+
case ref: FreshCap =>
840+
ref.origin match
841+
case Origin.InDecl(sym) if sym.isAbstractOrParamType =>
842+
report.error(
843+
em"""Separation failure: $descr $ref, which appears in the bound of $sym.
844+
|This is not allowed. The $sym has to be returned explicitly in the result type.""",
845+
pos)
846+
case _ =>
847+
case _ =>
848+
836849
/** If `tpe` appears as a (result-) type of a definition, treat its
837850
* hidden set minus its explicitly declared footprint as consumed.
838851
* If `tpe` appears as an argument to a consume parameter, treat
@@ -847,8 +860,11 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
847860
// "see through them" when we look at hidden sets.
848861
then
849862
val refs = tpe.deepCaptureSet.elems
850-
val toCheck = refs.transHiddenSet.directFootprint.nonPeaks.deduct(refs.directFootprint.nonPeaks)
851-
checkConsumedRefs(toCheck, tpe, role, i"${role.description} $tpe hides", pos)
863+
val refsStar = refs.transHiddenSet
864+
val toCheck = refsStar.directFootprint.nonPeaks.deduct(refs.directFootprint.nonPeaks)
865+
def descr = i"${role.description} $tpe hides"
866+
checkConsumedRefs(toCheck, tpe, role, descr, pos)
867+
checkNoTParamBounds(refsStar, descr, pos)
852868
case TypeRole.Argument(arg, _) =>
853869
if tpe.hasAnnotation(defn.ConsumeAnnot) then
854870
val capts = spanCaptures(arg).directFootprint.nonPeaks
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import caps.*
2+
3+
def Test =
4+
val c: Object^ = "a"
5+
val d: Object^ = "a"
6+
val e1, e2: Object^ = "a"
7+
val falseDeps: Object^ = "a"
8+
def f[T](consume x: T): Unit = ()
9+
def g(consume x: Object^): Unit = ()
10+
def h(consume x: (Object^, Object^)): Object^ = x._1 // error: local reach leak
11+
val cc = (c, c)
12+
f(cc) // no consume, it's boxed
13+
f(c) // no consume, it's boxed
14+
println(c) // ok
15+
g(d) // consume
16+
println(d) // error
17+
h((e1, e2)) // no consume, still boxed
18+
println(e1) // ok
19+
20+
class Ref extends Mutable
21+
def i[T <: Ref](consume x: Ref): Ref = x
22+
23+
24+
25+
26+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/consume-tvar-bound.scala:9:44 ----------------------------
2+
9 |def hh[T <: Ref^](x: Box[T]): (Ref^, Int) = (x.elem, 1) // error (but msg is strange since it does not highlight the underlying box conflict)
3+
| ^^^^^^^^^^^
4+
| Found: (T^'s1, Int)
5+
| Required: (Ref^, Int)
6+
|
7+
| Note that capability cap is not included in capture set {}.
8+
|
9+
| where: ^ refers to a fresh root capability in the result type of method hh
10+
| cap is a fresh root capability in the type of type T
11+
|
12+
| longer explanation available when compiling with `-explain`
13+
-- Error: tests/neg-custom-args/captures/consume-tvar-bound.scala:7:29 -------------------------------------------------
14+
7 |def h[T <: Ref^](x: Box[T]): Ref^ = x.elem // error
15+
| ^^^^
16+
| Separation failure: method h's result type Ref^ hides cap, which appears in the bound of type T.
17+
| This is not allowed. The type T has to be returned explicitly in the result type.
18+
|
19+
| where: cap is a fresh root capability in the type of type T
20+
-- Error: tests/neg-custom-args/captures/consume-tvar-bound.scala:11:37 ------------------------------------------------
21+
11 |def g[T <: Ref^](consume x: Box[T]): Ref^ = x.elem // error
22+
| ^^^^
23+
| Separation failure: method g's result type Ref^ hides cap, which appears in the bound of type T.
24+
| This is not allowed. The type T has to be returned explicitly in the result type.
25+
|
26+
| where: cap is a fresh root capability in the type of type T
27+
-- Error: tests/neg-custom-args/captures/consume-tvar-bound.scala:20:43 ------------------------------------------------
28+
20 | def f[T <: Ref^{rr}](consume x: Box[T]): Ref^ = x.elem // error
29+
| ^^^^
30+
| Separation failure: method f's result type Ref^ hides non-local parameter rr
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.language.experimental.captureChecking
2+
import scala.language.experimental.separationChecking
3+
4+
class Ref
5+
class Box[T](val elem: T)
6+
7+
def h[T <: Ref^](x: Box[T]): Ref^ = x.elem // error
8+
9+
def hh[T <: Ref^](x: Box[T]): (Ref^, Int) = (x.elem, 1) // error (but msg is strange since it does not highlight the underlying box conflict)
10+
11+
def g[T <: Ref^](consume x: Box[T]): Ref^ = x.elem // error
12+
13+
14+
def Test(rr: Ref^) =
15+
16+
val r: Ref^ = Ref()
17+
val r2 = h(Box(r))
18+
println(r)
19+
20+
def f[T <: Ref^{rr}](consume x: Box[T]): Ref^ = x.elem // error
21+

0 commit comments

Comments
 (0)