Skip to content

Commit 9aa08e2

Browse files
authored
Avoid box failures and better diagnosis if they come back (#24546)
The first commit gives a better error message for box failures. At least we don't get Note that cap is not included in {} anymore. To make this even better we'd have to trace back a box failure to the original types we were trying to compare. Right now the box failure error notes are too disconnected from the rest. But they become clearer when one compiles with -explain. The second commit fixes the underlying problem. The neg tests are now pos tests. I kept the special error message for better diagnosis if the mismatch arises elsewhere. The mismatch still persists in eta.scala. We should investigate whether that's legit or not. Fixes #24543
2 parents 413328e + 5d00194 commit 9aa08e2

File tree

5 files changed

+47
-9
lines changed

5 files changed

+47
-9
lines changed

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,9 @@ object CaptureSet:
588588
cs.mutability = Mutability.Reader
589589
cs
590590

591+
class EmptyOfBoxed(val tp1: Type, val tp2: Type) extends Const(emptyRefs):
592+
override def toString = "{} of boxed mismatch"
593+
591594
/** The universal capture set `{cap}` */
592595
def universal(using Context): Const =
593596
Const(SimpleIdentitySet(GlobalCap))
@@ -1341,15 +1344,19 @@ object CaptureSet:
13411344
case _ =>
13421345
false
13431346

1344-
/** An include failure F1 covers another include failure F2 unless F2
1345-
* strictly subsumes F1, which means they describe the same capture sets
1346-
* and the element in F2 is more specific than the element in F1.
1347+
/** An include failure F1 covers another include failure F2 unless one
1348+
* of the following two conditons holds:
1349+
* 1. F2 strictly subsumes F1, which means they describe the same capture sets
1350+
* and the element in F2 is more specific than the element in F1.
1351+
* 2. Both F1 and F2 are the empty set, but only F2 is an empty set synthesized
1352+
* when comparing types with different box status
13471353
*/
13481354
override def covers(other: Note)(using Context) = other match
13491355
case other @ IncludeFailure(cs1, elem1, _) =>
13501356
val strictlySubsumes =
13511357
cs.elems == cs1.elems
1352-
&& elem1.singletonCaptureSet.mightSubcapture(elem.singletonCaptureSet)
1358+
&& (elem1.singletonCaptureSet.mightSubcapture(elem.singletonCaptureSet)
1359+
|| cs1.isInstanceOf[EmptyOfBoxed] && !cs.isInstanceOf[EmptyOfBoxed])
13531360
!strictlySubsumes
13541361
case _ => false
13551362

@@ -1390,6 +1397,11 @@ object CaptureSet:
13901397
else
13911398
trailing:
13921399
i"capability ${elem.showAsCapability} cannot be included in capture set $cs"
1400+
case cs: EmptyOfBoxed =>
1401+
trailing:
1402+
val (boxed, unboxed) =
1403+
if cs.tp1.isBoxedCapturing then (cs.tp1, cs.tp2) else (cs.tp2, cs.tp1)
1404+
i"${cs.tp1} does not conform to ${cs.tp2} because $boxed is boxed but $unboxed is not"
13931405
case _ =>
13941406
def why =
13951407
val reasons = cs.elems.toList.collect:

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -550,10 +550,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
550550
|| !ctx.mode.is(Mode.CheckBoundsOrSelfType) && tp1.isAlwaysPure
551551
|| parent1.isSingleton && refs1.elems.forall(parent1 eq _)
552552
then
553+
def remainsBoxed1 = parent1.isBoxedCapturing || parent1.dealias.match
554+
case parent1: TypeRef =>
555+
parent1.superType.isBoxedCapturing
556+
// When comparing a type parameter with boxed upper bound on the left
557+
// we should not strip the box on the right. See i24543.scala.
558+
case _ =>
559+
false
553560
val tp2a =
554-
if tp1.isBoxedCapturing && !parent1.isBoxedCapturing
555-
then tp2.unboxed
556-
else tp2
561+
if tp1.isBoxedCapturing && !remainsBoxed1 then tp2.unboxed else tp2
557562
recur(parent1, tp2a)
558563
else thirdTry
559564
compareCapturing
@@ -2928,7 +2933,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
29282933
case _ =>
29292934
subc
29302935
&& (tp1.isBoxedCapturing == tp2.isBoxedCapturing
2931-
|| refs1.subCaptures(CaptureSet.empty, makeVarState()))
2936+
|| refs1.subCaptures(CaptureSet.EmptyOfBoxed(tp1, tp2), makeVarState()))
29322937

29332938
protected def logUndoAction(action: () => Unit) =
29342939
undoLog += action

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
| Found: (g : () -> A)
55
| Required: () -> Proc^{f}
66
|
7-
| Note that capability f is not included in capture set {}.
7+
| Note that () ->{f} Unit does not conform to Proc^{f} because () ->{f} Unit is boxed but Proc^{f} is not.
88
|
99
| longer explanation available when compiling with `-explain`
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import scala.language.experimental.captureChecking
2+
3+
class Ref
4+
case class Box[T](elem: T)
5+
6+
def h1[T <: Ref^](x: Box[T]): Unit =
7+
val y: (Ref^, Int) = (x.elem, 1) // was error
8+
9+
def h2[T <: Ref^](x: List[T]): (Ref^, Int) = (x.head, 1) // was error
10+
11+
12+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.language.experimental.captureChecking
2+
class Ref
3+
case class Box[+T](elem: T)
4+
5+
def test1(a: Ref^): Unit =
6+
def hh[T <: Ref^{a}](x: Box[T]): Unit =
7+
val y: (Ref^{a}, Int) = (x.elem, 1)
8+
val z = (x.elem, 1)
9+
val _: (Ref^{a}, Int) = z

0 commit comments

Comments
 (0)