Skip to content

Commit 5a38c0d

Browse files
committed
Improve handling of consume in class constructors
1 parent 9ba8854 commit 5a38c0d

File tree

4 files changed

+80
-3
lines changed

4 files changed

+80
-3
lines changed

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -917,12 +917,26 @@ class CheckCaptures extends Recheck, SymTransformer:
917917
*/
918918
def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) =
919919
var refined: Type = core
920-
var allCaptures: CaptureSet = initCs ++ captureSetImpliedByFields(cls, core)
920+
val implied = captureSetImpliedByFields(cls, core)
921+
var allCaptures: CaptureSet = initCs ++ implied
921922
for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do
922923
val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol
923924
if !getter.is(Private) && getter.hasTrackedParts then
924925
refined = refined.refinedOverride(getterName, argType.unboxed) // Yichen you might want to check this
925-
allCaptures ++= argType.captureSet
926+
if getter.hasAnnotation(defn.ConsumeAnnot)
927+
&& !implied.isAlwaysEmpty
928+
&& argType.captureSet.subCaptures(allCaptures)
929+
// A consume parameter of a constructor will be hidden in the implied fresh of the
930+
// class if there is one. Example:
931+
//
932+
// class A(consume val x: B^) { val c: C^ = ... }
933+
// val a = A(b) // a: A{...}^{cap hiding b}
934+
// But
935+
// class A1(val x: B^) { val c: C^ = ... }
936+
// val a1 = A1(b) // a1: A{...}^{cap, b}
937+
// See consume-in-constructor.scala.
938+
then () // force the fresh in implied to hide argType.captureSet
939+
else allCaptures ++= argType.captureSet
926940
(refined, allCaptures)
927941

928942
/** Augment result type of constructor with refinements and captures.

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import collection.mutable
77
import core.*
88
import Symbols.*, Types.*, Flags.*, Contexts.*, Names.*, Decorators.*
99
import CaptureSet.{Refs, emptyRefs, HiddenSet}
10+
import NameKinds.WildcardParamName
1011
import config.Printers.capt
1112
import StdNames.nme
1213
import util.{SimpleIdentitySet, EqHashMap, SrcPos}
@@ -986,7 +987,8 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
986987
traverseSection(tree)
987988
case tree: ValDef =>
988989
traverseChildren(tree)
989-
checkValOrDefDef(tree)
990+
if !tree.name.is(WildcardParamName) then
991+
checkValOrDefDef(tree)
990992
case tree: DefDef =>
991993
if skippable(tree.symbol) then
992994
capt.println(i"skipping sep check of ${tree.symbol}")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:18:10 --------------------------------------------
2+
18 | println(b) // error
3+
| ^
4+
| Separation failure: Illegal access to {b} which is hidden by the previous definition
5+
| of value a2 with type A2{val b: B^{b²}}^.
6+
| This type hides capabilities {cap, b²}
7+
|
8+
| where: ^ refers to a fresh root capability in the type of value a2
9+
| b is a value in class A2
10+
| b² is a value in method Test
11+
| cap is a fresh root capability created in value a2 when constructing instance A2
12+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:19:10 --------------------------------------------
13+
19 | println(a1) // error, since `b` was consumed before
14+
| ^^
15+
| Separation failure: Illegal access to {b} which is hidden by the previous definition
16+
| of value a2 with type A2{val b: B^{b²}}^.
17+
| This type hides capabilities {cap, b²}
18+
|
19+
| where: ^ refers to a fresh root capability in the type of value a2
20+
| b is a value in class A2
21+
| b² is a value in method Test
22+
| cap is a fresh root capability created in value a2 when constructing instance A2
23+
-- Error: tests/neg-custom-args/captures/consume-in-constructor.scala:25:10 --------------------------------------------
24+
25 | println(b) // error, b is hidden in the type of a1
25+
| ^
26+
| Separation failure: Illegal access to {b} which is hidden by the previous definition
27+
| of value a1 with type A1^.
28+
| This type hides capabilities {cap, b}
29+
|
30+
| where: ^ refers to a fresh root capability in the type of value a1
31+
| cap is a fresh root capability created in value a1 when constructing instance A1
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import caps.cap
2+
3+
class B
4+
5+
class A1(val b: B^):
6+
val bb: B^ = B()
7+
8+
class A2(consume val b: B^):
9+
val bb: B^ = B()
10+
11+
def Test =
12+
val b: B^ = B()
13+
val a1 = A1(b)
14+
val _: A1^{cap, b} = a1
15+
println(b) // OK since a1's type mentions `b` explicitly
16+
val a2 = A2(b)
17+
val _: A2^{cap, b} = a2
18+
println(b) // error
19+
println(a1) // error, since `b` was consumed before
20+
println(a2) // OK since b belongs to a2
21+
22+
def Test2 =
23+
val b: B^ = B()
24+
val a1: A1^ = A1(b)
25+
println(b) // error, b is hidden in the type of a1
26+
27+
28+
29+
30+

0 commit comments

Comments
 (0)