Skip to content

Commit a587261

Browse files
bracevacodersky
authored andcommitted
Canonicalize capture variable subtype comparisons
Fixes #22103 Subtype problems where at least one side is a type variable representing a capture variable are canonicalized to capturing type comparisons on the special `CapSet` for the universe capture sets. For example, `C <: CapSet^{C^}` becomes `CapSet^{C^} <: CapSet^{C^}`, and `A <: B` becomes `CapSet^{A^} <: CapSet^{B^}` if both `A` and `B` are capture variables. Needs a patch in subsumes Without the patch we get divergence in AvoidMap for cc-poly-varargs. The underlying cause for the divergence is that we could not conclude that the capture variable `Cap^` subsumes `left` through `src1`, i.e., `left` reaches `Cap^`. That caused `left` to appear circularly in its own capture set and subsequently avoidance to loop forever.
1 parent bd699fc commit a587261

File tree

5 files changed

+80
-18
lines changed

5 files changed

+80
-18
lines changed

Diff for: compiler/src/dotty/tools/dotc/cc/CaptureRef.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ trait CaptureRef extends TypeProxy, ValueType:
108108
* TODO: Document cases with more comments.
109109
*/
110110
final def subsumes(y: CaptureRef)(using Context): Boolean =
111-
112111
def subsumingRefs(x: Type, y: Type): Boolean = x match
113112
case x: CaptureRef => y match
114113
case y: CaptureRef => x.subsumes(y)
@@ -119,6 +118,7 @@ trait CaptureRef extends TypeProxy, ValueType:
119118
case info: SingletonCaptureRef => test(info)
120119
case info: AndType => viaInfo(info.tp1)(test) || viaInfo(info.tp2)(test)
121120
case info: OrType => viaInfo(info.tp1)(test) && viaInfo(info.tp2)(test)
121+
case info @ CapturingType(_,_) if this.derivesFrom(defn.Caps_CapSet) => test(info)
122122
case _ => false
123123

124124
(this eq y)
@@ -149,7 +149,7 @@ trait CaptureRef extends TypeProxy, ValueType:
149149
y.info match
150150
case TypeBounds(_, hi: CaptureRef) => this.subsumes(hi)
151151
case _ => y.captureSetOfInfo.elems.forall(this.subsumes)
152-
case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) =>
152+
case CapturingType(parent, refs) if parent.derivesFrom(defn.Caps_CapSet) || this.derivesFrom(defn.Caps_CapSet) =>
153153
refs.elems.forall(this.subsumes)
154154
case _ => false
155155
|| this.match

Diff for: compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import TypeComparer.subsumesExistentially
1818
import util.common.alwaysTrue
1919
import scala.collection.{mutable, immutable}
2020
import CCState.*
21+
import dotty.tools.dotc.core.TypeOps.AvoidMap
2122

