Skip to content

Commit 520668f

Browse files
nox213zielinsky
andauthored
Use upper bound of abstract types in exhaustivity checking (#23909)
closes #23620, #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 <[email protected]>
1 parent 5c51b7b commit 520668f

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
@@ -709,6 +709,8 @@ object SpaceEngine {
709709
else NoType
710710
}.filter(_.exists)
711711
parts
712+
case tref: TypeRef if tref.isUpperBoundedAbstract =>
713+
rec(tref.info.hiBound, mixins)
712714
case _ => ListOfNoType
713715
end rec
714716

@@ -724,6 +726,10 @@ object SpaceEngine {
724726
&& !cls.hasAnonymousChild // can't name anonymous classes as counter-examples
725727
&& cls.children.nonEmpty // can't decompose without children
726728

729+
extension (tref: TypeRef)
730+
def isUpperBoundedAbstract(using Context): Boolean =
731+
tref.symbol.isAbstractOrAliasType && !tref.info.hiBound.isNothingType
732+
727733
val ListOfNoType = List(NoType)
728734
val ListOfTypNoType = ListOfNoType.map(Typ(_, decomposed = true))
729735

@@ -852,7 +858,11 @@ object SpaceEngine {
852858
}) ||
853859
tpw.isRef(defn.BooleanClass) ||
854860
classSym.isAllOf(JavaEnum) ||
855-
classSym.is(Case)
861+
classSym.is(Case) ||
862+
(tpw.isInstanceOf[TypeRef] && {
863+
val tref = tpw.asInstanceOf[TypeRef]
864+
tref.isUpperBoundedAbstract && isCheckable(tref.info.hiBound)
865+
})
856866

857867
!sel.tpe.hasAnnotation(defn.UncheckedAnnot)
858868
&& !sel.tpe.hasAnnotation(defn.RuntimeCheckedAnnot)

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)