Skip to content

Commit 7af93dd

Browse files
committed
Map cap to fresh in capture sets
Fixes #23170 This was disabled before. We also had to slightly extend fresh/cap collapsing to make override checks go through under the new scheme.
1 parent d925cd8 commit 7af93dd

File tree

9 files changed

+54
-39
lines changed

9 files changed

+54
-39
lines changed

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class CCState:
118118

119119
private var capIsRoot: Boolean = false
120120

121-
private var ignoreFreshLevels: Boolean = false
121+
private var collapseFresh: Boolean = false
122122

123123
object CCState:
124124

@@ -164,20 +164,21 @@ object CCState:
164164
/** Is `caps.cap` a root capability that is allowed to subsume other capabilities? */
165165
def capIsRoot(using Context): Boolean = ccState.capIsRoot
166166

167-
/** Run `op` under the assumption that all FreshCap instances are equal.
167+
/** Run `op` under the assumption that all FreshCap instances are equal
168+
* to each other and to GlobalCap.
168169
* Needed to make override checking of types containing fresh work.
169170
* Asserted in override checking, tested in maxSubsumes.
170171
* Is this sound? Test case is neg-custom-args/captures/leaked-curried.scala.
171172
*/
172-
inline def ignoringFreshLevels[T](op: => T)(using Context): T =
173+
inline def withCollapsedFresh[T](op: => T)(using Context): T =
173174
if isCaptureCheckingOrSetup then
174175
val ccs = ccState
175-
val saved = ccs.ignoreFreshLevels
176-
ccs.ignoreFreshLevels = true
177-
try op finally ccs.ignoreFreshLevels = saved
176+
val saved = ccs.collapseFresh
177+
ccs.collapseFresh = true
178+
try op finally ccs.collapseFresh = saved
178179
else op
179180

180-
/** Should all FreshCap instances be treated equal? */
181-
def ignoreFreshLevels(using Context): Boolean = ccState.ignoreFreshLevels
181+
/** Should all FreshCap instances be treated as equal to GlobalCap? */
182+
def collapseFresh(using Context): Boolean = ccState.collapseFresh
182183

183184
end CCState

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,11 +490,11 @@ object Capabilities:
490490
|| this.match
491491
case x: FreshCap =>
492492
def levelOK =
493-
if ccConfig.useFreshLevels && !CCState.ignoreFreshLevels then
493+
if ccConfig.useFreshLevels && !CCState.collapseFresh then
494494
val yOwner = y.levelOwner
495495
yOwner.isStaticOwner || x.ccOwner.isContainedIn(yOwner)
496496
else y.core match
497-
case GlobalCap | ResultCap(_) | _: ParamRef => false
497+
case ResultCap(_) | _: ParamRef => false
498498
case _ => true
499499

500500
vs.ifNotSeen(this)(x.hiddenSet.elems.exists(_.subsumes(y)))
@@ -512,6 +512,7 @@ object Capabilities:
512512
y match
513513
case GlobalCap => true
514514
case _: ResultCap => false
515+
case _: FreshCap if CCState.collapseFresh => true
515516
case _ =>
516517
y.derivesFromSharedCapability
517518
|| canAddHidden && vs != VarState.HardSeparate && CCState.capIsRoot
@@ -619,8 +620,6 @@ object Capabilities:
619620
override def apply(t: Type) =
620621
if variance <= 0 then t
621622
else t match
622-
case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet =>
623-
t
624623
case t @ CapturingType(_, _) =>
625624
mapOver(t)
626625
case t @ AnnotatedType(parent, ann) =>

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ sealed abstract class CaptureSet extends Showable:
184184
*/
185185
def accountsFor(x: Capability)(using ctx: Context)(using vs: VarState = VarState.Separate): Boolean =
186186

187-
def debugInfo(using Context) = i"$this accountsFor $x"
187+
def debugInfo(using Context) =
188+
val suffix = if ctx.settings.YccVerbose.value then i" with ${x.captureSetOfInfo}" else ""
189+
i"$this accountsFor $x$suffix"
188190

