Skip to content

Commit 7a7f747

Browse files
committed
Normalize fresh caps in result types
Ensure that capset variables in types of vals and result types of non-anonymous functions contain only a single FreshCap, and furthermore that that FreshCap has as origin InDecl(owner), where owner is the val or def for which the type is defined.
1 parent a968863 commit 7a7f747

File tree

13 files changed

+120
-62
lines changed

13 files changed

+120
-62
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ object Capabilities:
147147
* @param origin an indication where and why the FreshCap was created, used
148148
* for diagnostics
149149
*/
150-
case class FreshCap private (owner: Symbol, origin: Origin)(using @constructorOnly ctx: Context) extends RootCapability:
150+
case class FreshCap (owner: Symbol, origin: Origin)(using @constructorOnly ctx: Context) extends RootCapability:
151151
val hiddenSet = CaptureSet.HiddenSet(owner, this: @unchecked)
152152
// fails initialization check without the @unchecked
153153

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

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ object CaptureSet:
777777
assert(elem.subsumes(elem1),
778778
i"Skipped map ${tm.getClass} maps newly added $elem to $elem1 in $this")
779779

780-
protected def includeElem(elem: Capability)(using Context): Unit =
780+
protected def includeElem(elem: Capability)(using Context, VarState): Unit =
781781
if !elems.contains(elem) then
782782
if debugVars && id == debugTarget then
783783
println(i"###INCLUDE $elem in $this")
@@ -803,7 +803,10 @@ object CaptureSet:
803803
// id == 108 then assert(false, i"trying to add $elem to $this")
804804
assert(elem.isWellformed, elem)
805805
assert(!this.isInstanceOf[HiddenSet] || summon[VarState].isSeparating, summon[VarState])
806-
includeElem(elem)
806+
try includeElem(elem)
807+
catch case ex: AssertionError =>
808+
println(i"error for incl $elem in $this, ${summon[VarState].toString}")
809+
throw ex
807810
if isBadRoot(elem) then
808811
rootAddedHandler()
809812
val normElem = if isMaybeSet then elem else elem.stripMaybe
@@ -947,16 +950,66 @@ object CaptureSet:
947950
override def toString = s"Var$id$elems"
948951
end Var
949952

950-
/** Variables that represent refinements of class parameters can have the universal
951-
* capture set, since they represent only what is the result of the constructor.
952-
* Test case: Without that tweak, logger.scala would not compile.
953-
*/
954-
class RefiningVar(owner: Symbol)(using Context) extends Var(owner):
955-
override def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context) = this
953+
inline val strictFreshLevels = true
956954

957955
/** Variables created in types of inferred type trees */
958-
class ProperVar(override val owner: Symbol, initialElems: Refs, nestedOK: Boolean)(using /*@constructorOnly*/ ictx: Context)
959-
extends Var(owner, initialElems, nestedOK)
956+
class ProperVar(override val owner: Symbol, initialElems: Refs = emptyRefs, nestedOK: Boolean = true, isRefining: Boolean)(using /*@constructorOnly*/ ictx: Context)
957+
extends Var(owner, initialElems, nestedOK):
958+
959+
/** Make sure that capset variables in types of vals and result types of
960+
* non-anonymous functions contain only a single FreshCap, and furthermore
961+
* that that FreshCap has as origin InDecl(owner), where owner is the val
962+
* or def for which the type is defined.
963+
* Note: This currently does not apply to classified or read-only fresh caps.
964+
*/
965+
override def includeElem(elem: Capability)(using ctx: Context, vs: VarState): Unit = elem match
966+
case elem: FreshCap
967+
if !nestedOK
968+
&& !elems.contains(elem)
969+
&& !owner.isAnonymousFunction
970+
&& ccConfig.newScheme =>
971+
def fail = i"attempting to add $elem to $this"
972+
def hideIn(fc: FreshCap): Unit =
973+
assert(elem.tryClassifyAs(fc.hiddenSet.classifier), fail)
974+
if strictFreshLevels && !isRefining then
975+
// If a variable is added by addCaptureRefinements in a synthetic
976+
// refinement of a class type, don't do level checking. The problem is
977+
// that the variable might be matched against a type that does not have
978+
// a refinement, in which case FreshCaps of the class definition would
979+
// leak out in the corresponding places. This will fail level checking.
980+
// The disallowBadRoots override below has a similar reason.
981+
// TODO: We should instead mark the variable as impossible to instantiate
982+
// and drop the refinement later in the inferred type.
983+
// Test case is drop-refinement.scala.
984+
assert(fc.acceptsLevelOf(elem),
985+
i"level failure, cannot add $elem with ${elem.levelOwner} to $owner / $getClass / $fail")
986+
fc.hiddenSet.add(elem)
987+
val isSubsumed = (false /: elems): (isSubsumed, prev) =>
988+
prev match
989+
case prev: FreshCap =>
990+
hideIn(prev)
991+
true
992+
case _ => isSubsumed
993+
if !isSubsumed then
994+
if elem.origin != Origin.InDecl(owner) || elem.hiddenSet.isConst then
995+
val fc = new FreshCap(owner, Origin.InDecl(owner))
996+
assert(fc.tryClassifyAs(elem.hiddenSet.classifier), fail)
997+
hideIn(fc)
998+
super.includeElem(fc)
999+
else
1000+
super.includeElem(elem)
1001+
case _ =>
1002+
super.includeElem(elem)
1003+
1004+
/** Variables that represent refinements of class parameters can have the universal
1005+
* capture set, since they represent only what is the result of the constructor.
1006+
* Test case: Without that tweak, logger.scala would not compile.
1007+
*/
1008+
override def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context) =
1009+
if isRefining then this
1010+
else super.disallowBadRoots(upto)(handler)
1011+
1012+
end ProperVar
9601013

