-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Disclaimer
I am aware, that the issue I am about to report may be a non-issue after all, because I am just missing an important mechanic with sealed classes, that makes the change I am suggesting impossible to implement. Or maybe there is already an analyzer/linter rule that covers this case, and I am just not aware of its existence.
I did look through the existing issues, but did not find one about exactly this question/suggestion, so I decided to create a new one.
Dart version: 3.5.1
Minimal reproduction: https://github.com/Yegair/pattern_never_matches_value_type_reproduction
What is the problem?
Assuming there are two sealed classes sealed class Foo and sealed class Bar, where no implementation of Foo implements or extends Bar and vice versa.
sealed class Foo {}
class FooImpl extends Foo {}
sealed class Bar {}
class BarImpl extends Bar {}When pattern matching a value that is known to be of type Foo at compile time against a pattern of Bar(), it should be clear, that this pattern match will never succeed.
switch (FooImpl()) {
// ❌ does not cause an error or a warning like: pattern_never_matches_value_type
case Bar():
print('matched Bar()');
break;
case Foo():
print('matched Foo()');
break;
}The problem is, that no warning is shown, neither by the compiler nor the analyzer, explaining that the pattern can never match the type. I.e. running dart analyze on this code causes the analyzer to report: No issues found!
It is possible to get the desired warning by adding a final modifier to all implementations of Foo.
sealed class Foo {}
- class FooImpl extends Foo {}
+ final class FooImpl extends Foo {}
sealed class Bar {}
class BarImpl extends Bar {}Now trying to pattern match on Bar() correctly yields an analyzer warning pattern_never_matches_value_type.
switch (FooImpl()) {
// ✅ correctly causes warning: pattern_never_matches_value_type
case Bar():
print('matched Bar()');
break;
case Foo():
print('matched Foo()');
break;
}However, assuming that sealed classes are effectively final, it should be possible for the compiler/analyzer to figure out, that a value of type Foo can never match the pattern Bar() even if the implementations of Foo are not explicitly marked as final.
Why does it matter?
I did a rather large change on a big project, replacing a sealed class FooFromApi in many (but not all) cases with another sealed class FooFromDatabase.
Unfortunately there was no support by the analyzer/compiler that helped finding all the cases where patterns were still trying to match FooFromApi(...), that should now instead match FooFromDatabase(...). In the end, I overlooked one such pattern that was not covered by tests, which then introduced a bug into the application logic.
Since the project uses Freezed to generate most of the data classes, it is not possible to make the implementations of the sealed classes final. Thus it would be really helpful if the analyzer/compiler could take care of this, since the usage of Freezed is very common in Flutter/Dart projects these days.
Even without the usage of Freezed or a similar tool, one might just forget to put final modifiers on all implementations of sealed classes, in which case support by the analyzer/compiler would also help a lot.