2223
/** A class for capture sets. Capture sets can be constants or variables.
2324
* Capture sets support inclusion constraints <:< where <:< is subcapturing.
@@ -1085,10 +1086,9 @@ object CaptureSet:
10851086
tp.captureSet
10861087
case tp: TermParamRef =>
10871088
tp.captureSet
1088-
case _: TypeRef =>
1089-
empty
1090-
case _: TypeParamRef =>
1091-
empty
1089+
case tp: (TypeRef | TypeParamRef) =>
1090+
if tp.derivesFrom(defn.Caps_CapSet) then tp.captureSet
1091+
else empty
10921092
case CapturingType(parent, refs) =>
10931093
recur(parent) ++ refs
10941094
case tp @ AnnotatedType(parent, ann) if ann.hasSymbol(defn.ReachCapabilityAnnot) =>

Diff for: compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+22-2
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,19 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
298298
}
299299
}
300300

301+
/** In capture checking, implements the logic to compare type variables which represent
302+
* capture variables.
303+
*
304+
* Note: should only be called in a context where tp1 or tp2 is a type variable representing a capture variable.
305+
*
306+
* @return -1 if tp1 or tp2 is not a capture variables, 1 if both tp1 and tp2 are capture variables and tp1 is a subcapture of tp2,
307+
* 0 if both tp1 and tp2 are capture variables but tp1 is not a subcapture of tp2.
308+
*/
309+
inline def tryHandleCaptureVars: Int =
310+
if !(isCaptureCheckingOrSetup && tp1.derivesFrom(defn.Caps_CapSet) && tp1.derivesFrom(defn.Caps_CapSet)) then -1
311+
else if (subCaptures(tp1.captureSet, tp2.captureSet, frozenConstraint).isOK) then 1
312+
else 0
313+
301314
def firstTry: Boolean = tp2 match {
302315
case tp2: NamedType =>
303316
def compareNamed(tp1: Type, tp2: NamedType): Boolean =
@@ -346,7 +359,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
346359
&& isSubPrefix(tp1.prefix, tp2.prefix)
347360
&& tp1.signature == tp2.signature
348361
&& !(sym1.isClass && sym2.isClass) // class types don't subtype each other
349-
|| thirdTryNamed(tp2)
362+
|| {val cv = tryHandleCaptureVars
363+
if (cv < 0) then thirdTryNamed(tp2)
364+
else cv != 0 }
350365
case _ =>
351366
secondTry
352367
end compareNamed
@@ -434,6 +449,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
434449

435450
def secondTry: Boolean = tp1 match {
436451
case tp1: NamedType =>
452+
val cv = tryHandleCaptureVars
453+
if (cv >= 0) then return cv != 0
437454
tp1.info match {
438455
case info1: TypeAlias =>
439456
if (recur(info1.alias, tp2)) return true
@@ -858,9 +875,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
858875
}
859876
compareTypeBounds
860877
case CapturingType(parent2, refs2) =>
861-
def compareCapturing =
878+
def compareCapturing: Boolean =
862879
val refs1 = tp1.captureSet
863880
try
881+
if tp1.isInstanceOf[TypeRef] then
882+
val cv = tryHandleCaptureVars
883+
if (cv >= 0) then return (cv != 0)
864884
if refs1.isAlwaysEmpty then recur(tp1, parent2)
865885
else
866886
// The singletonOK branch is because we sometimes have a larger capture set in a singleton
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import language.experimental.captureChecking
2+
import caps.*
3+
4+
def test[C^] =
5+
val a: C = ???
6+
val b: CapSet^{C^} = a
7+
val c: C = b
8+
val d: CapSet^{C^, c} = a
9+
10+
// TODO: make "CapSet-ness" of type variables somehow contagious?
11+
// Then we don't have to spell out the bounds explicitly...
12+
def testTrans[C^, D >: CapSet <: C, E >: CapSet <: D, F >: C <: CapSet^] =
13+
val d1: D = ???
14+
val d2: CapSet^{D^} = d1
15+
val d3: D = d2
16+
val e1: E = ???
17+
val e2: CapSet^{E^} = e1
18+
val e3: E = e2
19+
val d4: D = e1
20+
val c1: C = d1
21+
val c2: C = e1
22+
val f1: F = c1
23+
val d_e_f1: CapSet^{D^,E^,F^} = d1
24+
val d_e_f2: CapSet^{D^,E^,F^} = e1
25+
val d_e_f3: CapSet^{D^,E^,F^} = f1
26+
val f2: F = d_e_f1
27+
val c3: C = d_e_f1 // error
28+
val c4: C = f1 // error
29+
val e4: E = f1 // error
30+
val e5: E = d1 // error
31+
val c5: CapSet^{C^} = e1
32+
33+
34+
trait A[+T]
35+
36+
trait B[-C]
37+
38+
def testCong[C^, D^] =
39+
val a: A[C] = ???
40+
val b: A[CapSet^{C^}] = a
41+
val c: A[CapSet^{D^}] = a // error
42+
val d: A[CapSet^{C^,D^}] = a
43+
val e: A[C] = d // error
44+
val f: B[C] = ???
45+
val g: B[CapSet^{C^}] = f
46+
val h: B[C] = g
47+
val i: B[CapSet^{C^,D^}] = h // error
48+
val j: B[C] = i

Diff for: tests/pos-custom-args/captures/cc-poly-varargs.scala

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
abstract class Source[+T, Cap^]:
2-
def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{this, f} = ???
1+
abstract class Source[+T, Cap^]
32

4-
// TODO: The extension version of `transformValuesWith` doesn't work currently.
5-
// extension[T, Cap^](src: Source[T, Cap]^)
6-
// def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ???
3+
extension[T, Cap^](src: Source[T, Cap]^)
4+
def transformValuesWith[U](f: (T -> U)^{Cap^}): Source[U, Cap]^{src, f} = ???
75

86
def race[T, Cap^](sources: Source[T, Cap]^{Cap^}*): Source[T, Cap]^{Cap^} = ???
97

@@ -12,8 +10,4 @@ def either[T1, T2, Cap^](
1210
src2: Source[T2, Cap]^{Cap^}): Source[Either[T1, T2], Cap]^{Cap^} =
1311
val left = src1.transformValuesWith(Left(_))
1412
val right = src2.transformValuesWith(Right(_))
15-
race[Either[T1, T2], Cap](left, right)
16-
// Explicit type arguments are required here because the second argument
17-
// is inferred as `CapSet^{Cap^}` instead of `Cap`.
18-
// Although `CapSet^{Cap^}` subsumes `Cap` in terms of capture sets,
19-
// `Cap` is not a subtype of `CapSet^{Cap^}` in terms of subtyping.
13+
race(left, right)

0 commit comments

Comments
 (0)