Description
Consider:
switch (obj) {
case int: print('int');
}
In Dart both before 3.0 and in 3.0 with patterns, this case does not match integers, it matches only the type object int
itself. Prior to 3.0, there was no way to do a type test in a switch, so users would rarely ever think to write the name of a type and expect it to type test.
With patterns, we expect type tests to be common in switches. There are two idiomatic ways to write them:
switch (obj) {
case int(): print('int');
case int _: print('int');
}
(The first, an object pattern, is useful when you want to do further destructuring on the value after testing its type. It's likely the idiomatic style to use in algebraic datatype style code. The latter, a wildcard variable declaration, allows you to match any possible type annotation. Object patterns only support named types, but not more complex type annotations like records, functions, nullable types, etc.)
Because type tests are common in patterns, we expect that users will sometimes write a type literal when they meant to do a type test. This is valid code (though in some cases it will lead to non-exhaustive switch errors), but it probably doesn't do what the user wants.
We tried hard to tweak the syntax to avoid this footgun, but every other approach we came up with seemed to be worse overall. We also considered making it an error to have a constant in a pattern that resolves to a type literal. This would force users away from the footgun... but it's also a breaking change. There is code in the wild today that deliberately uses type literals in switch cases to test for type objects.
Given that, I think it would help the user experience if we added a lint that reports a type literal being used as a constant in a switch case (in a Dart 3.0 library). Instead, it should suggest that users use either an object pattern or variable pattern if they meant to do a type test, or an explicit ==
pattern if they really did mean to compare for equality to a type literal.
The heuristic I'd suggest is:
-
If the matched value type is
Type
, then suggest they change the case fromcase SomeType
tocase == SomeType
. That has the same semantics, but makes it clearer to the reader that a type equality test is intended (and silences the lint). -
Otherwise, if the matched value type is anything else, suggest that they use
case SomeType _
if they indended to do a type test and== SomeType
otherwise.
This lint will be useful whenever it arrives, but nothing is blocked in 3.0 around having it. It should just help users avoid what's likely a mistake.
Activity
scheglov commentedon Mar 22, 2023
@pq @bwilkerson Do we want to make it s lint, or a warning inside the analyzer?
bwilkerson commentedon Mar 22, 2023
It isn't wrong to use a type literal in a switch case, so I see this as enforcing a coding style, which makes it a lint.
pq commentedon Mar 22, 2023
I'm all for this and agree with Brian that a lint is the right place to implement.
munificent commentedon Mar 22, 2023
Yes, if it were up to me (and it's not, this is really for the tooling teams to decide), I would make it a lint too. For me, warnings all represent some kind of dead code: Code you can remove or simplify and the result is a program with the same semantics.
bwilkerson commentedon Mar 22, 2023
The other major use for warnings is for code that's guaranteed to be wrong, but for reasons that the type system isn't able to express.
munificent commentedon Mar 22, 2023
Can you give me an example of the latter?
bwilkerson commentedon Mar 22, 2023
We have a warning telling you that the callback can't be used as an error handler:
The type system can't tell us that because the type of the parameter is
Function
.munificent commentedon Mar 22, 2023
Oh, interesting. So there's some ad hoc overloading support in the analyzer effectively?
40 remaining items