diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index ee94d9bf1d31..1eb6744a39b0 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -588,6 +588,9 @@ object CaptureSet: cs.mutability = Mutability.Reader cs + class EmptyOfBoxed(val tp1: Type, val tp2: Type) extends Const(emptyRefs): + override def toString = "{} of boxed mismatch" + /** The universal capture set `{cap}` */ def universal(using Context): Const = Const(SimpleIdentitySet(GlobalCap)) @@ -1341,15 +1344,19 @@ object CaptureSet: case _ => false - /** An include failure F1 covers another include failure F2 unless F2 - * strictly subsumes F1, which means they describe the same capture sets - * and the element in F2 is more specific than the element in F1. + /** An include failure F1 covers another include failure F2 unless one + * of the following two conditons holds: + * 1. F2 strictly subsumes F1, which means they describe the same capture sets + * and the element in F2 is more specific than the element in F1. + * 2. Both F1 and F2 are the empty set, but only F2 is an empty set synthesized + * when comparing types with different box status */ override def covers(other: Note)(using Context) = other match case other @ IncludeFailure(cs1, elem1, _) => val strictlySubsumes = cs.elems == cs1.elems - && elem1.singletonCaptureSet.mightSubcapture(elem.singletonCaptureSet) + && (elem1.singletonCaptureSet.mightSubcapture(elem.singletonCaptureSet) + || cs1.isInstanceOf[EmptyOfBoxed] && !cs.isInstanceOf[EmptyOfBoxed]) !strictlySubsumes case _ => false @@ -1390,6 +1397,11 @@ object CaptureSet: else trailing: i"capability ${elem.showAsCapability} cannot be included in capture set $cs" + case cs: EmptyOfBoxed => + trailing: + val (boxed, unboxed) = + if cs.tp1.isBoxedCapturing then (cs.tp1, cs.tp2) else (cs.tp2, cs.tp1) + i"${cs.tp1} does not conform to ${cs.tp2} because $boxed is boxed but $unboxed is not" case _ => def why = val reasons = cs.elems.toList.collect: diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 6a7e4733f9d9..b9e57445ce94 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -550,10 +550,15 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling || !ctx.mode.is(Mode.CheckBoundsOrSelfType) && tp1.isAlwaysPure || parent1.isSingleton && refs1.elems.forall(parent1 eq _) then + def remainsBoxed1 = parent1.isBoxedCapturing || parent1.dealias.match + case parent1: TypeRef => + parent1.superType.isBoxedCapturing + // When comparing a type parameter with boxed upper bound on the left + // we should not strip the box on the right. See i24543.scala. + case _ => + false val tp2a = - if tp1.isBoxedCapturing && !parent1.isBoxedCapturing - then tp2.unboxed - else tp2 + if tp1.isBoxedCapturing && !remainsBoxed1 then tp2.unboxed else tp2 recur(parent1, tp2a) else thirdTry compareCapturing @@ -2915,7 +2920,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case _ => subc && (tp1.isBoxedCapturing == tp2.isBoxedCapturing - || refs1.subCaptures(CaptureSet.empty, makeVarState())) + || refs1.subCaptures(CaptureSet.EmptyOfBoxed(tp1, tp2), makeVarState())) protected def logUndoAction(action: () => Unit) = undoLog += action diff --git a/tests/neg-custom-args/captures/eta.check b/tests/neg-custom-args/captures/eta.check index dfce53accd4d..09878f84135d 100644 --- a/tests/neg-custom-args/captures/eta.check +++ b/tests/neg-custom-args/captures/eta.check @@ -4,6 +4,6 @@ | Found: (g : () -> A) | Required: () -> Proc^{f} | - | Note that capability f is not included in capture set {}. + | Note that () ->{f} Unit does not conform to Proc^{f} because () ->{f} Unit is boxed but Proc^{f} is not. | | longer explanation available when compiling with `-explain` diff --git a/tests/pos-custom-args/captures/i24543.scala b/tests/pos-custom-args/captures/i24543.scala new file mode 100644 index 000000000000..e09607cbf53c --- /dev/null +++ b/tests/pos-custom-args/captures/i24543.scala @@ -0,0 +1,12 @@ +import scala.language.experimental.captureChecking + +class Ref +case class Box[T](elem: T) + +def h1[T <: Ref^](x: Box[T]): Unit = + val y: (Ref^, Int) = (x.elem, 1) // was error + +def h2[T <: Ref^](x: List[T]): (Ref^, Int) = (x.head, 1) // was error + + + diff --git a/tests/pos-custom-args/captures/i24543a.scala b/tests/pos-custom-args/captures/i24543a.scala new file mode 100644 index 000000000000..034a45db2df0 --- /dev/null +++ b/tests/pos-custom-args/captures/i24543a.scala @@ -0,0 +1,9 @@ +import scala.language.experimental.captureChecking +class Ref +case class Box[+T](elem: T) + +def test1(a: Ref^): Unit = + def hh[T <: Ref^{a}](x: Box[T]): Unit = + val y: (Ref^{a}, Int) = (x.elem, 1) + val z = (x.elem, 1) + val _: (Ref^{a}, Int) = z