It turns out that some time ago I got rid of the logic that depends on
this; the associated functionality is still there, but it's tracked
automatically by flow analysis via
`_PatternContext._matchedValueInfo`, so we no longer need to track it
in the shared analysis logic.
Change-Id: Ia38e0baf5f599c929aa2000aade41473b997f80a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/284480
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@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>
This adds support for record types in the exhaustiveness algorithm.
The original algorithm was based on that record pattern would
match fields on all types, but that is no longer the case. Instead
record patterns only match corresponding to their own type. For this
reason the testing code is updated to create record spaces in relation
to a type. For instance, when the test create a record space {x: B}, it
is know create in relation to a type, say (x: A, y: A), and the create
space will therefore have (x: *, y: *) structure where the y: component
is implicitly Top, similar to how object patterns are used.
Unlike the Dart record types used for type checking and inference, the
record types used for exhaustiveness do not take the field types into
account for its subtype relation. This is avoid conclusions like
(int i, Object o) and (Object o, int i) having no values in common because
their corresponding types (int, Object) and (Object, int) are not subtypes
of each other. Instead, the subtype relation for record types used for
exhaustiveness only use the structure of the record to determine whether
two types are related.
Note though, that fields of a record type still know the type of the field.
This is used when expanded a record type into a space; the field spaces
will be derived from the field types in this case.
Change-Id: I84735d827494bcf384fd5f419d71933830ff5d15
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283182
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
This optional error fires if patterns are enabled and the type
analyzer encounters a switch that is required to be exhaustive (i.e. a
switch expression, or a switch statement with an always-exhaustive
type), and flow analysis cannot prove that the switch is exhaustive.
This is intended to be available as a temporary workaround if we
decide to ship an early beta of the "patterns" feature before
exhaustiveness checking is sufficiently ready. We won't enable it
unless we need to, and we won't ship the final patterns feature with
it enabled.
Change-Id: I8c4c65c21abdc32b91537b3f30e7fd86f446f571
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283060
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This makes the unit tests clearer, since it's possible to tell the
meaning of each argument. It also simplifies the underlying unit
testing logic that formats the errors.
Change-Id: I7212fe0c909ca0b627f8a8889621af3a62a63f00
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283132
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This allows the parser to parse constant patterns at lower precedence
level in order to recognize more expressions in this context. To
support this, _parsePrecedenceExpressionLoop special cases a few cases
that should _not_ be parsed as expression in a constant pattern context.
Closes#50996
Change-Id: I43bb0ce52d366bd2dfcf47e12eec5883402f668a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282100
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
The `numMergedCases` argument was a holdover from the old analyzer
representation of switch statements (in which cases sharing a body
were not merged); it is no longer useful.
Also, the analyzer tests in `switch_case_completes_normally_test.dart`
have been adjusted so that they no longer expect this error to be
issued when pattern support is enabled; this is consistent with the
spec (which removes this error).
Change-Id: Ia22a35b7a94ee585e0614d85012ee80b9dbc12d8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/283125
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This replaces the StaticTypeImpl with an implementation of the shared
exhaustiveness classes for a test type system.
This will ensure that the unittests test the shared part of the code
and make it easier to handle for instance fields on Object and
non-interface types.
Change-Id: I101cdd41cf86b2847bd5959c15d4138640e34679
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282882
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: 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>
This is an implementation of `sync*` via two main mechanisms:
- The closure context infrastructure is used for preserving local
state. All local variables in `sync*` functions are implicitly
captured in the contexts even if they are not captured by a lambda.
- Suspension and resumption of the body is implemented via a state
machine as a switch in a loop. This allows for an arbitrary control
flow graph that can be resumed at any point. A subclass of the code
generator generates control constructs containing any `yield` or
`yield*` statements as jumps around this CFG while delegating the
rest of the code generation to the normal member code generator.
This version does not support `switch` or `try` inside a `sync*`
function. Support for these statements will be added later.
Change-Id: Iec8236f64500d823f574aa628ddb0d22fe4ac2d0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280166
Reviewed-by: Joshua Litt <joshualitt@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Johnni Winther <johnniwinther@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>
This will make it possible to generalize the flow analysis logic we
already have for equality expressions and calls to `identical`, so
that we can use it for constant patterns (which implicitly do an
equality check) and relational patterns involving `==` or `!=`.
This requires adding a new method to the flow analysis API,
`FlowAnalysis.assignedVariablePattern`, to handle variable references
appearing inside a pattern assignment. Previously we were able to use
`FlowAnalysis.write` for this purpose, relying on its call to
`getExpressionInfo` to gather the relevant information about the RHS
of the assignment. But now, since we are calling
`equalityOperand_end` from `_pushScrutinee`, the expression info is no
longer available at the time we visit the variable reference.
Change-Id: I9a28bced97dcf6ee15674eaa4430910121185489
Bug: https://github.com/dart-lang/sdk/issues/50419
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280219
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>