Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exhaustive check on generic sealed class is considered not exhaustive #4199

Open
rrousselGit opened this issue Dec 6, 2024 · 2 comments
Open
Labels
request Requests to resolve a particular developer problem

Comments

@rrousselGit
Copy link

Consider the following sealed hierarchy:

sealed class Base<A> {}

sealed class Mid<A, B> extends Base<A> {}

final class Final<A, B> extends Mid<A, B> {}

We can then use it with pattern matching like so:

void main() {
  Base<int> base = Final<int, Object?>();

  switch (base) {
    case Final<int, Object?>():
      print('42');
  }
}

The problem is, this switch is considered non-exhaustive.

To have the compiler consider the switch as exhaustive, we incorrectly have to change Final<int, Object?>() into Final<Object?, Object?>().

This is incorrect, because given Base<int>, it is guaranteed that all instances will implement Final<int, ...>

Removing the intermediary Mid class fixes the issue, so it probably has to do with the added level of indirection.

@rrousselGit rrousselGit added the request Requests to resolve a particular developer problem label Dec 6, 2024
@simphotonics
Copy link

simphotonics commented Dec 10, 2024

Using Dart SDK version: 3.5.4 (stable) on linux_x64 and DartPad,
the switch expression below is considered to be exhaustively matched:

sealed class Base<A> {}
sealed class Mid<A, B> extends Base<A> {}
final class Final<A, B> extends Mid<A, B> {}

void main() {
  Base<int> base = Final<int, Object?>();

  switch (base) {
    case Final<int, Object?>():
      print('42');
    case Base<int>(): // Analyzer does not complain.
      print('never reached');
  }
}

Should it be even possible to match an "instance" of an abstract class?

@lrhn
Copy link
Member

lrhn commented Dec 10, 2024

This is a known shortcoming of the exhaustivness analysis. It doesn't work well with supertypes with type parameters that aren't used. See fx dart-lang/sdk#53486

That means that Final<int, Object?>() is not enough since the space it thinks it needs to exhaust is Mid<Object?, Object?>, and it only exhausts Mid<int, Object?>. That's because it can't figure out the connection between the B of Mid and Base<A>, so it gives up on both type parameters of Mid, and thinks you need to exhaust Mid<Object?, Object?> in order to exhaust Base<int>.
There is room for improvement in that area.

So case Base<int>() is considered reachable, and it does exhaust all of Base<int>. (The algorithm can see that.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests

3 participants