From 1476ebb71e13621661bcb1b3dbe91e8ec5d4583e Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Fri, 12 Sep 2025 12:20:17 +0900 Subject: [PATCH 1/6] Enable exhaustivity and reachability checks for opaque types --- .../tools/dotc/transform/patmat/Space.scala | 8 +++++- tests/pos/i23620.scala | 18 +++++++++++++ tests/warn/i23620b.check | 16 ++++++++++++ tests/warn/i23620b.scala | 25 +++++++++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i23620.scala create mode 100644 tests/warn/i23620b.check create mode 100644 tests/warn/i23620b.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 2a0cbbdbf006..aa7e53c2f7fd 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -709,6 +709,8 @@ object SpaceEngine { else NoType }.filter(_.exists) parts + case tref: TypeRef if tref.symbol.isOpaqueAlias && !tref.info.hiBound.isNothingType => + rec(tref.info.hiBound, mixins) case _ => ListOfNoType end rec @@ -852,7 +854,11 @@ object SpaceEngine { }) || tpw.isRef(defn.BooleanClass) || classSym.isAllOf(JavaEnum) || - classSym.is(Case) + classSym.is(Case) || + (tpw.isInstanceOf[TypeRef] && { + val tref = tpw.asInstanceOf[TypeRef] + tref.symbol.isOpaqueAlias && !tref.info.hiBound.isNothingType + }) !sel.tpe.hasAnnotation(defn.UncheckedAnnot) && !sel.tpe.hasAnnotation(defn.RuntimeCheckedAnnot) diff --git a/tests/pos/i23620.scala b/tests/pos/i23620.scala new file mode 100644 index 000000000000..aa81f09ee182 --- /dev/null +++ b/tests/pos/i23620.scala @@ -0,0 +1,18 @@ +trait Foo +trait Bar + +type FooOrBar = FooOrBar.Type +object FooOrBar: + opaque type Type <: (Foo | Bar) = Foo | Bar + + def bar: FooOrBar = new Bar {} + +trait Buz + +@main def main = + val p: FooOrBar | Buz = FooOrBar.bar + + p match + case _: Foo => println("foo") + case _: Buz => println("buz") + case _: Bar => println("bar") diff --git a/tests/warn/i23620b.check b/tests/warn/i23620b.check new file mode 100644 index 000000000000..5a3aa7abb344 --- /dev/null +++ b/tests/warn/i23620b.check @@ -0,0 +1,16 @@ +-- [E029] Pattern Match Exhaustivity Warning: tests/warn/i23620b.scala:20:2 -------------------------------------------- +20 | p match // warn + | ^ + | match may not be exhaustive. + | + | It would fail on pattern case: _: Bar + | + | longer explanation available when compiling with `-explain` +-- [E029] Pattern Match Exhaustivity Warning: tests/warn/i23620b.scala:23:2 -------------------------------------------- +23 | p2 match { //warn + | ^^ + | match may not be exhaustive. + | + | It would fail on pattern case: _: Bar + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i23620b.scala b/tests/warn/i23620b.scala new file mode 100644 index 000000000000..beaf142276db --- /dev/null +++ b/tests/warn/i23620b.scala @@ -0,0 +1,25 @@ +trait Foo +trait Bar + +type FooOrBar = FooOrBar.Type +object FooOrBar: + opaque type Type <: (Foo | Bar) = Foo | Bar + + def bar: FooOrBar = new Bar {} + +type OnlyFoo = OnlyFoo.Type +object OnlyFoo: + opaque type Type <: (Foo | Bar) = Foo + + def foo: OnlyFoo = new Foo {} + +@main def main = + val p: FooOrBar= FooOrBar.bar + val p2: OnlyFoo = OnlyFoo.foo + + p match // warn + case _: Foo => println("foo") + + p2 match { //warn + case _: Foo => println("foo") + } From fa563720b7a913bcdd2a8e86f34913b6664491fb Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Tue, 23 Sep 2025 15:12:03 +0900 Subject: [PATCH 2/6] call isCheckable recursively --- compiler/src/dotty/tools/dotc/transform/patmat/Space.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index aa7e53c2f7fd..897e3fd369f0 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -857,7 +857,10 @@ object SpaceEngine { classSym.is(Case) || (tpw.isInstanceOf[TypeRef] && { val tref = tpw.asInstanceOf[TypeRef] - tref.symbol.isOpaqueAlias && !tref.info.hiBound.isNothingType + if (tref.symbol.isOpaqueAlias && !tref.info.hiBound.isNothingType) + isCheckable(tref.info.hiBound) + else + false }) !sel.tpe.hasAnnotation(defn.UncheckedAnnot) From d869916c189c18672112e5a8db37f72de8f20365 Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Tue, 23 Sep 2025 16:07:54 +0900 Subject: [PATCH 3/6] address reviews --- compiler/src/dotty/tools/dotc/transform/patmat/Space.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 897e3fd369f0..d92b72a211d2 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -709,7 +709,7 @@ object SpaceEngine { else NoType }.filter(_.exists) parts - case tref: TypeRef if tref.symbol.isOpaqueAlias && !tref.info.hiBound.isNothingType => + case tref: TypeRef if tref.symbol.isAbstractOrAliasType && !tref.info.hiBound.isNothingType => rec(tref.info.hiBound, mixins) case _ => ListOfNoType end rec @@ -857,7 +857,7 @@ object SpaceEngine { classSym.is(Case) || (tpw.isInstanceOf[TypeRef] && { val tref = tpw.asInstanceOf[TypeRef] - if (tref.symbol.isOpaqueAlias && !tref.info.hiBound.isNothingType) + if (tref.symbol.isAbstractOrAliasType && !tref.info.hiBound.isNothingType) isCheckable(tref.info.hiBound) else false From 1edbb996a3f07500f8749a372dbcd36fce92120f Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Sun, 26 Oct 2025 22:16:23 +0900 Subject: [PATCH 4/6] address reviews --- .../dotty/tools/dotc/transform/patmat/Space.scala | 11 ++++++----- tests/warn/i23620b.check | 10 +++++++++- tests/warn/i23620b.scala | 15 ++++++++++++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index d92b72a211d2..8ef30051d8d4 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -709,7 +709,7 @@ object SpaceEngine { else NoType }.filter(_.exists) parts - case tref: TypeRef if tref.symbol.isAbstractOrAliasType && !tref.info.hiBound.isNothingType => + case tref: TypeRef if tref.isUpperBoundedAbstract => rec(tref.info.hiBound, mixins) case _ => ListOfNoType end rec @@ -726,6 +726,10 @@ object SpaceEngine { && !cls.hasAnonymousChild // can't name anonymous classes as counter-examples && cls.children.nonEmpty // can't decompose without children + extension (tref: TypeRef) + def isUpperBoundedAbstract(using Context): Boolean = + tref.symbol.isAbstractOrAliasType && !tref.info.hiBound.isNothingType + val ListOfNoType = List(NoType) val ListOfTypNoType = ListOfNoType.map(Typ(_, decomposed = true)) @@ -857,10 +861,7 @@ object SpaceEngine { classSym.is(Case) || (tpw.isInstanceOf[TypeRef] && { val tref = tpw.asInstanceOf[TypeRef] - if (tref.symbol.isAbstractOrAliasType && !tref.info.hiBound.isNothingType) - isCheckable(tref.info.hiBound) - else - false + tref.isUpperBoundedAbstract && isCheckable(tref.info.hiBound) }) !sel.tpe.hasAnnotation(defn.UncheckedAnnot) diff --git a/tests/warn/i23620b.check b/tests/warn/i23620b.check index 5a3aa7abb344..556448701026 100644 --- a/tests/warn/i23620b.check +++ b/tests/warn/i23620b.check @@ -7,10 +7,18 @@ | | longer explanation available when compiling with `-explain` -- [E029] Pattern Match Exhaustivity Warning: tests/warn/i23620b.scala:23:2 -------------------------------------------- -23 | p2 match { //warn +23 | p2 match { // warn | ^^ | match may not be exhaustive. | | It would fail on pattern case: _: Bar | | longer explanation available when compiling with `-explain` +-- [E029] Pattern Match Exhaustivity Warning: tests/warn/i23620b.scala:37:2 -------------------------------------------- +37 | x match // warn + | ^ + | match may not be exhaustive. + | + | It would fail on pattern case: B + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i23620b.scala b/tests/warn/i23620b.scala index beaf142276db..dfdac45347a2 100644 --- a/tests/warn/i23620b.scala +++ b/tests/warn/i23620b.scala @@ -20,6 +20,19 @@ object OnlyFoo: p match // warn case _: Foo => println("foo") - p2 match { //warn + p2 match { // warn case _: Foo => println("foo") } + +sealed trait S +trait Z + +case object A extends S, Z +case object B extends S, Z + +trait HasT: + type T <: S & Z + +def nonExhaustive(h: HasT, x: h.T) = + x match // warn + case A => () From e86ce626b88bd23dde91592160bccc72ae37c720 Mon Sep 17 00:00:00 2001 From: Yoonjae Jeon Date: Sun, 26 Oct 2025 22:48:11 +0900 Subject: [PATCH 5/6] add test --- tests/warn/i24246.check | 8 ++++++++ tests/warn/i24246.scala | 10 ++++++++++ 2 files changed, 18 insertions(+) create mode 100644 tests/warn/i24246.check create mode 100644 tests/warn/i24246.scala diff --git a/tests/warn/i24246.check b/tests/warn/i24246.check new file mode 100644 index 000000000000..fa197419d86d --- /dev/null +++ b/tests/warn/i24246.check @@ -0,0 +1,8 @@ +-- [E029] Pattern Match Exhaustivity Warning: tests/warn/i24246.scala:8:2 ---------------------------------------------- +8 | x match { // warn + | ^ + | match may not be exhaustive. + | + | It would fail on pattern case: ZZ + | + | longer explanation available when compiling with `-explain` diff --git a/tests/warn/i24246.scala b/tests/warn/i24246.scala new file mode 100644 index 000000000000..f5bd02692774 --- /dev/null +++ b/tests/warn/i24246.scala @@ -0,0 +1,10 @@ +trait X + +sealed trait Y +case object YY extends Y, X +case object ZZ extends Y, X + +def foo[A <: X & Y](x: A): Unit = + x match { // warn + case YY => () + } From 57105d52b2708a410f64f60bedeaf6892212d59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zieli=C5=84ski=20Patryk?= <75637004+zielinsky@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:57:32 +0100 Subject: [PATCH 6/6] Fix identation in Space.scala --- .../src/dotty/tools/dotc/transform/patmat/Space.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 8ef30051d8d4..c98c3f48269d 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -859,10 +859,10 @@ object SpaceEngine { tpw.isRef(defn.BooleanClass) || classSym.isAllOf(JavaEnum) || classSym.is(Case) || - (tpw.isInstanceOf[TypeRef] && { - val tref = tpw.asInstanceOf[TypeRef] - tref.isUpperBoundedAbstract && isCheckable(tref.info.hiBound) - }) + (tpw.isInstanceOf[TypeRef] && { + val tref = tpw.asInstanceOf[TypeRef] + tref.isUpperBoundedAbstract && isCheckable(tref.info.hiBound) + }) !sel.tpe.hasAnnotation(defn.UncheckedAnnot) && !sel.tpe.hasAnnotation(defn.RuntimeCheckedAnnot)