9611014
/** A variable that is derived from some other variable via a map or filter. */
9621015
abstract class DerivedVar(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context)

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1376,7 +1376,10 @@ class CheckCaptures extends Recheck, SymTransformer:
13761376
* where local capture roots are instantiated to root variables.
13771377
*/
13781378
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type =
1379-
testAdapted(actual, expected, tree, addenda)(err.typeMismatch)
1379+
try testAdapted(actual, expected, tree, addenda)(err.typeMismatch)
1380+
catch case ex: AssertionError =>
1381+
println(i"error while checking $tree: $actual against $expected")
1382+
throw ex
13801383

13811384
@annotation.tailrec
13821385
private def findImpureUpperBound(tp: Type)(using Context): Type = tp match

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
252252
* Polytype bounds are only cleaned using step 1, but not otherwise transformed.
253253
*/
254254
private def transformInferredType(tp: Type)(using Context): Type =
255-
def mapInferred(refine: Boolean): TypeMap = new TypeMap with SetupTypeMap:
255+
def mapInferred(inCaptureRefinement: Boolean): TypeMap = new TypeMap with SetupTypeMap:
256256
override def toString = "map inferred"
257257

258258
var refiningNames: Set[Name] = Set()
@@ -262,23 +262,23 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
262262
* where CV_1, ..., CV_n are fresh capture set variables.
263263
*/
264264
def addCaptureRefinements(tp: Type): Type = tp match
265-
case _: TypeRef | _: AppliedType if refine && tp.typeParams.isEmpty =>
265+
case _: TypeRef | _: AppliedType if !inCaptureRefinement && tp.typeParams.isEmpty =>
266266
tp.typeSymbol match
267267
case cls: ClassSymbol
268268
if !defn.isFunctionClass(cls) && cls.is(CaptureChecked) =>
269-
cls.paramGetters.foldLeft(tp) { (core, getter) =>
269+
cls.paramGetters.foldLeft(tp): (core, getter) =>
270270
if atPhase(thisPhase.next)(getter.hasTrackedParts)
271271
&& getter.isRefiningParamAccessor
272272
&& !refiningNames.contains(getter.name) // Don't add a refinement if we have already an explicit one for the same name
273273
then
274274
val getterType =
275-
mapInferred(refine = false)(tp.memberInfo(getter)).strippedDealias
275+
mapInferred(inCaptureRefinement = true)(tp.memberInfo(getter)).strippedDealias
276276
RefinedType(core, getter.name,
277-
CapturingType(getterType, new CaptureSet.RefiningVar(ctx.owner)))
277+
CapturingType(getterType,
278+
CaptureSet.ProperVar(ctx.owner, isRefining = true)))
278279
.showing(i"add capture refinement $tp --> $result", capt)
279280
else
280281
core
281-
}
282282
case _ => tp
283283
case _ => tp
284284

@@ -302,11 +302,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
302302
mapFollowingAliases(tp)
303303
addVar(
304304
addCaptureRefinements(normalizeCaptures(normalizeFunctions(tp1, tp))),
305-
ctx.owner)
305+
ctx.owner,
306+
isRefining = inCaptureRefinement)
306307
end mapInferred
307308

308309
try
309-
val tp1 = mapInferred(refine = true)(tp)
310+
val tp1 = mapInferred(inCaptureRefinement = false)(tp)
310311
val tp2 = toResultInResults(NoSymbol, _ => assert(false))(tp1)
311312
if tp2 ne tp then capt.println(i"expanded inferred in ${ctx.owner}: $tp --> $tp1 --> $tp2")
312313
tp2
@@ -685,7 +686,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
685686
// Infer the self type for the rest, which is all classes without explicit
686687
// self types (to which we also add nested module classes), provided they are
687688
// neither pure, nor are publicily extensible with an unconstrained self type.
688-
val cs = CaptureSet.ProperVar(cls, CaptureSet.emptyRefs, nestedOK = false)
689+
val cs = CaptureSet.ProperVar(cls, CaptureSet.emptyRefs, nestedOK = false, isRefining = false)
689690
if cls.derivesFrom(defn.Caps_Capability) then
690691
// If cls is a capability class, we need to add a fresh readonly capability to
691692
// ensure we cannot treat the class as pure.
@@ -851,8 +852,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
851852
else maybeAdd(tp, tp)
852853

