Currently every FlowModel creates a new empty map that's just supposed
to be empty. When compiling `compile.dart` (from the CFE) this creates
more than 200,000 maps for seemingly no reason.
This CL removes it, thus saving the creation of (...instrumenting the
platform...) 229,472 maps when compiling `compile.dart` (from the CFE).
Thinking it was done to avoid some polymorphism I have gone over the
created flowgraphs for the file in both JIT and AOT and found only
improvements.
AOT compiled `compile.dart` then compiling itself improves by ~3.5%:
```
Difference at 95.0% confidence
-0.1365 +/- 0.0576111
-3.55191% +/- 1.49912%
(Student's t, pooled s = 0.090011)
```
Change-Id: Ifdbb57e9aa3c23b2af512a2104aaf6caf72831ef
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301061
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
This fixes a minor bug in flow analysis which was preventing it from
recognizing when a switch statement was trivially exhaustive, meaning
one of its reachable cases was guaranteed to always match.
This mostly addresses
https://github.com/dart-lang/language/issues/2980, but flow analysis
still fails to recognize that:
- A list pattern containing a just a single rest pattern always
matches (unless the rest pattern has a subpattern that may fail to
match).
- A null check pattern always matches if its subpattern always matches
and the matched value type is non-nullable.
- The relational pattern `!= null` always matches if its subpattern
always matches and the matched value type is non-nullable.
Fortunately, these drawbacks are small and don't lead to unsoundness.
I'll try to address them in follow up CLs.
Bug: https://github.com/dart-lang/language/issues/2980
Change-Id: Ie9f8564cde66a5a2c41114033ca3ff0e1a0f139a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/293860
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Judging by several test cases that have shown up in co19 tests and
informal discussions, it appears to be a common expectation that a
pattern like `int? x?` or `int? x!` should promote `x` to
non-nullable. Previous to this change, this didn't work in general.
Consider:
Object? o = ...;
switch (o) {
case int? x?:
print(x.isEven); // (1)
}
At (1), the null-check happens *before* the required type check of the
variable pattern; therefore it promotes the matched value type to
`Object`. This is not a subtype of the required type of the variable
pattern (which is `int?`), therefore, previous to this change, `x` was
not promoted.
With this change, since the matched value type of `Object` is
non-nullable, the required type of the variable pattern is
re-interpreted as its non-nullable counterpart, `int`.
In a fully null-safe program, this is sound, because if the matched
value type is non-nullable, that guarantees that the matched value is
not `null`, and therefore it is equivalent to type check against the
non-nullable counterpart of the required type.
In a program that is not fully null-safe, the matched value might have
originated in a non-null-safe library (and thus might be `null` in
violation of its static type). So, strictly speaking, it is not sound
to re-interpret the required type of the pattern as its non-nullable
counterpart. However, the only way this unsoundness can manifest is
for the matched value to be promoted to non-nullable when it is in
fact `null`, and that is precisely the sort of unsoundness thta we
permit in mixed-mode programs. So this change won't result in
unsoundness escalation.
Bug: https://github.com/dart-lang/sdk/issues/51644
Change-Id: I9479e3c29e12f2a62a9e165b32c3480d7e299c29
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/287040
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This unit test makes sure that parenthesizing a variable pattern
doesn't destroy its ability to track the flow analysis consequences of
a boolean expression.
No code changes were are required, since the implementation passes the
test. But prior to
https://dart-review.googlesource.com/c/sdk/+/282482 this test would
have failed, so it seems reasonable to include it as a unit test to
prevent regressions.
Change-Id: If5595a346757e608bc133846062ec37f47bc3e1e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/284485
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Previously, if multiple switch cases shared a body, and at least one
of those cases promoted a match variable using a `when` clause, the
promotion would not be carried over to the merged variable seen in the
shared case body. This was happening because promotions performed in
the `when` clause were applied to the promotion key associated with
the client's representation of the variable, whereas flow analysis was
computing the state of the merged variable based on the promotion keys
used internally while visiting the pattern (which don't include
promotions from the `when` clause).
With this change, flow analysis now computes the merged variable state
based on the promotion keys associated with the client's
representation of the variable components, so promotions from `when`
clauses take effect.
In the process, I've moved a lot of the tracking of these promotion
keys from the `TypeAnalyzer` class to the `_FlowAnalysisImpl` class.
This avoids the need to expose more flow analysis internals through
its public API.
Fixes#51399.
Bug: https://github.com/dart-lang/sdk/issues/51399
Change-Id: I8f1bb6e49ceb8441bb743c63af61c119df9041f2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283441
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
For each subpattern of a record pattern, we define its "demonstrated
type" to be the type that the matched value has been promoted to if
the subpattern match succeeds. At the conclusion of visiting a record
pattern, we promote the whole record pattern's matched value to a
record type formed by combining together the demonstrated types of the
subpatterns. So, for example, the pattern `(int _, String _)`
promotes the matched value to `(int, String)`.
This change contains a lot of specific tests, to make sure the
demonstrated type of each kind of pattern behaves as expected; but the
actual machinery is general, since it just takes advantage of the type
promotion performed by the subpatterns.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Ie4da81964b5ade657c23a598ee18982b793fb4ac
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282806
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
to be reachable for `ifTrue` and `ifFalse`.
This is because checking only the type to determine the reachability is not correct, since for the list and map patters, the expression can match, but this is not always true.
Fixes#51353
Change-Id: Iea53fd192d16e786fa8f2540425fc3cb97664d10
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282580
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Previously, we kept track of the promotion information for a matched
value using a `ReferenceWithType` stored in the `_PatternContext`.
This information did not get updated in the event of a promotion, so
that meant that if a pattern tried to promote the same value twice
(e.g. `int _ && num _`), the second promotion attempt would not see
the effects of the first attempt, so it might wind up demoting the
matched value.
The solution is to just track the promotion key in `_PatternContext`,
and resynthesize the `ReferenceWithType` object (with the proper type)
whenever we need it.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: I026a4d2f42875a003ce8840dfc88d65484bb33ae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282800
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Previously, we stored an EqualityInfo object representing the
scrutinee at the time of entry to the top level of the pattern; then
when handling, for example, a relational pattern using `==`, we used
that EqualityInfo object to decide how to handle the equality check,
*even if we were no longer at the top level of the pattern*. As a
result, we would sometimes come to incorrect conclusions about
relational patterns inside subpatterns. For example, we would fail to
see that `if ((null,) case (== null,))` is a guaranteed match, because
when processing the `== null` pattern, we would erroneously use the
EqualityInfo for `(null,)` (which is *not* `null`).
To solve this, we only store the scrutinee *reference* at the top
level of the pattern (this is sufficient to allow us to decide whether
or not to promote the scrutinee). When we encounter a pattern like
`== null`, we compute the appropriate EqualityInfo directly based on
the ExpressionInfo, type, and reference that are appropriate to the
current pattern level.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: I4d1bc34fcb2d238e7b69e1b88df50b389410963c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282162
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
If a cast pattern fails to match, it throws an exception, so the
"match failure" code path should be unreachable.
To fix this, I added an optional argument `updateUnmatched` to
`patternRequiredType` to allow the type analyzer to tell flow analysis
whether a required type is enforced via a match failure or an
exception. I also renamed `patternRequiredType` to
`promoteForPattern`; this is in anticipation of an upcoming CL which
will use this method to promote to a type which isn't necessarily the
same as the required type.
I also discovered a few testing gaps while double-checking that every
call to `promoteForPattern` makes the proper choice about what to pass
for `updateMatched`; I've added test cases to cover these gaps.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Id6f63138d01b481e7c9442469ce45e2aaa507a5a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/281740
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
There are two new API methods: `equalityRelationalPattern_end` (for
relational patterns using `==` or `!=`, where some degree of flow
analysis is warranted) and `nonEqualityRelationalPattern_end` (for all
other relational patterns, where all we care about is making sure that
both the "matched" and "unmatched" branches are reachable).
I've moved most of the logic for constant patterns into
`equalityRelationalPattern_end`, since it's essentially the same logic
(except that `equalityRelationalPattern_end` also has support for
`!=`). So now, `constantPattern_end` simply calls
`equalityRelationalPattern_end` in the case where pattern support is
enabled.
I also had to make a change to `RelationalOperatorResolution` to make
it possible to distinguish `==` from `!=`.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Idc5dbcfb02e3f8b57cfcccb3f46364fe34268ded
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280900
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Since matching a constant pattern implicitly performs an equality
check, we do the same flow analysis for constant patterns that we
would have done for an explicit equality test. There are only two
user-visible behaviours:
- If the constant pattern is a `null` literal, then in the code path
where the pattern fails to match, the scrutinee is promoted to a
non-nullable.
- If the constant pattern and the scrutinee are both `null` literals,
the pattern match is known to always succeed (so the code path where
the pattern fails to match is marked as unreachable).
The first of these two behaviours is genuine useful to the user, since
it allows things like:
switch (expr) {
case null:
...
...
default:
// expr is known to be non-`null`.
}
The second behaviour is not so useful, but seemed worth doing to
maximize code sharing and to keep the behaviour consistent between
pattern matching and explicit null checks.
Making this work required extracting some of the logic that was
formerly in `_FlowAnalysisImpl.equalityOperation_end` into a method
`_equalityCheck`, which understands how to analyze an equality check
regardless of whether it's an explicit equality expression or an
implicit part of a pattern. It returns an `_EqualityCheckResult`,
which `equalityOperation_end` examines to determine exactly what needs
to be done to for an equality test in an expression context;
similarly, `_FlowAnalysisImpl.constantPattern_end` now calls
`_equalityCheck` and then does the right thing for patterns.
It was also necessary to extract the logic from
`nullCheckOrAssertPattern_begin` for handling null checks in patterns;
that logic is now in `_nullCheckPattern`, which is also called by
`constantPattern_end`.
In the process of testing this change, I discovered (and fixed) a
minor bug: when analyzing a switch statement or switch expression, we
were re-capturing the value of the scrutinee before visiting each
pattern. This meant, for example, that we would analyze the following
code incorrectly:
int? i = ...;
switch (i) {
case _ when f(i = ...):
...
case null:
...
default:
// `i` should *not* be promoted to non-null here, because it's
// not guaranteed to be the same as the value matched by `case
// null` above.
}
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: I4d30d6bc2673d9968e69f3384a37e1b2b9cc4a8f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280861
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Previously, we treated cast patterns as a simple type-check, relying
on promotion machinery to supply the correct matched value type for
the inner pattern. In other words `P as T` was analyzed like `T() &&
P`. This had two unforunate consequences:
(1) If the type `T` was not a subtype of the matched value type, no
promotion occurred. So for example, if `x` had type `int?`, then
`if (x case var y as String?)` caused `y` to get an inferred type
of `int?` (which is clearly not what the user wants).
(2) Any promotions triggered inside the inner pattern would propagate
to the outer pattern. So for example, if `x` had the type
`Object?`, then `if (x case int _ as num)` caused `x` to be
promoted first to `num` and then to `int`. Although this was
sound, it seemed to me that it was strange and unexpected
behaviour, because the typical user expectation when performing a
cast is that only the type being cast to should be used for
promotion.
The solution to both of these is simple: we analyze the inner pattern
as though its matched value is distinct from the matched value
supplied to the cast, and has a static type of the cast type. Even
though in reality we know that the two values are the same, it's sound
for flow analysis to ignore that information.
Fixing this made it possible to add tests of some other patterns flow
analysis behaviours that weren't previously testable, so I've included
several tests in this CL that aren't strictly testing the change.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: I8cf864c3bf20f55406cfd74aeb8891d8e81f93e2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280207
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
There are two ways type promotion can occur when switch cases share a body:
(1) the scrutinee variable (if any) might be promoted, e.g.:
f(Object x) {
switch (x) {
case int _ && < 0:
case int _ && > 10:
// `x` is promoted to `int` because both cases promote the
// scrutinee variable to `int`.
}
}
(2) explicitly matched variables might be promoted at the time of the
match, e.g.:
f<T>(T t) {
if (t is int) {
switch (t) {
case var x && < 0:
case var x && > 10:
// `x` has type `T` but is promoted to `T&int`, because
// both declarations of `x` are in a context where the
// matched value has type `T&int`.
}
}
}
The existing flow analysis logic handles case (1) without any extra
work, because those promotions are joined as a natural consequence of
the flow control join at the end of matching the cases.
However, flow analysis has to do some extra work for case (2), because
the two copies of variable `x` are associated with different variable
declarations (and hence have different promotion keys). To ensure
that the promotions are joined in this case, we need to copy the flow
model for the two copies of `x` into a common promotion key prior to
doing the flow control join.
The bookkeeping necessary to figure out a common promotion key is
similar to the bookkeeping for logical-or patterns.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: I9ee4ec5d797dae28099aafbaf34fbbeeee5cd626
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280201
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
There are three ways type promotion can occur in an or-pattern:
(1) the scrutinee variable (if any) might be promoted, e.g.:
f(Object x) {
if (x case int _ && < 0 || int _ && > 10) {
// `x` is promoted to `int` because both sides of the `||`
// promote the scrutinee variable to `int`.
}
}
(2) the implicit temporary variable that holds the matched value might
be promoted, e.g.:
f(Object Function() g) {
if (g() case (int _ && < 0 || int _ && > 10) && (var x)) {
// `x` has type `int` because both sides of the `||` promote
// the matched value to `int`.
}
}
For this sort of promotion to work, we need to
(3) explicitly matched variables might be promoted at the time of the
match, e.g.:
f<T>(T t) {
if (t is int) {
if (t case var x && < 0 || var x && > 10) {
// `x` has type `T` but is promoted to `T&int`, because both
// declarations of `x` are in a context where the matched
// value has type `T&int`.
}
}
}
The existing flow analysis logic handles cases (1) and (2) without any
extra work, because those promotions are joined as a natural
consequence of the flow control join at the end of matching the
logical-or pattern.
However, flow analysis has to do some extra work for case (3), because
the two copies of variable `x` are associated with different variable
declarations (and hence have different promotion keys). To ensure
that the promotions are joined in this case, we need to copy the flow
model for the two copies of `x` into a common promotion key prior to
doing the flow control join.
The bookkeeping necessary to figure out a common promotion key is
similar to the bookkeeping necessary to track the association between
the individual declared variable patterns and the joined pattern
variable (and this is bookkeeping that flow analysis is already
doing). So as part of this change I went ahead and removed the
`getJoinedVariableComponents` method (which was previously used by
flow analysis to query this association). This reduces the
constraints on the analyzer and CFE implementations by not requiring
them to do this bookkeeping themselves.
In the process I've made two additional small changes:
- I modified the logic for assigning types and finality to joined
variables so that if there is a finality conflict but no type
conflict, the common type is used; conversely, if there is a type
conflict but no finality conflict, the common finality is used.
This should help reduce follow-on errors.
- I added logic to ensure that if a variable is only declared on one
side or the other of a logical-or, flow analysis still considers
that variable to be definitely assigned. This should help reduce
follow-on errors.
Change-Id: I62f17adb6a51a583707c216ed48d941d1c621eea
Bug: https://github.com/dart-lang/sdk/issues/50419
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279756
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
The tables of subtypes and "factor" results used in testing the shared
analysis logic were becoming unwieldy and difficult to maintain.
Replace them with implementations of the subtype and factor algorithms
from the spec.
Change-Id: Ic607c3fda45a69661e094292a38a7bc8fd22859a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279748
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
A variable or wildcard pattern should only promote the scrutinee if
the expression is being matched is the scrutinee. If the variable
pattern is a subpattern, no promotion should occur.
E.g. promotion occurs here:
f(Object? x) {
if (x case int _) {
// `x` is promoted to `int`
}
}
But not here:
f(Object? x) {
if (x case num(sign: int _)) {
// `x.sign` is known to be an `int`, but `x` is simply a `num`.
}
}
Change-Id: Iaa5bab8ce5b81db9d5496ac0bf2ee10473302371
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279641
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Rather than allocate instances of PatternVariableJoin during the
"previsit" stage, the test is responsible for allocating them prior to
calling `run`. This will allow the test to include assertions in the
data structure passed to `run` that interrogate the state of the
joined variables (e.g. to check that they're properly promoted).
Change-Id: I7524c540872be153eb0a4cdf8bff679843335231
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279277
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
In a future CL I will need the ability for flow analysis to initialize
promotion keys that aren't associated with any particular variable.
In anticipation of that, this CL refactors `FlowAnalysis.initialize`
so that it immediately looks up the unpromoted type of the variable
(which is the only information it needs), and thereafter just uses the
variable's promotion key.
From the point of view of flow analysis clients, there is no
functional change.
Change-Id: I54794bde49c7af745b43a09914f70c9c4e6d48da
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279074
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Previously, if a switch case was unreachable, we would not insert a
synthetic break at the end of it. Technically this is not a problem
(since a synthetic break is only required to prevent one case from
falling through to another at runtime, and unreachable code is by
definition never reached at runtime). However, it makes it confusing
for CFE and back-end developers, and there's little harm in adding the
synthetic break anyway.
So with this change, we determine whether to add the synthetic break
by checking whether the bottom of the case block is reachable *from
its top* (rather than globally reachable).
Fixes#50994.
Bug: https://github.com/dart-lang/sdk/issues/50994
Change-Id: I17757b182c29da782457adc057b1b8a6fc91e55e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278897
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
A switch statement like this one:
switch (E) {
case P1 when G1:
S1;
case P2 when G2:
S2;
case P3 when G3:
}
Is equivalent to an if/else chain like this:
var tmp = E;
if (tmp case P1 when G1) {
S1;
} else if (tmp case P2 when G2) {
S2;
} else if (tmp case P3 when G3) {
S3;
}
Therefore, if the failure of a particular pattern/guard combination to
match implies a type promotion, it makes sense for that promotion to
be carried into later cases. For example:
int? x = ...;
switch (E) {
case _ when x == null:
break;
default:
x.isEven; // OK because `x` known to be non-null.
}
This enabled some more thorough testing of type promotion in switches,
which then caught a bug introduced in a previous CL: when the switch
scrutinee is a variable reference, and we are trying to determine
whether it is safe for a pattern to promote the scrutinee variable, we
were checking the wrong SSA node to determine whether the variable had
been reassigned. For example:
Object x;
switch (x) {
case _ when f(x = ...);
break;
case int _:
// `x` is not promoted to `int` because it is no longer the
// same as the cached scrutinee.
break;
}
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Ie8d6cf0fc662aa5ef0ac81eb2343952028dd2abb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278533
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Implement the necessary flow analysis bookkeeping so that in a pattern
of the form `P1 && P2`, any type checks performed by `P1` will be
reflected in the matched value type used by `P2`. To accomplish this,
we allocate a promotion key to represent the "cached value" matched by
every pattern and subpattern (so that `P1 && P2` promotes properly
even when in a subpattern of another pattern). This promotion key is
stored (in the form of a `ReferenceWithType<Type>`) in the
`_PatternContext` class.
Also, move `_scrutineeReference` and `_scrutineeType` into
`_FlowAnalysisImpl`; this preserves the existing behaviour of
promoting the scrutinee expression, but with less bookkeeping (since
we don't need to pass it along from one context to the next). And add
`_FlowAnalysisImpl._scrutineeSsaReference` so that if the scrutinee is
modified during execution of the switch statement, it won't be
erroneously promoted by patterns that follow.
Finally, implement some preliminary logic for `||` patterns to avoid
breaking existing tests. I will fill this out (and test it
thoroughly) in a follow-up CL.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: I0d934e706ad5b19cda46a198afb2fb7a4309829b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/275788
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
I previously thought that the only possible situation where a switch
statement over a valid type could be exhaustive without containing any
cases was if the scrutinee type was the empty record type (`()`). But
that isn't true at all:
- The empty record type is inhabited by empty record objects, so such
a switch statement would not be exhaustive after all.
- It is possible for the user to create a type that can be
exhaustively switched over with zero cases: by creating an abstract
sealed class without any subclasses.
In the latter case, I think it makes the most sense to treat the code
after the switch as unreachable, because that's the normal behaviour
of a switch over an exhaustive type with no `break`s. It is sound to
do so because the type is uninhabited, therefore the body of the
switch statement itself will never be reached.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Ibc0a21226f25ae155db994343874354d5b8e4f7d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/274621
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
When patterns support is disabled, flow analysis considers a switch
statement to be exhaustive if it has a `default` clause or if the
scrutinee type was an enum and all enum cases were covered (this
matches the behaviour of previous releases of Dart).
When patterns support is enabled, flow analysis considers a switch
statement to be exhaustive if it has a `default` clause or if the
scrutinee type is an "exhaustive type" (as defined in the patterns
spec). A later stage of analysis will check that such switch
statements truly are exhaustive, and issue a compile-time error if
they aren't.
Note that as part of this change I've modified the analyzer so that it
only attempts to track whether all enum cases are covered if patterns
support is disabled. I didn't make a corresponding change to the CFE
because the CFE stores exhaustiveness information in the kernel output
(`SwitchStatement.isExplicitlyExhaustive`) and I didn't want to break
that.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Ib2e51971a1b814c003401b3d54d41f7a9ef9f59a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/274720
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This CL adds support for flow analysis with variable patterns and
guards, and integrates it with if-case elements, if-case statements,
pattern variable declarations, switch expressions, and switch
statements. It includes support for guards.
No other types of patterns are handled yet.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Iacad82b472cba0e2e670981847258e4046017576
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/274162
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
The field `Var.errorId` was being used for two purposes:
- For checking error messages.
- To distinguish different variables with the same name when merging
variables in `||` and cases.
As a result, a lot of variables to be tagged with error IDs even in
tests that weren't generating any errors, which was confusing.
This change creates a new `Var.identity` field which is used for
merging variables in `||` and cases; it defaults to the variable name
but may be overridden in tests where distinguishing variables of the
same name is important.
Change-Id: Ieb587f434520dc484180aaa72658c899a2eb06d0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273824
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
By creating a mixin shared by the `Pattern` and `GuardedPattern`
classes, we can avoid having to explicitly write `.noGuard` in cases
where a pattern doesn't have a corresponding guard.
Change-Id: I9178674306fca1c6517a3c54f27fb2f28cf17716
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/270229
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This change refactors the logic for detecting overlapping and missing
variable patterns so that it can be invoked prior to the rest of type
analysis, rather than during it. In addition separating concerns
nicely (since no types are involved in these checks), I believe this
will facilitate integration with the analyzer and front end, by
allowing them to detect these errors and find the unique set of
variables defined by a pattern, at the time they are resolving
identifiers to their corresponding declarations.
Change-Id: I40879fca46d39e78a60813db007983e57a3aec31
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/259021
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>