Background: null-shorting is a feature of Dart in which an expression
might evaluate to `null` because a `null` was found deep in a
subexpression, skipping evaluation of the rest of the expression. For
example, in the expression `a?.b(c).d`, if `a` evaluates to `null`,
execution will skip the evaluation of `c`, the method call to `b`, and
the get of `d`, will be skipped, and the whole expression `a?.b(c).d`
will evaluate to `null`. The analyzer needs to account for this by
making the static type of `a?.b(c).d` nullable, even if the type of
the `d` getter is not nullable.
The analyzer's implementation of null shorting works like this:
- When visiting a null-aware expression (such as `a?.b(c)` in the
above example), the analyzer uses
`NullShortableExpression.nullShortingTermination` to find the "null
shorting termination expression". This is the expression that will
evaluate to `null` if the null-shorting code path is taken
(`a?.b(c).d` in the above example).
- The null shorting termination expression is pushed onto the stack
`ResolverVisitor._unfinishedNullShorts`.
- After visiting an expression that might be a null shorting
termination expression, the analyzer passes the node that was just
visited to `ResolverVisitor.nullShortingTermination`, which checks
whether it matches any nodes at the top of the
`ResolverVisitor._unfinishedNullShorts` stack. If any nodes match,
they are popped off the stack, and the static type of the null
shorting termination expression is made nullable.
A subtlety with this approach is that if the resolution process has
rewritten the null shorting termination expression, then there are two
expressions in play: the old one (from the original AST, before
rewriting) and the new one (after rewriting). The node in
`ResolverVisitor._unfinishedNullShorts` is the old one, because the
code that pushes nodes onto the stack happens before rewrites. But the
node that needs to have its static type changed is the new one,
because that's the one that will wind up in the final resolved AST. In
fact, trying to change the static type of the old node would lead to a
crash, because the old node doesn't have a static type assigned.
Previous to this CL, the analyzer dealt with this situation by having
`ResolverVisitor.visitMethodInvocation` pass `discardType: true` to
`ResolverVisitor.nullShortingTermination`. This disabled the logic for
changing the type of the null shorting termination expression, which
avoided a crash, but it meant that the type was never updated
properly, leading to https://github.com/dart-lang/sdk/issues/56896.
The proper solution is to pass both the old and new nodes to
`ResolverVisitor.nullShortingTermination`. The old node is matched up
against `ResolverVisitor._unfinishedNullShorts`, and the new node is
used for marking the static type as nullable.
As part of this fix, I've changed the return types of methods in
`MethodInvocationResolver` from `FunctionExpressionInvocation?` to
`FunctionExpressionInvocationImpl?`. This is a harmless change, since
all these methods are private to the analyzer, and it avoids some type
casts in `ResolverVisitor.nullShortingTermination`.
I also added an assertion to `ResolverVisitor.nullShortingTermination`
to verify that the correct rewritten node is passed in (this assertion
relies on the `ResolverVisitor._replacements` expando, which is only
populated when assertions are enabled). Adding this assertion exposed
two other call sites that had to be updated (in
`ResolverVisitor.visitIndexExpression` and
`ResolverVisitor.visitPropertyAccess`). I've added additional analyzer
tests to cover these cases.
Fixes https://github.com/dart-lang/sdk/issues/56896.
Bug: https://github.com/dart-lang/sdk/issues/56896
Change-Id: I8119a05b4d9f386b1129e5419b823a61677358c5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/390560
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Collects files from `package:async_helper` and `tests/language`
that are generally useful, so that all test-related helpers are
in `package:expect`.
Moves the two libraries from `package:async_helper` into `package:expect`,
and the `tests/language/static_type_helper.dart` file too.
Deprecates `async_minitest.dart`, to follow `minitest.dart`,
expecting the Flutter use of it to have been fixed to not break
on deprecation (I believe Flutter no longer breaks builds on deprecations at all).
Patch 1 is the actual change.
Patch 2+4+8 is changing all existing references to the files.
Patch 6 ignores deprecation in files still using `async_minitest.dart`.
3+5+7+9 are updating this text to make the numbers match.
Then it's just test-expectations and small tweaks from there.
Change-Id: I1b665135b5fef9b9a0c3b340ffe9daf874d0174c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373120
Reviewed-by: Nate Bosch <nbosch@google.com>
Reviewed-by: Devon Carew <devoncarew@google.com>
Commit-Queue: Lasse Nielsen <lrn@google.com>
Several unusual constructs that lead to unreachable code are now
recognized by flow analysis:
- Control flow after an expression of the form `e ?? other` or `e ??=
other`, where `e` has static type `Null` and `other` has static type
`Never`, is considered unreachable.
- Control flow predicated on an expression of the form `e is Never`
evaluating to `true` is considered unreachable.
- Control flow predicated on an expression of the form `e is! Never`
evaluating to `false` is considered unreachable.
- Control flow on the RHS of a null-aware access such as
`e?.property...`, `e?.property = ...` or `e?.method(...)`, where `e`
has static type `Null`, is considered unreachable (Note: this can
arise in the presence of extension methods).
Previously, these behaviors only took effect if `e` was a reference to
a local variable.
Note: the change to `regress/issue_31180` is because I’ve corrected
the behavior of implicit temporary variables to not undergo a type
change from `Null` to `dynamic`, so the dead code part of `null?[1]`
is now erroneous. (I had to make this change in order for the last
bullet above to work properly; without it, the type change to
`dynamic` prevents flow analysis from recognizing that the code to the
right of `?.` is unreachable.) There's no behavioral change to
correct code, but I've captured the behavioral change to incorrect
code in
`tests/language_2/null_aware/null_aware_index_on_null_error_test.dart`.
Bug: https://github.com/dart-lang/sdk/issues/49635
Change-Id: I8b24b3b040a34f897c0b61dcb9bd105be6d0af6d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251280
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Bob Nystrom <rnystrom@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
This currently fails for the CFE with:
static error failures:
- Unexpected error at line 137, column 14: A value of type 'int?' can't be assigned to a variable of type 'int' because 'int?' is nullable and 'int' isn't.
- Unexpected error at line 137, column 25: Operand of null-aware operation '!' has type 'int' which excludes null.
Bug: https://github.com/dart-lang/sdk/issues/47338
Change-Id: Ia55e19a0bf296a8427f6510d6ddda5016acdecea
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/215101
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jake Macdonald <jakemac@google.com>
This changes the span reported from the _receiver_ to
the _use_ (method name, property name, operator token).
I think this change is overall an improvement.
Specifically, its a great improvement for cmdline
output, where the receiver and the "use" are on
different lines.
One possibly weird change is that if the operator is `[]`,
then I only highlight the `[` character. I don't know if
there is a better place, and I think this is fine.
Fixes https://github.com/dart-lang/sdk/issues/43708
Change-Id: Ie66ddf04b4904a367575193106385dd63ee39985
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/188680
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>