853854
/** Add a capture set variable to `tp` if necessary. */
854-
private def addVar(tp: Type, owner: Symbol)(using Context): Type =
855-
decorate(tp, CaptureSet.ProperVar(owner, _, nestedOK = !ctx.mode.is(Mode.CCPreciseOwner)))
855+
private def addVar(tp: Type, owner: Symbol, isRefining: Boolean)(using Context): Type =
856+
decorate(tp, CaptureSet.ProperVar(owner, _, nestedOK = !ctx.mode.is(Mode.CCPreciseOwner), isRefining))
856857

857858
/** A map that adds <fluid> capture sets at all contra- and invariant positions
858859
* in a type where a capture set would be needed. This is used to make types

tests/neg-custom-args/captures/cc-this5.check

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
-- Error: tests/neg-custom-args/captures/cc-this5.scala:16:20 ----------------------------------------------------------
2-
16 | def f = println(c) // error
3-
| ^
4-
| reference (c : Cap) is not included in the allowed capture set {}
5-
| of the enclosing class A
61
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this5.scala:21:15 -------------------------------------
72
21 | val x: A = this // error
83
| ^^^^

tests/neg-custom-args/captures/cc-this5.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def foo(c: Cap) =
1313
def test(c: Cap) =
1414
class A:
1515
val x: A = this
16-
def f = println(c) // error
16+
def f = println(c)
1717

1818
def test2(c: Cap) =
1919
class A:

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
| Footprint set of function result : {a}
1414
| The two sets overlap at : {a}
1515
|
16-
|where: ^ refers to a fresh root capability classified as Mutable created in value a when constructing mutable Ref
16+
|where: ^ refers to a fresh root capability classified as Mutable in the type of value a
1717
| ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply
1818
-- Error: tests/neg-custom-args/captures/i23726.scala:15:5 -------------------------------------------------------------
1919
15 | f3(b) // error
@@ -30,7 +30,7 @@
3030
| Footprint set of function result : {op, b}
3131
| The two sets overlap at : {b}
3232
|
33-
|where: ^ refers to a fresh root capability classified as Mutable created in value b when constructing mutable Ref
33+
|where: ^ refers to a fresh root capability classified as Mutable in the type of value b
3434
| ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply
3535
-- Error: tests/neg-custom-args/captures/i23726.scala:23:5 -------------------------------------------------------------
3636
23 | f7(a) // error
@@ -47,5 +47,5 @@
4747
| Footprint set of function prefix : {f7*, a, b}
4848
| The two sets overlap at : {a}
4949
|
50-
|where: ^ refers to a fresh root capability classified as Mutable created in value a when constructing mutable Ref
50+
|where: ^ refers to a fresh root capability classified as Mutable in the type of value a
5151
| ^² refers to a fresh root capability classified as Mutable created in method test1 when checking argument to parameter x of method apply

tests/neg-custom-args/captures/reference-cc.check

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
-- Error: tests/neg-custom-args/captures/reference-cc.scala:42:20 ------------------------------------------------------
2-
42 | def f = println(c) // error
3-
| ^
4-
| reference (c : Cap) is not included in the allowed capture set {}
5-
| of the enclosing class A
61
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reference-cc.scala:44:29 ---------------------------------
72
44 | val later = usingLogFile { file => () => file.write(0) } // error
83
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/neg-custom-args/captures/reference-cc.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class Cap extends caps.SharedCapability
3939
def test(c: Cap) =
4040
class A:
4141
val x: A = this
42-
def f = println(c) // error
42+
def f = println(c)
4343

4444
val later = usingLogFile { file => () => file.write(0) } // error
4545
later() // crash
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scope-extrude-mut.scala:9:8 ------------------------------
22
9 | a = a1 // error
33
| ^^
4-
|Found: (a1 : A^)
5-
|Required: A^²
4+
| Found: (a1 : A^)
5+
| Required: A^²
66
|
7-
|Note that capability cap is not included in capture set {cap²}
8-
|because cap in method b is not visible from cap² in variable a.
7+
| Note that capability cap is not included in capture set {cap²}
8+
| because cap in method b is not visible from cap² in variable a.
99
|
10-
|where: ^ refers to a fresh root capability classified as Mutable created in value a1 when constructing mutable A
11-
| ^² refers to a fresh root capability classified as Mutable in the type of variable a
12-
| cap is a fresh root capability classified as Mutable created in value a1 when constructing mutable A
13-
| cap² is a fresh root capability classified as Mutable in the type of variable a
10+
| where: ^ refers to a fresh root capability classified as Mutable in the type of value a1
11+
| ^² refers to a fresh root capability classified as Mutable in the type of variable a
12+
| cap is a fresh root capability classified as Mutable in the type of value a1
13+
| cap² is a fresh root capability classified as Mutable in the type of variable a
1414
|
1515
| longer explanation available when compiling with `-explain`

0 commit comments

Comments
 (0)