Skip to content

Commit f03b5b6

Browse files
committed
Check separation of different parts of a declared type.
1 parent d8f1423 commit f03b5b6

File tree

4 files changed

+73
-22
lines changed

4 files changed

+73
-22
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,14 +1248,17 @@ object CaptureSet:
12481248
* arguments. This have to be included to be conservative in dcs but must be
12491249
* excluded in narrowCaps.
12501250
*/
1251-
def ofTypeDeeply(tp: Type, includeTypevars: Boolean = false)(using Context): CaptureSet =
1251+
def ofTypeDeeply(
1252+
tp: Type,
1253+
includeTypevars: Boolean = false,
1254+
union: (CaptureSet, CaptureSet) => Context ?=> CaptureSet = _ ++ _)(using Context): CaptureSet =
12521255
val collect = new TypeAccumulator[CaptureSet]:
12531256
val seen = util.HashSet[Symbol]()
12541257
def apply(cs: CaptureSet, t: Type) =
12551258
if variance < 0 then cs
12561259
else t.dealias match
12571260
case t @ CapturingType(p, cs1) =>
1258-
this(cs, p) ++ cs1
1261+
union(this(cs, p), cs1)
12591262
case t @ AnnotatedType(parent, ann) =>
12601263
this(cs, parent)
12611264
case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) =>

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

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,10 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
5252

5353
private def hidden(using Context): Refs =
5454
val seen: util.EqHashSet[CaptureRef] = new util.EqHashSet
55-
56-
def hiddenByElem(elem: CaptureRef): Refs =
57-
if seen.add(elem) then elem match
58-
case Fresh.Cap(hcs) => hcs.elems.filter(!_.isRootCapability) ++ recur(hcs.elems)
59-
case ReadOnlyCapability(ref) => hiddenByElem(ref).map(_.readOnly)
60-
case _ => emptySet
61-
else emptySet
62-
6355
def recur(cs: Refs): Refs =
6456
(emptySet /: cs): (elems, elem) =>
65-
elems ++ hiddenByElem(elem)
66-
57+
if seen.add(elem) then elems ++ hiddenByElem(elem, recur)
58+
else elems
6759
recur(refs)
6860
end hidden
6961

@@ -79,6 +71,11 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
7971
refs -- captures(dep).footprint
8072
end extension
8173

74+
private def hiddenByElem(ref: CaptureRef, recur: Refs => Refs)(using Context): Refs = ref match
75+
case Fresh.Cap(hcs) => hcs.elems.filter(!_.isRootCapability) ++ recur(hcs.elems)
76+
case ReadOnlyCapability(ref1) => hiddenByElem(ref1, recur).map(_.readOnly)
77+
case _ => emptySet
78+
8279
/** The captures of an argument or prefix widened to the formal parameter, if
8380
* the latter contains a cap.
8481
*/
@@ -212,6 +209,34 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
212209
if !overlap.isEmpty then
213210
sepUseError(tree, usedFootprint, overlap)
214211