189191
def test(using Context) = reporting.trace(debugInfo):
190192
elems.exists(_.subsumes(x))
@@ -210,9 +212,8 @@ sealed abstract class CaptureSet extends Showable:
210212
*/
211213
def mightAccountFor(x: Capability)(using Context): Boolean =
212214
reporting.trace(i"$this mightAccountFor $x, ${x.captureSetOfInfo}?", show = true):
213-
CCState.withCapAsRoot:
214-
CCState.ignoringFreshLevels: // OK here since we opportunistically choose an alternative which gets checked later
215-
elems.exists(_.subsumes(x)(using ctx)(using VarState.ClosedUnrecorded))
215+
CCState.withCollapsedFresh: // OK here since we opportunistically choose an alternative which gets checked later
216+
elems.exists(_.subsumes(x)(using ctx)(using VarState.ClosedUnrecorded))
216217
|| !x.isTerminalCapability
217218
&& {
218219
val elems = x.captureSetOfInfo.elems

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ object CheckCaptures:
159159
def showInOpenedFreshBinders(mts: List[MethodType]): String = mts match
160160
case Nil => i"the part $t of "
161161
case mt :: mts1 =>
162-
CCState.inNewExistentialScope(mt):
162+
inNewExistentialScope(mt):
163163
showInOpenedFreshBinders(mts1)
164164
showInOpenedFreshBinders(openScopes.reverse)
165165
report.error(
@@ -1160,8 +1160,8 @@ class CheckCaptures extends Recheck, SymTransformer:
11601160
checkSubset(localSet, thisSet, tree.srcPos) // (2)
11611161
for param <- cls.paramGetters do
11621162
if !param.hasAnnotation(defn.ConstructorOnlyAnnot)
1163-
&& !param.hasAnnotation(defn.UntrackedCapturesAnnot) then
1164-
CCState.withCapAsRoot: // OK? We need this here since self types use `cap` instead of `fresh`
1163+
&& !param.hasAnnotation(defn.UntrackedCapturesAnnot) then
1164+
withCapAsRoot: // OK? We need this here since self types use `cap` instead of `fresh`
11651165
checkSubset(param.termRef.captureSet, thisSet, param.srcPos) // (3)
11661166
for pureBase <- cls.pureBaseClass do // (4)
11671167
def selfTypeTree = impl.body
@@ -1711,7 +1711,7 @@ class CheckCaptures extends Recheck, SymTransformer:
17111711
def traverse(t: Tree)(using Context) =
17121712
t match
17131713
case t: Template =>
1714-
ignoringFreshLevels:
1714+
withCollapsedFresh:
17151715
checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t))
17161716
case _ =>
17171717
traverseChildren(t)
@@ -1915,9 +1915,8 @@ class CheckCaptures extends Recheck, SymTransformer:
19151915
val normArgs = args.lazyZip(tl.paramInfos).map: (arg, bounds) =>
19161916
arg.withType(arg.nuType.forceBoxStatus(
19171917
bounds.hi.isBoxedCapturing | bounds.lo.isBoxedCapturing))
1918-
CCState.withCapAsRoot: // OK? We need this since bounds use `cap` instead of `fresh`
1919-
CCState.ignoringFreshLevels:
1920-
checkBounds(normArgs, tl)
1918+
withCollapsedFresh: // OK? We need this since bounds use `cap` instead of `fresh`
1919+
checkBounds(normArgs, tl)
19211920
if ccConfig.postCheckCapturesets then
19221921
args.lazyZip(tl.paramNames).foreach(checkTypeParam(_, _, fun.symbol))
19231922
case _ =>
@@ -1936,9 +1935,8 @@ class CheckCaptures extends Recheck, SymTransformer:
19361935
case tree: InferredTypeTree =>
19371936
case tree: New =>
19381937
case tree: TypeTree =>
1939-
CCState.withCapAsRoot:
1940-
CCState.ignoringFreshLevels:
1941-
checkAppliedTypesIn(tree.withType(tree.nuType))
1938+
withCollapsedFresh:
1939+
checkAppliedTypesIn(tree.withType(tree.nuType))
19421940
case _ => traverseChildren(t)
19431941
checkApplied.traverse(unit)
19441942
end postCheck

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ object Types extends TypeUtils {
869869
pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, overridingRefinement)
870870
else
871871
val isRefinedMethod = rinfo.isInstanceOf[MethodOrPoly]
872-
val joint = CCState.ignoringFreshLevels:
872+
val joint = CCState.withCollapsedFresh:
873873
pdenot.meet(
874874
new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre, isRefinedMethod),
875875
pre,

tests/neg-custom-args/captures/widen-reach.check

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
|
77
| where: ^ refers to the universal root capability
88
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/widen-reach.scala:9:24 -----------------------------------
9-
9 | val foo: IO^ -> IO^ = x => x // error // error
9+
9 | val foo: IO^ -> IO^ = x => x // error
1010
| ^^^^^^
1111
| Found: (x: IO^) ->? IO^{x}
1212
| Required: (x: IO^) -> IO^²
@@ -25,13 +25,3 @@
2525
| ^^^^^^
2626
| Local reach capability x* leaks into capture scope of method test.
2727
| To allow this, the parameter x should be declared with a @use annotation
28-
-- [E164] Declaration Error: tests/neg-custom-args/captures/widen-reach.scala:9:6 --------------------------------------
29-
9 | val foo: IO^ -> IO^ = x => x // error // error
30-
| ^
31-
| error overriding value foo in trait Foo of type IO^ -> box IO^;
32-
| value foo of type IO^ -> IO^² has incompatible type
33-
|
34-
| where: ^ refers to the universal root capability
35-
| ^² refers to a fresh root capability in the type of value foo
36-
|
37-
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/widen-reach.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ trait Foo[+T]:
66
val foo: IO^ -> T
77

88
trait Bar extends Foo[IO^]: // error
9-
val foo: IO^ -> IO^ = x => x // error // error
9+
val foo: IO^ -> IO^ = x => x // error
1010

1111
def test(x: Foo[IO^]): Unit =
1212
val y1: Foo[IO^{x*}] = x
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import language.experimental.captureChecking
2+
3+
class Capbility
4+
5+
trait A:
6+
type T <: Capbility^{caps.cap.rd}
7+
8+
class B extends A:
9+
type T = C^{caps.cap.rd}
10+
11+
class C extends Capbility
12+
13+
14+
trait A2:
15+
type T[Cap^]
16+
17+
def takesCap[Cap^](t: T[Cap]): Unit
18+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import caps.*
2+
3+
trait A
4+
5+
extension (a: A^{cap.rd})
6+
def await = ()
7+
8+
def awaitA[C <: {cap.rd}](a: A^{C}) = a.await

0 commit comments

Comments
 (0)