Skip to content

Commit 19dbbf5

Browse files
authored
Fix #18246: correctly compute capture sets in TypeComparer.glb (#18254)
Intuitively, `glb(S1^C1, S2^C2)` should capture `C1 ⊓ C2`, where `⊓` denotes the glb of capture sets. When computing `glb(tp1, tp2)`, if `tp1` matches `CapturingType(parent1, refs1)`, it captures `C0 ++ refs1` where `C0` is the nested capture sets inside `parent1`. Denote the capture set of `tp2` with `C2`, the result should capture `(C0 ++ refs1) ⊓ C2`. Presumably, `⊓` distributes over the union (`++`); therefore `(C0 ++ refs1) ⊓ C2` equals `C0 ⊓ C2 ++ refs1 ⊓ C2`. Previously, if `C2` is a subcapture of `refs1`, it is simply dropped. However, based on the above reasoning, we have `refs1 ⊓ C2` equals `C2`; therefore `C0 ⊓ C2 ++ refs1 ⊓ C2` equals `C0 ⊓ C2 ++ C2`. `C2` should not be dropped, but rather kept. This PR fixes the logic to behave correctly. Fixes #18246.
2 parents a0121f3 + 4c807c5 commit 19dbbf5

File tree

4 files changed

+20
-6
lines changed

4 files changed

+20
-6
lines changed

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

+4-5
Original file line numberDiff line numberDiff line change
@@ -2618,12 +2618,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
26182618
case tp1: TypeVar if tp1.isInstantiated =>
26192619
tp1.underlying & tp2
26202620
case CapturingType(parent1, refs1) =>
2621-
if subCaptures(tp2.captureSet, refs1, frozen = true).isOK
2621+
val refs2 = tp2.captureSet
2622+
if subCaptures(refs2, refs1, frozen = true).isOK
26222623
&& tp1.isBoxedCapturing == tp2.isBoxedCapturing
2623-
then
2624-
parent1 & tp2
2625-
else
2626-
tp1.derivedCapturingType(parent1 & tp2, refs1)
2624+
then (parent1 & tp2).capturing(refs2)
2625+
else tp1.derivedCapturingType(parent1 & tp2, refs1)
26272626
case tp1: AnnotatedType if !tp1.isRefining =>
26282627
tp1.underlying & tp2
26292628
case _ =>

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1940,7 +1940,7 @@ object Types {
19401940
* the two capture sets are combined.
19411941
*/
19421942
def capturing(cs: CaptureSet)(using Context): Type =
1943-
if cs.isConst && cs.subCaptures(captureSet, frozen = true).isOK then this
1943+
if cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(captureSet, frozen = true).isOK then this
19441944
else this match
19451945
case CapturingType(parent, cs1) => parent.capturing(cs1 ++ cs)
19461946
case _ => CapturingType(this, cs)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-glb.scala:7:19 ----------------------------------------
2+
7 | val x2: Foo[T] = x1 // error
3+
| ^^
4+
| Found: (x1 : (Foo[T]^) & (Foo[Any]^{io}))
5+
| Required: Foo[T]
6+
|
7+
| longer explanation available when compiling with `-explain`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import language.experimental.captureChecking
2+
trait Cap
3+
trait Foo[+T]
4+
5+
def magic[T](io: Cap^, x: Foo[T]^{io}): Foo[T]^{} =
6+
val x1: Foo[T]^{cap} & Foo[Any]^{io} = x
7+
val x2: Foo[T] = x1 // error
8+
x2 // boom, an impure value becomes pure

0 commit comments

Comments
 (0)