Skip to content

Commit fc593df

Browse files
authored
Fix #19607: Allow to instantiate *wildcard* type captures to TypeBounds. (#19627)
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. --- Companion PR to fix the spec/SIP: scala/improvement-proposals#77
2 parents fe77e3f + 784497d commit fc593df

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
@@ -3388,29 +3388,38 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
33883388
// Actual matching logic
33893389

33903390
val instances = Array.fill[Type](spec.captureCount)(NoType)
3391+
val noInstances = mutable.ListBuffer.empty[(TypeName, TypeBounds)]
33913392

33923393
def rec(pattern: MatchTypeCasePattern, scrut: Type, variance: Int, scrutIsWidenedAbstract: Boolean): Boolean =
33933394
pattern match
3394-
case MatchTypeCasePattern.Capture(num, isWildcard) =>
3395+
case MatchTypeCasePattern.Capture(num, /* isWildcard = */ true) =>
3396+
// instantiate the wildcard in a way that the subtype test always succeeds
3397+
instances(num) = variance match
3398+
case 1 => scrut.hiBound // actually important if we are not in a class type constructor
3399+
case -1 => scrut.loBound
3400+
case 0 => scrut
3401+
!instances(num).isError
3402+
3403+
case MatchTypeCasePattern.Capture(num, /* isWildcard = */ false) =>
3404+
def failNotSpecific(bounds: TypeBounds): TypeBounds =
3405+
noInstances += spec.origMatchCase.paramNames(num) -> bounds
3406+
bounds
3407+
33953408
instances(num) = scrut match
33963409
case scrut: TypeBounds =>
3397-
if isWildcard then
3398-
// anything will do, as long as it conforms to the bounds for the subsequent `scrut <:< instantiatedPat` test
3399-
scrut.hi
3400-
else if scrutIsWidenedAbstract then
3401-
// always keep the TypeBounds so that we can report the correct NoInstances
3402-
scrut
3410+
if scrutIsWidenedAbstract then
3411+
failNotSpecific(scrut)
34033412
else
34043413
variance match
34053414
case 1 => scrut.hi
34063415
case -1 => scrut.lo
3407-
case 0 => scrut
3416+
case 0 => failNotSpecific(scrut)
34083417
case _ =>
3409-
if !isWildcard && scrutIsWidenedAbstract && variance != 0 then
3410-
// force a TypeBounds to report the correct NoInstances
3418+
if scrutIsWidenedAbstract && variance != 0 then
3419+
// fail as not specific
34113420
// the Nothing and Any bounds are used so that they are not displayed; not for themselves in particular
3412-
if variance > 0 then TypeBounds(defn.NothingType, scrut)
3413-
else TypeBounds(scrut, defn.AnyType)
3421+
if variance > 0 then failNotSpecific(TypeBounds(defn.NothingType, scrut))
3422+
else failNotSpecific(TypeBounds(scrut, defn.AnyType))
34143423
else
34153424
scrut
34163425
!instances(num).isError
@@ -3486,12 +3495,8 @@ class MatchReducer(initctx: Context) extends TypeComparer(initctx) {
34863495
MatchResult.Stuck
34873496

34883497
if rec(spec.pattern, scrut, variance = 1, scrutIsWidenedAbstract = false) then
3489-
if instances.exists(_.isInstanceOf[TypeBounds]) then
3490-
MatchResult.NoInstance {
3491-
constrainedCaseLambda.paramNames.zip(instances).collect {
3492-
case (name, bounds: TypeBounds) => (name, bounds)
3493-
}
3494-
}
3498+
if noInstances.nonEmpty then
3499+
MatchResult.NoInstance(noInstances.toList)
34953500
else
34963501
val defn.MatchCase(instantiatedPat, reduced) =
34973502
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)