Skip to content

Commit ff6f4b8

Browse files
committed
Two fixes to handling of abstract types with cap bounds
1. Strip implied cap.rd sets from Capability types in extends and bounds I.e. We do not add an implicit cap.rd in class C extends Capability type T <: C 2. Don't map CapSet^ to CapSet^{fresh}. Assume `^` is root in some comparisons with CapSet.
1 parent f0c050e commit ff6f4b8

File tree

6 files changed

+55
-25
lines changed

6 files changed

+55
-25
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1341,7 +1341,7 @@ class CheckCaptures extends Recheck, SymTransformer:
13411341
else if !owner.exists then false
13421342
else isPure(owner.info) && isPureContext(owner.owner, limit)
13431343

1344-
// Augment expeced capture set `erefs` by all references in actual capture
1344+
// Augment expected capture set `erefs` by all references in actual capture
13451345
// set `arefs` that are outside some `C.this.type` reference in `erefs` for an enclosing
13461346
// class `C`. If an added reference is not a ThisType itself, add it to the capture set
13471347
// (i.e. use set) of the `C`. This makes sure that any outer reference implicitly subsumed

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

+19-12
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,20 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
336336
def fail(msg: Message) =
337337
if !tptToCheck.isEmpty then report.error(msg, tptToCheck.srcPos)
338338

339+
/** If C derives from Capability and we have a C^cs in source, we leave it as is
340+
* instead of expanding it to C^{cap.rd}^cs. We do this by stripping capability-generated
341+
* universal capture sets from the parent of a CapturingType.
342+
*/
343+
def stripImpliedCaptureSet(tp: Type): Type = tp match
344+
case tp @ CapturingType(parent, refs)
345+
if (refs eq CaptureSet.universalImpliedByCapability) && !tp.isBoxedCapturing =>
346+
parent
347+
case tp: AliasingBounds =>
348+
tp.derivedAlias(stripImpliedCaptureSet(tp.alias))
349+
case tp: RealTypeBounds =>
350+
tp.derivedTypeBounds(stripImpliedCaptureSet(tp.lo), stripImpliedCaptureSet(tp.hi))
351+
case _ => tp
352+
339353
object toCapturing extends DeepTypeMap, SetupTypeMap:
340354
override def toString = "transformExplicitType"
341355

@@ -367,16 +381,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
367381
CapturingType(fntpe, cs, boxed = false)
368382
else fntpe
369383

370-
/** If C derives from Capability and we have a C^cs in source, we leave it as is
371-
* instead of expanding it to C^{cap.rd}^cs. We do this by stripping capability-generated
372-
* universal capture sets from the parent of a CapturingType.
373-
*/
374-
def stripImpliedCaptureSet(tp: Type): Type = tp match
375-
case tp @ CapturingType(parent, refs)
376-
if (refs eq CaptureSet.universalImpliedByCapability) && !tp.isBoxedCapturing =>
377-
parent
378-
case _ => tp
379-
380384
/** Check that types extending SharedCapability don't have a `cap` in their capture set.
381385
* TODO This is not enough.
382386
* We need to also track that we cannot get exclusive capabilities in paths
@@ -456,8 +460,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
456460
toCapturing.keepFunAliases = false
457461
transform(tp1)
458462
else tp1
459-
if freshen then root.capToFresh(tp2).tap(addOwnerAsHidden(_, sym))
460-
else tp2
463+
val tp3 =
464+
if sym.isType then stripImpliedCaptureSet(tp2)
465+
else tp2
466+
if freshen then root.capToFresh(tp3).tap(addOwnerAsHidden(_, sym))
467+
else tp3
461468
end transformExplicitType
462469

463470
/** Substitute parameter symbols in `from` to paramRefs in corresponding

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ object root:
162162
case _ if root.isCap => Some(Kind.Global)
163163
case _ => None
164164

165-
/** Map each occurrence of cap to a different Sep.Cap instance */
165+
/** Map each occurrence of cap to a different Fresh instance
166+
* Exception: CapSet^ stays as it is.
167+
*/
166168
class CapToFresh(owner: Symbol)(using Context) extends BiTypeMap, FollowAliasesMap:
167169
thisMap =>
168170

@@ -171,6 +173,8 @@ object root:
171173
else t match
172174
case t: CaptureRef if t.isCap =>
173175
Fresh.withOwner(owner)
176+
case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet =>
177+
t
174178
case t @ CapturingType(_, _) =>
175179
mapOver(t)
176180
case t @ AnnotatedType(parent, ann) =>

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
426426
if (tp1.prefix.isStable) return tryLiftedToThis1
427427
case _ =>
428428
if isCaptureVarComparison then
429-
return subCaptures(tp1.captureSet, tp2.captureSet).isOK
429+
return CCState.withCapAsRoot:
430+
subCaptures(tp1.captureSet, tp2.captureSet).isOK
430431
if (tp1 eq NothingType) || isBottom(tp1) then
431432
return true
432433
}
@@ -575,7 +576,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
575576
&& (isBottom(tp1) || GADTusage(tp2.symbol))
576577

577578
if isCaptureVarComparison then
578-
return subCaptures(tp1.captureSet, tp2.captureSet).isOK
579+
return CCState.withCapAsRoot:
580+
subCaptures(tp1.captureSet, tp2.captureSet).isOK
579581

580582
isSubApproxHi(tp1, info2.lo) && (trustBounds || isSubApproxHi(tp1, info2.hi))
581583
|| compareGADT

compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala

+10-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import NameKinds.DefaultGetterName
88
import NullOpsDecorator.*
99
import collection.immutable.BitSet
1010
import scala.annotation.tailrec
11-
import cc.isCaptureChecking
11+
import cc.{isCaptureChecking, CCState}
1212

1313
import scala.compiletime.uninitialized
1414

@@ -215,14 +215,15 @@ object OverridingPairs:
215215
if member.isType then // intersection of bounds to refined types must be nonempty
216216
memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi)
217217
&& (
218-
(memberTp frozen_<:< otherTp)
219-
|| !member.owner.derivesFrom(other.owner)
220-
&& {
221-
// if member and other come from independent classes or traits, their
222-
// bounds must have non-empty-intersection
223-
val jointBounds = (memberTp.bounds & otherTp.bounds).bounds
224-
jointBounds.lo frozen_<:< jointBounds.hi
225-
}
218+
CCState.withCapAsRoot: // If upper bound is CapSet^ any capture set of lower bound is OK
219+
(memberTp frozen_<:< otherTp)
220+
|| !member.owner.derivesFrom(other.owner)
221+
&& {
222+
// if member and other come from independent classes or traits, their
223+
// bounds must have non-empty-intersection
224+
val jointBounds = (memberTp.bounds & otherTp.bounds).bounds
225+
jointBounds.lo frozen_<:< jointBounds.hi
226+
}
226227
)
227228
else
228229
member.name.is(DefaultGetterName) // default getters are not checked for compatibility
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import language.experimental.captureChecking
2+
3+
trait A:
4+
type T <: caps.Capability
5+
6+
class B extends A:
7+
type T = C
8+
9+
class C extends caps.Capability
10+
11+
12+
trait A2:
13+
type T[Cap^]
14+
15+
def takesCap[Cap^](t: T[Cap]): Unit
16+

0 commit comments

Comments
 (0)