Skip to content

Commit 784497d

Browse files
committed
Fix #19607: Allow to instantiate *wildcard* type captures to TypeBounds.
When matching in a match type, if we encounter a `TypeBounds` scrutinee and we have a wildcard capture on the right, we used to pick the `hi` bound "because anything between between `lo` and `hi` would work". It turns out that *nothing* between `lo` and `hi` works when the type constructor is invariant. Instead, we must be keep the type bounds, and instantiate the wildcard capture to a wildcard type argument. This is fine because a wildcard capture can never be referred to in the body of the case. However, previously this could never happen in successful cases, and we therefore used the presence of a `TypeBounds` in the `instances` as the canonical signal for "fail as not specific". We now use a separate `noInstances` list to be that signal. This change departs from the letter of the spec but not from its spirit. As evidenced by the wording, the spec always *intended* for "the pick" to one that would always succeed. We wrongly assumed `hi` was always working.
1 parent 5c628d9 commit 784497d

File tree

2 files changed

+35
-18
lines changed

2 files changed

+35
-18
lines changed

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

+23-18
Original file line numberDiff line numberDiff line change
@@ -3410,29 +3410,38 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
34103410
// Actual matching logic
34113411

34123412
val instances = Array.fill[Type](spec.captureCount)(NoType)
3413+
val noInstances = mutable.ListBuffer.empty[(TypeName, TypeBounds)]
34133414

34143415
def rec(pattern: MatchTypeCasePattern, scrut: Type, variance: Int, scrutIsWidenedAbstract: Boolean): Boolean =
34153416
pattern match
3416-
case MatchTypeCasePattern.Capture(num, isWildcard) =>
3417+
case MatchTypeCasePattern.Capture(num, /* isWildcard = */ true) =>
3418+
// instantiate the wildcard in a way that the subtype test always succeeds
3419+
instances(num) = variance match
3420+
case 1 => scrut.hiBound // actually important if we are not in a class type constructor
3421+
case -1 => scrut.loBound
3422+
case 0 => scrut
3423+
!instances(num).isError
3424+
3425+
case MatchTypeCasePattern.Capture(num, /* isWildcard = */ false) =>
3426+
def failNotSpecific(bounds: TypeBounds): TypeBounds =
3427+
noInstances += spec.origMatchCase.paramNames(num) -> bounds
3428+
bounds
3429+
34173430
instances(num) = scrut match
34183431
case scrut: TypeBounds =>
3419-
if isWildcard then
3420-
// anything will do, as long as it conforms to the bounds for the subsequent `scrut <:< instantiatedPat` test
3421-
scrut.hi
3422-
else if scrutIsWidenedAbstract then
3423-
// always keep the TypeBounds so that we can report the correct NoInstances
3424-
scrut
3432+
if scrutIsWidenedAbstract then
3433+
failNotSpecific(scrut)
34253434
else
34263435
variance match
34273436
case 1 => scrut.hi
34283437
case -1 => scrut.lo
3429-
case 0 => scrut
3438+
case 0 => failNotSpecific(scrut)
34303439
case _ =>
3431-
if !isWildcard && scrutIsWidenedAbstract && variance != 0 then
3432-
// force a TypeBounds to report the correct NoInstances
3440+
if scrutIsWidenedAbstract && variance != 0 then
3441+
// fail as not specific
34333442
// the Nothing and Any bounds are used so that they are not displayed; not for themselves in particular
3434-
if variance > 0 then TypeBounds(defn.NothingType, scrut)
3435-
else TypeBounds(scrut, defn.AnyType)
3443+
if variance > 0 then failNotSpecific(TypeBounds(defn.NothingType, scrut))
3444+
else failNotSpecific(TypeBounds(scrut, defn.AnyType))
34363445
else
34373446
scrut
34383447
!instances(num).isError
@@ -3508,12 +3517,8 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
35083517
MatchResult.Stuck
35093518

35103519
if rec(spec.pattern, scrut, variance = 1, scrutIsWidenedAbstract = false) then
3511-
if instances.exists(_.isInstanceOf[TypeBounds]) then
3512-
MatchResult.NoInstance {
3513-
constrainedCaseLambda.paramNames.zip(instances).collect {
3514-
case (name, bounds: TypeBounds) => (name, bounds)
3515-
}
3516-
}
3520+
if noInstances.nonEmpty then
3521+
MatchResult.NoInstance(noInstances.toList)
35173522
else
35183523
val defn.MatchCase(instantiatedPat, reduced) =
35193524
instantiateParamsSpec(instances, constrainedCaseLambda)(constrainedCaseLambda.resultType): @unchecked

tests/pos/i19607.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
trait Foo
2+
trait Bar[T]
3+
4+
type MatchType[T] = T match
5+
case Bar[?] => Nothing
6+
case _ => T
7+
8+
object Test:
9+
def foo(b: Bar[? >: Foo]): Unit =
10+
summon[MatchType[b.type] =:= Nothing]
11+
summon[MatchType[Bar[? >: Foo]] =:= Nothing]
12+
end Test

0 commit comments

Comments
 (0)