Skip to content

Commit 3f9878b

Browse files
nox213zielinsky
authored andcommitted
Use upper bound of abstract types in exhaustivity checking (scala#23909)
closes scala#23620, scala#24246 If the upper bound of an abstract type is not `sealed` but is effectively sealed, it is not handled correctly, since classSym could return a type that is not `sealed` (Object in the issue above). If the type were not abstract, it would pass the logic that checks whether it is an Or, And, etc., and would be handled properly. This PR makes exhaustivity checking use the upper bound of abstract types that are effectively sealed. --------- Co-authored-by: Zieliński Patryk <75637004+zielinsky@users.noreply.github.com>
1 parent 8245bef commit 3f9878b

File tree

6 files changed

+109
-1
lines changed

6 files changed

+109
-1
lines changed

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,8 @@ object SpaceEngine {
687687
else NoType
688688
}.filter(_.exists)
689689
parts
690+
case tref: TypeRef if tref.isUpperBoundedAbstract =>
691+
rec(tref.info.hiBound, mixins)
690692
case _ => ListOfNoType
691693
end rec
692694

@@ -702,6 +704,10 @@ object SpaceEngine {
702704
&& !cls.hasAnonymousChild // can't name anonymous classes as counter-examples
703705
&& cls.children.nonEmpty // can't decompose without children
704706

707+
extension (tref: TypeRef)
708+
def isUpperBoundedAbstract(using Context): Boolean =
709+
tref.symbol.isAbstractOrAliasType && !tref.info.hiBound.isNothingType
710+
705711
val ListOfNoType = List(NoType)
706712
val ListOfTypNoType = ListOfNoType.map(Typ(_, decomposed = true))
707713

@@ -826,7 +832,11 @@ object SpaceEngine {
826832
classSym.is(Case) && {
827833
if seen.add(classSym) then productSelectorTypes(tpw, sel.srcPos).exists(isCheckable(_))
828834
else true // recursive case class: return true and other members can still fail the check
829-
}
835+
} ||
836+
(tpw.isInstanceOf[TypeRef] && {
837+
val tref = tpw.asInstanceOf[TypeRef]
838+
tref.isUpperBoundedAbstract && isCheckable(tref.info.hiBound)
839+
})
830840

831841
!sel.tpe.hasAnnotation(defn.UncheckedAnnot)
832842
&& {

tests/pos/i23620.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
trait Foo
2+
trait Bar
3+
4+
type FooOrBar = FooOrBar.Type
5+
object FooOrBar:
6+
opaque type Type <: (Foo | Bar) = Foo | Bar
7+
8+
def bar: FooOrBar = new Bar {}
9+
10+
trait Buz
11+
12+
@main def main =
13+
val p: FooOrBar | Buz = FooOrBar.bar
14+
15+
p match
16+
case _: Foo => println("foo")
17+
case _: Buz => println("buz")
18+
case _: Bar => println("bar")

tests/warn/i23620b.check

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- [E029] Pattern Match Exhaustivity Warning: tests/warn/i23620b.scala:20:2 --------------------------------------------
2+
20 | p match // warn
3+
| ^
4+
| match may not be exhaustive.
5+
|
6+
| It would fail on pattern case: _: Bar
7+
|
8+
| longer explanation available when compiling with `-explain`
9+
-- [E029] Pattern Match Exhaustivity Warning: tests/warn/i23620b.scala:23:2 --------------------------------------------
10+
23 | p2 match { // warn
11+
| ^^
12+
| match may not be exhaustive.
13+
|
14+
| It would fail on pattern case: _: Bar
15+
|
16+
| longer explanation available when compiling with `-explain`
17+
-- [E029] Pattern Match Exhaustivity Warning: tests/warn/i23620b.scala:37:2 --------------------------------------------
18+
37 | x match // warn
19+
| ^
20+
| match may not be exhaustive.
21+
|
22+
| It would fail on pattern case: B
23+
|
24+
| longer explanation available when compiling with `-explain`

tests/warn/i23620b.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
trait Foo
2+
trait Bar
3+
4+
type FooOrBar = FooOrBar.Type
5+
object FooOrBar:
6+
opaque type Type <: (Foo | Bar) = Foo | Bar
7+
8+
def bar: FooOrBar = new Bar {}
9+
10+
type OnlyFoo = OnlyFoo.Type
11+
object OnlyFoo:
12+
opaque type Type <: (Foo | Bar) = Foo
13+
14+
def foo: OnlyFoo = new Foo {}
15+
16+
@main def main =
17+
val p: FooOrBar= FooOrBar.bar
18+
val p2: OnlyFoo = OnlyFoo.foo
19+
20+
p match // warn
21+
case _: Foo => println("foo")
22+
23+
p2 match { // warn
24+
case _: Foo => println("foo")
25+
}
26+
27+
sealed trait S
28+
trait Z
29+
30+
case object A extends S, Z
31+
case object B extends S, Z
32+
33+
trait HasT:
34+
type T <: S & Z
35+
36+
def nonExhaustive(h: HasT, x: h.T) =
37+
x match // warn
38+
case A => ()

tests/warn/i24246.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- [E029] Pattern Match Exhaustivity Warning: tests/warn/i24246.scala:8:2 ----------------------------------------------
2+
8 | x match { // warn
3+
| ^
4+
| match may not be exhaustive.
5+
|
6+
| It would fail on pattern case: ZZ
7+
|
8+
| longer explanation available when compiling with `-explain`

tests/warn/i24246.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
trait X
2+
3+
sealed trait Y
4+
case object YY extends Y, X
5+
case object ZZ extends Y, X
6+
7+
def foo[A <: X & Y](x: A): Unit =
8+
x match { // warn
9+
case YY => ()
10+
}

0 commit comments

Comments
 (0)