212+
def checkType(tpt: Tree, sym: Symbol)(using Context) =
213+
def checkSep(hidden: Refs, footprint: Refs, descr: String) =
214+
val overlap = hidden.overlapWith(footprint)
215+
if !overlap.isEmpty then
216+
report.error(
217+
em"""Separation failure: ${tpt.nuType} captures a root element hiding ${CaptureSet(hidden)}
218+
|and also $descr ${CaptureSet(footprint)}.
219+
|The two sets overlap at ${CaptureSet(overlap)}""",
220+
tpt.srcPos)
221+
222+
val capts = CaptureSet.ofTypeDeeply(tpt.nuType,
223+
union = (xs, ys) => ctx ?=> CaptureSet(xs.elems ++ ys.elems))
224+
.elems
225+
// Note: Can't use captures(...) or deepCaptureSet here because these would map
226+
// e.g (Object^{<cap hiding x}, Object^{x}) to {<cap hiding x>} and we need
227+
// {<cap hiding x>, x} instead.
228+
val explicitFootprint = capts.footprint
229+
var hiddenFootprint: Refs = emptySet
230+
//println(i"checking tp ${tpt.tpe} with $capts fp $explicitFootprint")
231+
for ref <- capts do
232+
val hidden0 = hiddenByElem(ref, _.hidden).footprint
233+
val hiddenByRef = hiddenByElem(ref, _.hidden).footprint.deductSym(sym)
234+
if !hiddenByRef.isEmpty then
235+
checkSep(hiddenByRef, explicitFootprint, "refers to")
236+
checkSep(hiddenByRef, hiddenFootprint, "captures another root element hiding")
237+
hiddenFootprint ++= hiddenByRef
238+
end checkType
239+
215240
private def collectMethodTypes(tp: Type): List[TermLambda] = tp match
216241
case tp: MethodType => tp :: collectMethodTypes(tp.resType)
217242
case tp: PolyType => collectMethodTypes(tp.resType)
@@ -269,11 +294,13 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
269294
defsShadow = saved
270295
case tree: ValOrDefDef =>
271296
traverseChildren(tree)
272-
if previousDefs.nonEmpty && !tree.symbol.isOneOf(TermParamOrAccessor) then
273-
capt.println(i"sep check def ${tree.symbol}: ${tree.tpt} with ${captures(tree.tpt).hidden.footprint}")
274-
defsShadow ++= captures(tree.tpt).hidden.footprint.deductSym(tree.symbol)
275-
resultType(tree.symbol) = tree.tpt.nuType
276-
previousDefs.head += tree
297+
if !tree.symbol.isOneOf(TermParamOrAccessor) then
298+
checkType(tree.tpt, tree.symbol)
299+
if previousDefs.nonEmpty then
300+
capt.println(i"sep check def ${tree.symbol}: ${tree.tpt} with ${captures(tree.tpt).hidden.footprint}")
301+
defsShadow ++= captures(tree.tpt).hidden.footprint.deductSym(tree.symbol)
302+
resultType(tree.symbol) = tree.tpt.nuType
303+
previousDefs.head += tree
277304
case _ =>
278305
traverseChildren(tree)
279306
end SepChecker

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
-- Error: tests/neg-custom-args/captures/sepchecks2.scala:7:10 ---------------------------------------------------------
2-
7 | println(c) // error
1+
-- Error: tests/neg-custom-args/captures/sepchecks2.scala:8:10 ---------------------------------------------------------
2+
8 | println(c) // error
33
| ^
44
| Separation failure: Illegal access to {c} which is hidden by the previous definition
55
| of value xs with type List[box () => Unit].
66
| This type hides capabilities {xs*, c}
7-
-- Error: tests/neg-custom-args/captures/sepchecks2.scala:10:33 --------------------------------------------------------
8-
10 | foo((() => println(c)) :: Nil, c) // error
7+
-- Error: tests/neg-custom-args/captures/sepchecks2.scala:11:33 --------------------------------------------------------
8+
11 | foo((() => println(c)) :: Nil, c) // error
99
| ^
1010
| Separation failure: argument of type (c : Object^)
1111
| to method foo: (xs: List[box () => Unit], y: Object^): Nothing
@@ -19,3 +19,15 @@
1919
| Hidden footprint of current argument : {c}
2020
| Declared footprint of current argument: {}
2121
| Undeclared overlap of footprints : {c}
22+
-- Error: tests/neg-custom-args/captures/sepchecks2.scala:12:10 --------------------------------------------------------
23+
12 | val x1: (Object^, Object^) = (c, c) // error
24+
| ^^^^^^^^^^^^^^^^^^
25+
| Separation failure: (box Object^, box Object^) captures a root element hiding {c}
26+
| and also captures another root element hiding {c}.
27+
| The two sets overlap at {c}
28+
-- Error: tests/neg-custom-args/captures/sepchecks2.scala:13:10 --------------------------------------------------------
29+
13 | val x2: (Object^, Object^{d}) = (d, d) // error
30+
| ^^^^^^^^^^^^^^^^^^^^^
31+
| Separation failure: (box Object^, box Object^{d}) captures a root element hiding {d}
32+
| and also refers to {d}.
33+
| The two sets overlap at {d}
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
import language.future // sepchecks on
22

3+
34
def foo(xs: List[() => Unit], y: Object^) = ???
45

56
def Test(c: Object^) =
67
val xs: List[() => Unit] = (() => println(c)) :: Nil
78
println(c) // error
89

9-
def Test2(c: Object^) =
10+
def Test2(c: Object^, d: Object^) =
1011
foo((() => println(c)) :: Nil, c) // error
12+
val x1: (Object^, Object^) = (c, c) // error
13+
val x2: (Object^, Object^{d}) = (d, d) // error
14+
15+
def Test3(c: Object^, d: Object^) =
16+
val x: (Object^, Object^) = (c, d) // ok
17+
18+
def Test4(c: Object^, d: Object^) =
19+
val x: (Object^, Object^{c}) = (d, c) // ok

0 commit comments

Comments
 (0)