Commit Graph

219 Commits

Author SHA1 Message Date
Jens Johansen c0c9c98787 [flow analysis] Don't create a new empty map for every FlowModel
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>
2023-05-04 06:25:28 +00:00
Paul Berry dc639c13cb Patterns flow analysis: recognize [...] (and related patterns) as trivially exhaustive.
If a list pattern consists of a single rest pattern, and that rest
pattern is guaranteed to match, then the whole list pattern is
guaranteed to match as well (provided that the matched value type is a
subtype of the list pattern's required type).

Bug: https://github.com/dart-lang/language/issues/2980
Change-Id: I316cc93d4e696f094716be92e1fbc1cd3a43a73c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/294622
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-04-11 19:54:20 +00:00
Paul Berry 2ec3b513db Patterns flow analysis: recognize trivially exhaustive switches.
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>
2023-04-05 23:32:28 +00:00
Konstantin Shcheglov 1ec1e7358a Changes for map pattern: report an error for rest elements, empty map pattern.
Bug: https://github.com/dart-lang/language/issues/2861
Change-Id: I00ccb3ea03aa476f96c2ecf3e3a9e13bd4926193
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/291940
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Marya Belanger <mbelanger@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
2023-03-30 17:54:06 +00:00
Paul Berry df7c4bb439 Allow empty switch expressions.
This an experimental change; we haven't yet decided whether we want to
allow this.  See https://github.com/dart-lang/language/issues/2939 for
discussion.

Bug: https://github.com/dart-lang/language/issues/2939
Change-Id: If69ab66d18ae7ec3dfc79496ce13517ef1c15227
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/290907
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
2023-03-24 20:22:52 +00:00
Paul Berry adbf363009 flow analysis: don't promote scrutinee of irrefutable patterns.
As discussed in
https://github.com/dart-lang/language/issues/2857#issuecomment-1458522087,
we only want a pattern match to promote the scrutinee in refutable
contexts (if-case and switch).

Bug: https://github.com/dart-lang/language/issues/2857, https://github.com/dart-lang/sdk/issues/50419
Change-Id: I187ef632e5da95b931e5cda34db06491a5228c98
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/288000
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-03-21 21:17:12 +00:00
Johnni Winther e54fdb19a1 [cfe] Use library features for allow patterns in the parser
The parser used the global features and not the library features to
determine whether pattern syntax should be expected.

Closes #51625
Closes #51626

Change-Id: I3adc05468c0f9d2c5b919dc7fa12a5de4dcbbc7b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/288504
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
2023-03-14 11:37:28 +00:00
Paul Berry 5f8c28e226 Patterns flow analysis: promote to non-nullable when matched value is non-nullable.
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>
2023-03-07 17:12:22 +00:00
Paul Berry 6b33d5f42a Flow analysis: test parenthesized patterns.
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>
2023-02-22 17:19:17 +00:00
Paul Berry 6e6f89a255 Flow analysis: fix handling of promotions in when clauses with shared case bodies.
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>
2023-02-16 18:11:14 +00:00
Paul Berry b10ec24fe9 Flow analysis: implement type promotion for record patterns.
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>
2023-02-14 14:42:36 +00:00
Konstantin Shcheglov 6a6b54c7d3 Report unnecessaryWildcardPattern()
Change-Id: I6bca9eb8ec95796bdbcb02a017e919c424d8415d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282482
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
2023-02-14 01:07:33 +00:00
Ahmed Ashour 875c10f6fe [flow analysis] list pattern and map pattern
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>
2023-02-14 00:10:54 +00:00
Paul Berry e6a9446813 Flow analysis: don't de-promote pattern matched values.
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>
2023-02-13 20:18:47 +00:00
Paul Berry 90feb064f3 Flow analysis: promote when assigning to variables in patterns.
Most pattern variable assignments don't need to promote, because
pattern variable assignments must be irrefutable, and thus the pattern
match cannot fail.  There's one exception, though: when the assigned
value has type `dynamic`, an implicit downcast is performed.

Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Ic8f572303d60c354e9e342f79af03e669fa248c3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282381
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
2023-02-13 15:55:19 +00:00
Paul Berry 95e6dba347 Flow analysis: add missing tests for wildcard patterns
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: If38ad38cefbdca97144dd853c05aeca3ddb61cf2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282320
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-02-13 15:34:17 +00:00
Paul Berry 98f585d092 Flow analysis: fix improper handling of scrutinee EqualityInfo.
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>
2023-02-10 15:24:08 +00:00
Paul Berry c72a10f5c5 Flow analysis: fix reachability for cast patterns.
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>
2023-02-09 14:47:48 +00:00
Paul Berry a66dcf1b77 Flow analysis tests: use a trailing comma for unary types
Change-Id: I0183c332492b298d5d3db70f7222111757a0e364
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/281544
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-02-08 15:38:48 +00:00
Paul Berry 832ae6722d Flow analysis: add support for relational patterns.
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>
2023-02-07 21:08:50 +00:00
Paul Berry 3d01b0aac3 Flow analysis: add support for constant patterns.
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>
2023-02-07 18:24:54 +00:00
Konstantin Shcheglov 007562a06a Report matchedTypeIsSubtypeOfRequired for cast pattern.
https://github.com/dart-lang/language/pull/2813

Change-Id: If8942efca4fa736cb24c625bc83963bbd69302f0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/281060
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
2023-02-06 20:53:50 +00:00
Paul Berry 5afdebf219 Flow analysis: fix handling of cast patterns.
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>
2023-02-02 17:05:08 +00:00
Paul Berry 167cf8e637 Flow analysis: implement type promotions for switch cases that share a body.
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>
2023-02-01 18:04:35 +00:00
Paul Berry 459b2bf80e Flow analysis: implement type promotions for or-patterns.
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>
2023-02-01 17:18:17 +00:00
Paul Berry 9b9cf3c928 Flow analysis: allow null to be passed to handleBreak/handleContinue
This should allow for better error recovery in the case where a label
target can't be found.

Change-Id: I05ec107a4ecef3f73cfba5931b77b8250707d2ec
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/280120
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-01-31 17:00:40 +00:00
Paul Berry 5668aa4732 Flow analysis: Add patternRequiredType.
This method is used for declared variable patterns, list patterns, map
patterns, record patterns, object patterns, and wildcard patterns to
cause the value being matched to be promoted.

As a temporary measure it's also used for cast patterns; this will be
fixed in a later CL.

Change-Id: Iccc9ce4a53e2a10753847f9ecade091f1b230111
Bug: https://github.com/dart-lang/sdk/issues/50419
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279653
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-01-26 15:54:17 +00:00
Paul Berry e396024712 Shared inference testing: replace factor and subtype tables with an algorithm.
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>
2023-01-26 15:53:47 +00:00
Paul Berry 61f23df80f Flow analysis for null-check/null-assert patterns.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: I7fd845b0e465b63a2a9fd5e17bf254b9073a7d1f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279459
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
2023-01-25 16:37:30 +00:00
Paul Berry c90d7839eb Patterns flow analysis: don't promote scrutinee based on subpatterns.
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>
2023-01-25 00:02:21 +00:00
Paul Berry ba8f0bd947 Shared type analysis: rework testing of joined pattern variables.
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>
2023-01-21 01:02:49 +00:00
Paul Berry 03e1659cc4 Flow analysis: reduce the extent to which variable is used by initialize().
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>
2023-01-17 22:55:39 +00:00
Paul Berry 510cf2b258 Flow analysis: fix synthetic break insertion for unreachable switch cases
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>
2023-01-13 14:25:58 +00:00
Paul Berry 78ac63303b Flow analysis: fix handling of patterns inside guards.
Thanks to Konstantin for noticing this issue.

Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: I0380b846839c5afa1b20a52d55d980ddb6d09fec
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278810
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-01-11 00:02:16 +00:00
Paul Berry 5b234e4b0d Flow analysis: properly model if/else nature of switches.
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>
2023-01-09 18:22:51 +00:00
Ahmed Ashour 17abdd6d2d [flow analysis] fix label statement
Fixes #50294

Change-Id: I39503270f82788a9edb4ce6c53251b0e0ebb05fb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/277400
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-01-05 13:49:40 +00:00
Paul Berry f835846705 Flow analysis: add support for promotion of cached values.
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>
2022-12-16 21:11:25 +00:00
Paul Berry c430a3b4aa Patterns flow analysis: fix handling of empty exhaustive switch statements.
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>
2022-12-13 14:36:57 +00:00
Paul Berry 3a1cc9faf6 Flow analysis: update exhaustiveness logic for patterns.
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>
2022-12-12 19:15:53 +00:00
Paul Berry d0fc6ec27b Flow analysis: account for implicit break at the end of switch statement cases.
Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Ie9b0dd3319dd4e28f53082794aa18ed422b0894b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/274605
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2022-12-09 17:20:38 +00:00
Paul Berry 966aed6bd1 Shared pattern analysis: add support for pattern variable assignment.
Bug: https://github.com/dart-lang/sdk/issues/50585
Change-Id: I1939c6fd8fac205abeac5b0a9b3da9b3b4adca01
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/274604
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
2022-12-09 17:16:38 +00:00
Paul Berry ea9b19c2c4 Begin implementing flow analysis for patterns.
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>
2022-12-09 06:00:36 +00:00
Paul Berry 0fd211facd Shared analysis testing: Distinguish Var.errorId from variable identity.
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>
2022-12-07 22:10:35 +00:00
Paul Berry 759a742c16 Shared analysis: update ifCase API to match if_.
This change makes the API for the `ifCase` method the same as that of
`if_`, which should make it easier to write flow analysis unit tests
for if-case statements.

Bug: https://github.com/dart-lang/sdk/issues/50419
Change-Id: Iea7e0fabba9966eae52c0d28b52d2f8fcea25e46
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273820
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2022-12-07 22:02:32 +00:00
Paul Berry 03f6c4c7ce Shared analysis logic: remove .noGuard from tests.
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>
2022-11-17 15:35:07 +00:00
Konstantin Shcheglov d4c443cfb7 Enable annotate_overrides lint in _fe_analyzer_shared/
Change-Id: Ic8e34af296368e8159fa75887ed54cbb4b550648
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/270226
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
2022-11-17 15:13:39 +00:00
Konstantin Shcheglov 5ce9338197 Implement variables and scopes rules.
Change-Id: Ice0b5375b8a5a0f9fa69c9d1a9aeb6241157dfb9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/269540
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
2022-11-16 21:56:22 +00:00
Paul Berry 6318d7f2e5 Move "mini-AST" type operations to their own class.
This will allow them to be re-used in other _fe_analyzer_shared test
files.

Change-Id: Ib9c321ba8985241f2ad2c1783e9896f3f1e57408
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/264960
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
2022-10-21 19:58:03 +00:00
Paul Berry d695d95263 Shared type analysis: add "if-case" support.
Change-Id: I7c956239dd050c3c07ff85508180c451a70ca8a8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/257601
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2022-09-16 13:41:31 +00:00
Paul Berry a7cebc7349 Shared type analysis for patterns: Refactor VariableBindings logic.
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>
2022-09-13 17:07:48 +00:00