Skip to content

Conversation

@nox213
Copy link
Contributor

@nox213 nox213 commented Sep 12, 2025

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.

@nox213 nox213 changed the title Enable exhaustivity and reachability checks for opaque types Use upper bound of effectively sealed abstract types in exhaustivity checking Sep 25, 2025
@nox213 nox213 changed the title Use upper bound of effectively sealed abstract types in exhaustivity checking Use upper bound of abstract types in exhaustivity checking Sep 29, 2025
@nox213
Copy link
Contributor Author

nox213 commented Oct 21, 2025

@zielinsky Could you review this PR?

Copy link
Member

@zielinsky zielinsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this change impacts abstract type members as well, could you add tests for them too?

@nox213
Copy link
Contributor Author

nox213 commented Oct 24, 2025

Since this change impacts abstract type members as well, could you add tests for them too?

Sure, I made a test code like below.

trait Foo
trait Bar

trait AbstractType:
  type Type <: (Foo | Bar)

object FooOrBar extends AbstractType:
  type Type = Foo | Bar

trait Buz

@main def main =
  val p: FooOrBar.Type | Buz = new Buz {}

  p match
    case _: Foo => println("foo")
    case _: Buz => println("buz")
    case _: Bar => println("bar")

It passes well, but this abstract type member setup doesn't hide the implementation type, it doesn't reach the logic that I added.

   tpw.isInstanceOf[OrType] || // It is caught by this condition
      (tpw.isInstanceOf[AndType] && {
        val and = tpw.asInstanceOf[AndType]
        isCheckable(and.tp1) || isCheckable(and.tp2)
      }) ||
      tpw.isRef(defn.BooleanClass) ||
      classSym.isAllOf(JavaEnum) ||
      classSym.is(Case) ||
        (tpw.isInstanceOf[TypeRef] && {
          val tref = tpw.asInstanceOf[TypeRef]
          if (tref.symbol.isAbstractOrAliasType && !tref.info.hiBound.isNothingType) {
            println(s"  hibound: ${tref.info.hiBound}")
            isCheckable(tref.info.hiBound)
          } else
            false
        })

Is this expected, right?
If this is okay, I will add this test case.

@zielinsky
Copy link
Member

zielinsky commented Oct 25, 2025

@nox213,
@road21 created an issue (#24246) where there is a perfect test for type parameters ("abstract" type in terms of isAbstractOrAliasType). Could you add it also?

The test you sent does not test the change you made, so there is no point in adding it :/
Here's a test for abstract type members

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 
    case A => ()

@nox213 nox213 requested a review from zielinsky October 26, 2025 13:16
Copy link
Member

@zielinsky zielinsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@zielinsky zielinsky merged commit 520668f into scala:main Oct 26, 2025
51 checks passed
@WojciechMazur WojciechMazur added this to the 3.8.0 milestone Oct 28, 2025
tgodzik pushed a commit to scala/scala3-lts that referenced this pull request Nov 10, 2025
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>
tgodzik added a commit to scala/scala3-lts that referenced this pull request Nov 10, 2025
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>
[Cherry-picked 520668f][modified]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

no exhaustiveness check for subtypes of sealed hierarchies False pattern match not exhaustive for union types + opaque types

5 participants