Initial version of DefiniteAssignmentTracker.
I think it supports all expressions and statements.
Change-Id: I753bd6ce781f54c1dfd0f15f69fca2f15c2a1877
Reviewed-on: https://dart-review.googlesource.com/c/87423
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/dart/resolver/definite_assignment.dart b/pkg/analyzer/lib/src/dart/resolver/definite_assignment.dart
new file mode 100644
index 0000000..0b1cd1d
--- /dev/null
+++ b/pkg/analyzer/lib/src/dart/resolver/definite_assignment.dart
@@ -0,0 +1,793 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:meta/meta.dart';
+
+/// Object that tracks "assigned" status for local variables in a function body.
+///
+/// The client should create a new instance of tracker for a function body.
+///
+/// Each declared local variable must be added using [add]. If the variable
+/// is assigned at the declaration, it is not actually tracked.
+///
+/// For each read of a local variable [read] should be invoked, and for each
+/// write - [write]. If there is a "read" of a variable before it is
+/// definitely written, the variable is added to output [readBeforeWritten].
+///
+/// For each AST node that affects definite assignment the client must invoke
+/// corresponding `beginX` and `endX` methods. They will combine assignment
+/// facts registered in parts of AST. These invocations are expected to be
+/// performed as a part of the resolution pass over the AST.
+///
+/// In the examples below, Dart code is listed on the left, and the set of
+/// calls that need to be made to [DefiniteAssignmentTracker] are listed on
+/// the right.
+///
+///
+/// --------------------------------------------------
+/// Assignments.
+///
+/// When the LHS is a local variable, and the assignment is pure, i.e. uses
+/// operators `=` and `??=`, only the RHS is executed, and then the
+/// variable on the LHS is marked as definitely assigned.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// int V2; // add(V2)
+/// V1 = 0; // write(V1)
+/// V2 = V2; // read(V2) => readBeforeWritten; write(V2)
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => readBeforeWritten (already)
+/// ```
+///
+/// In compound assignments to a local variable, or assignments where the LHS
+/// is not a simple identifier, the LHS is executed first, and then the RHS.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// List<int> V2; // add(V2)
+/// V1 += 1; // read(V1) => readBeforeWritten; write(V1)
+/// V2[0] = (V2 = [0])[0]; // read(V2) => readBeforeWritten; write(V2)
+/// V1; // read(V1) => readBeforeWritten (already)
+/// V2; // read(V2) => readBeforeWritten (already)
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Logical expression.
+///
+/// In logical expressions `a && b` or `a || b` only `a` is always executed,
+/// in the enclosing branch. The expression `b` might not be executed, so its
+/// results are on the exit from the logical expression.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// int V2; // add(V2)
+/// (
+/// V1 = 1 // write(V1)
+/// ) > 0
+/// && // beginBinaryExpressionLogicalRight()
+/// (
+/// V2 = V1 // read(V1) => OK; write(V2)
+/// ) > 0
+/// ; // endBinaryExpressionLogicalRight()
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// assert(C, M)
+///
+/// The `assert` statement is not guaranteed to execute, so assignments in the
+/// condition and the message are discarded. But assignments in the condition
+/// are visible in the message.
+///
+/// ```dart
+/// bool V; // add(V)
+/// assert( // beginAssertStatement()
+/// V // read(V) => readBeforeWritten
+/// ); // endAssertExpression()
+/// V // read(V) => readBeforeWritten
+/// ```
+///
+/// ```dart
+/// bool V; // add(V)
+/// assert( // beginAssertExpression()
+/// V = true, // write(V)
+/// "$V", // read(V) => OK
+/// ); // endAssertExpression()
+/// V // read(V) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// if (E) {} else {}
+///
+/// The variable must be assigned in the `then` branch, and the `else` branch.
+///
+/// The condition `E` contributes into the current branch.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// int V2; // add(V2)
+/// int V3; // add(V3)
+/// if (E)
+/// { // beginIfStatementThen()
+/// V1 = 0; // write(V1)
+/// V2 = 0; // write(V2)
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => OK
+/// } else { // beginIfStatementElse()
+/// V1 = 0; // write(V1)
+/// V3 = 0; // write(V3)
+/// V1; // read(V1) => OK
+/// V3; // read(V3) => OK
+/// } // endIfStatement(hasElse: true)
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => readBeforeWritten
+/// V3; // read(V3) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// while (E) {}
+///
+/// If `E` is not the `true` literal, which is covered below, then the body of
+/// the loop might be not executed at all. So, the fork is discarded.
+///
+/// The condition `E` contributes into the current branch.
+///
+/// ```dart
+/// int V; // add(V)
+/// while ( // beginWhileStatement(labels: [])
+/// E
+/// ) { // beginWhileStatementBody(isTrue: false)
+/// V = 0; // write(V)
+/// V; // read(V) => OK
+/// } // endWhileStatement()
+/// V; // read(V) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// while (true) { ...break... }
+///
+/// Statements `break` and `continue` in loops make the rest of the enclosing
+/// (or labelled) loop ineligible for marking variables definitely assigned
+/// outside of the loop. However it is still OK to mark variables assigned and
+/// use their values in the rest of the loop.
+///
+/// ```dart
+/// int V;
+/// while ( // beginWhileStatement(labels: [])
+/// true
+/// ) { // beginWhileStatementBody(isTrue: true)
+/// if (condition) { // beginIfStatement(hasElse: false)
+/// break; // handleBreak(while);
+/// } // endIfStatement()
+/// V = 0; // write(V)
+/// V; // read(V) => OK
+/// } // endWhileStatement()
+/// V; // read(V) => readBeforeWritten
+/// ```
+///
+/// Nested loops:
+///
+/// ```dart
+/// int V1, V2;
+/// L1: while ( // beginWhileStatement(node)
+/// true
+/// ) { // beginWhileStatementBody(isTrue: true)
+/// while ( // beginWhileStatement(labels: [])
+/// true
+/// ) { // beginWhileStatementBody()
+/// if (C1) { // beginIfStatement(hasElse: true)
+/// V1 = 0; // write(V1)
+/// } else { // beginIfStatementElse()
+/// if (C2) { // beginIfStatement(hasElse: false)
+/// break L1; // handleBreak(L1: while)
+/// } // endIfStatement()
+/// V1 = 0; // write(V1)
+/// } // endIfStatement()
+/// V1; // read(V1) => OK
+/// } // endWhileStatement()
+/// V1; // read(V1) => OK
+/// while ( // beginWhileStatement(node)
+/// true
+/// ) { // beginWhileStatementBody(isTrue: true)
+/// V2 = 0; // write(V2)
+/// break; // handleBreak(while)
+/// } // endWhileStatement()
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => OK
+/// } // endWhileStatement()
+/// V1; // read(V1) => readBeforeWritten
+/// V2; // read(V2) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// do {} while (E)
+///
+/// The body and the condition always execute, all assignments contribute to
+/// the current branch. The body is tracked in its own branch, so that if
+/// it has an interruption (`break` or `continue`), we can discard the branch
+/// after or before the condition.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// int V2; // add(V2)
+/// do { // beginDoWhileStatement(node)
+/// V1 = 0; // write(V1)
+/// V1; // read(V1) => OK
+/// } while // beginDoWhileStatementCondition()
+/// ((V2 = 0) >= 0) // write(V2)
+/// ; // endDoWhileStatement()
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => OK
+/// ```
+///
+///
+/// --------------------------------------------------
+/// do { ...break... } while (E)
+///
+/// The `break` statement prevents execution of the rest of the body, and
+/// the condition. So, the branch ends after the condition.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// int V2; // add(V2)
+/// int V3; // add(V3)
+/// do { // beginDoWhileStatement(node)
+/// V1 = 0; // write(V1)
+/// V1; // read(V1) => OK
+/// if (C1) break; // handleBreak(do)
+/// V2 = 0; // write(V2)
+/// V2; // read(V2) => OK
+/// } while // beginDoWhileStatementCondition()
+/// ((V3 = 0) >= 0) // write(V3)
+/// ; // endDoWhileStatement()
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => readBeforeWritten
+/// V3; // read(V3) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// do { ...continue... } while (E)
+///
+/// The `continue` statement prevents execution of the rest of the body, but
+/// the condition is always executed. So, the branch ends before the condition,
+/// and the condition contributes to the enclosing branch.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// int V2; // add(V2)
+/// int V3; // add(V3)
+/// do { // beginDoWhileStatement(node)
+/// V1 = 0; // write(V1)
+/// V1; // read(V1) => OK
+/// if (C1) continue; // handleContinue(do)
+/// V2 = 0; // write(V2)
+/// V2; // read(V2) => OK
+/// } while // beginDoWhileStatementCondition()
+/// ((V3 = 0) >= 0) // write(V3)
+/// ; // endDoWhileStatement()
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => readBeforeWritten
+/// V3; // read(V3) => OK
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Try / catch.
+///
+/// The variable must be assigned in the `try` branch and every `catch` branch.
+///
+/// Note, that an improvement is possible, when some first statements in the
+/// `try` branch can be shown to not throw (e.g. `V = 0;`). We could consider
+/// these statements as definitely executed, so producing definite assignments.
+///
+/// ```dart
+/// int V; // add(V)
+/// try { // beginTryStatement()
+/// V = f(); // write(V)
+/// } // endTryStatementBody()
+/// catch (_) { // beginTryStatementCatchClause()
+/// V = 0; // write(V)
+/// } // endTryStatementCatchClause(); endTryStatementCatchClauses()
+/// V; // read(V) => OK
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Try / finally.
+///
+/// The `finally` clause is always executed, so it is tracked in the branch
+/// that contains the `try` statement.
+///
+/// Without `catch` clauses the `try` clause is always executed to the end,
+/// so also can be tracked in the branch that contains the `try` statement.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// int V2; // add(V2)
+/// try { // beginTryStatement()
+/// V1 = 0; // write(V1)
+/// } // endTryStatementBody(); endTryStatementCatchClauses();
+/// finally {
+/// V2 = 0; // write(V2)
+/// }
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => OK
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Try / catch / finally.
+///
+/// The `finally` clause is always executed, so it is tracked in the branch
+/// that contains the `try` statement.
+///
+/// The `try` and `catch` branches are tracked as without `finally`.
+///
+///
+/// --------------------------------------------------
+/// switch (E) { case E1: ... default: }
+///
+/// The variable must be assigned in every `case` branch and must have the
+/// `default` branch. If the `default` branch is missing, then the `switch`
+/// does not definitely cover all possible values of `E`, so the variable is
+/// not definitely assigned.
+///
+/// The expression `E` contributes into the current branch.
+///
+/// ```dart
+/// int V; // add(V)
+/// switch // beginSwitchStatement()
+/// (E) { // endSwitchStatementExpression()
+/// case 1: // beginSwitchStatementMember()
+/// V = 0; // write(V)
+/// break; // handleBreak(switch)
+/// default: // beginSwitchStatementMember()
+/// V = 0; // write(V); handleBreak(switch)
+/// } // endSwitchStatement(hasDefault: true)
+/// V; // read(V) => OK
+/// ```
+///
+/// The presence of a `continue L` statement in switch / case is analyzed in an
+/// approximate way; if a given variable is not written to in all case bodies,
+/// it is considered "not definitely assigned", even though a full basic block
+/// analysis would show that it is definitely assigned.
+///
+/// ```dart
+/// int V; // add(V)
+/// switch // beginSwitchStatement()
+/// (E) { // endSwitchStatementExpression()
+/// L: case 1: // beginSwitchStatementMember()
+/// V = 0; // write(V)
+/// break; // handleBreak(switch)
+/// case 2: // beginSwitchStatementMember()
+/// continue L; // handleContinue(switch)
+/// default: // beginSwitchStatementMember()
+/// V = 0; // write(V); handleBreak(switch)
+/// } // endSwitchStatement(hasDefault: true)
+/// V; // read(V) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// For each.
+///
+/// The iterable might be empty, so the body might be not executed, so writes
+/// in the body are discarded. But writes in the iterable expressions are
+/// always executed.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// int V2; // add(V2)
+/// for (var _ in (V1 = [0, 1, 2])) // beginForEachStatement(node)
+/// { // beginForEachStatementBody()
+/// V2 = 0; // write(V1)
+/// } // endForEachStatement();
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// For statement.
+///
+/// Very similar to `while` statement. The initializer and condition parts
+/// are always executed, so contribute to definite assignments. The body and
+/// updaters might be not executed, so writes in them are discarded. The
+/// updaters are executed after the body, so writes in the body are visible in
+/// the updaters, correspondingly AST portions should be visited, and
+/// [beginForEachStatementBody] should be before [beginForStatementUpdaters],
+/// and followed with [endForStatement].
+///
+/// ```dart
+/// int V1; // 1. add(V1)
+/// int V2; // 2. add(V2)
+/// int V3; // 3. add(V3)
+/// int V4; // 4. add(V4)
+/// for ( // 5. beginForStatement(node)
+/// var _ = (V1 = 0); // 6. write(V1)
+/// (V2 = 0) >= 0; // 7. write(V2)
+/// V3 = 0 // 10. beginForStatementUpdaters(); write(V3)
+/// ) { // 8. beginForStatementBody()
+/// V4 = 0; // 9. write(V4)
+/// } // 11. endForStatement()
+/// V1; // 12. read(V1) => OK
+/// V2; // 13. read(V2) => OK
+/// V3; // 14. read(V3) => readBeforeWritten
+/// V4; // 15. read(V4) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Function expressions - local functions and closures.
+///
+/// A function expression, e.g. a closure passed as an argument of an
+/// invocation, might be invoked synchronously, asynchronously, or never.
+/// So, all local variables that are read in the function expression must be
+/// definitely assigned, but any writes should be discarded on exit from the
+/// function expression.
+///
+/// ```dart
+/// int V1; // add(V1)
+/// int V2; // add(V2)
+/// int V3; // add(V2)
+/// V1 = 0; // write(V1)
+/// void f() { // beginFunctionExpression()
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => readBeforeWritten
+/// V3 = 0; // write(V1)
+/// } // endFunctionExpression();
+/// V2 = 0; // write(V2)
+/// f();
+/// V1; // read(V1) => OK
+/// V2; // read(V2) => OK
+/// V3; // read(V3) => readBeforeWritten
+/// ```
+///
+///
+/// --------------------------------------------------
+/// Definite exit.
+///
+/// If a definite exit is reached, e.g. a `return` or a `throw` statement,
+/// then all the variables are vacuously definitely assigned.
+///
+/// ```dart
+/// int V; // add(V)
+/// if (E) {
+/// { // beginIfStatementThen()
+/// V = 0; // write(V)
+/// } else { // beginIfStatementElse()
+/// return; // handleExit()
+/// } // endIfStatement(hasElse: true)
+/// V; // read(V) => OK
+/// ```
+class DefiniteAssignmentTracker {
+ /// The output list of variables that were read before they were written.
+ final List<LocalVariableElement> readBeforeWritten = [];
+
+ /// The stack of sets of variables that are not definitely assigned.
+ final List<_ElementSet> _stack = [];
+
+ /// The mapping from labeled [Statement]s to the index in the [_stack]
+ /// where the first related element is located. The number of elements
+ /// is statement specific.
+ final Map<Statement, int> _statementToStackIndex = {};
+
+ /// The current set of variables that are not definitely assigned.
+ _ElementSet _current = _ElementSet.empty;
+
+ @visibleForTesting
+ bool get isRootBranch {
+ return _stack.isEmpty;
+ }
+
+ /// Add a new [variable], which might be already [assigned].
+ void add(LocalVariableElement variable, {bool assigned: false}) {
+ if (!assigned) {
+ _current = _current.add(variable);
+ }
+ }
+
+ void beginAssertStatement() {
+ _stack.add(_current);
+ }
+
+ void beginBinaryExpressionLogicalRight() {
+ _stack.add(_current);
+ }
+
+ void beginConditionalExpressionElse() {
+ var afterCondition = _stack.last;
+ _stack.last = _current; // after then
+ _current = afterCondition;
+ }
+
+ void beginConditionalExpressionThen() {
+ _stack.add(_current); // after condition
+ }
+
+ void beginDoWhileStatement(DoStatement statement) {
+ _statementToStackIndex[statement] = _stack.length;
+ _stack.add(_ElementSet.empty); // break set
+ _stack.add(_ElementSet.empty); // continue set
+ }
+
+ void beginDoWhileStatementCondition() {
+ var continueSet = _stack.removeLast();
+ // If there was a `continue`, use it.
+ // If there was not any, use the current.
+ _current = _current.union(continueSet);
+ }
+
+ void beginForEachStatement(ForEachStatement statement) {
+ // Not strongly necessary, because we discard everything anyway.
+ // Just for consistency, so that `break` is handled without `null`.
+ _statementToStackIndex[statement] = _stack.length;
+ }
+
+ void beginForEachStatementBody() {
+ _stack.add(_current);
+ }
+
+ void beginForStatement(ForStatement statement) {
+ // Not strongly necessary, because we discard everything anyway.
+ // Just for consistency, so that `break` is handled without `null`.
+ _statementToStackIndex[statement] = _stack.length;
+ }
+
+ void beginForStatementBody() {
+ _stack.add(_current); // break set
+ _stack.add(_ElementSet.empty); // continue set
+ }
+
+ void beginForStatementUpdaters() {
+ var continueSet = _stack.last;
+ _current = _current.union(continueSet);
+ }
+
+ void beginFunctionExpression() {
+ _stack.add(_current);
+ }
+
+ void beginIfStatementElse() {
+ var enclosing = _stack.last;
+ _stack.last = _current;
+ _current = enclosing;
+ }
+
+ void beginIfStatementThen() {
+ _stack.add(_current);
+ }
+
+ void beginSwitchStatement(SwitchStatement statement) {
+ _statementToStackIndex[statement] = _stack.length;
+ _stack.add(_ElementSet.empty); // break set
+ _stack.add(_ElementSet.empty); // continue set (placeholder)
+ }
+
+ void beginSwitchStatementMember() {
+ _current = _stack.last; // before cases
+ }
+
+ void beginTryStatement() {
+ _stack.add(_current); // before body, for catches
+ }
+
+ void beginTryStatementCatchClause() {
+ _current = _stack[_stack.length - 2]; // before body
+ }
+
+ void beginWhileStatement(WhileStatement statement) {
+ _statementToStackIndex[statement] = _stack.length;
+ }
+
+ void beginWhileStatementBody(bool isTrue) {
+ _stack.add(isTrue ? _ElementSet.empty : _current); // break set
+ _stack.add(_ElementSet.empty); // continue set
+ }
+
+ void endAssertStatement() {
+ _current = _stack.removeLast();
+ }
+
+ void endBinaryExpressionLogicalRight() {
+ _current = _stack.removeLast();
+ }
+
+ void endConditionalExpression() {
+ var thenSet = _stack.removeLast();
+ var elseSet = _current;
+ _current = thenSet.union(elseSet);
+ }
+
+ void endDoWhileStatement() {
+ var breakSet = _stack.removeLast();
+ // If there was a `break`, use it.
+ // If there was not any, use the current.
+ _current = _current.union(breakSet);
+ }
+
+ void endForEachStatement() {
+ _current = _stack.removeLast();
+ }
+
+ void endForStatement() {
+ _stack.removeLast(); // continue set
+ _current = _stack.removeLast(); // break set, before body
+ }
+
+ void endFunctionExpression() {
+ _current = _stack.removeLast();
+ }
+
+ void endIfStatement(bool hasElse) {
+ if (hasElse) {
+ var thenSet = _stack.removeLast();
+ var elseSet = _current;
+ _current = thenSet.union(elseSet);
+ } else {
+ var afterCondition = _stack.removeLast();
+ _current = afterCondition;
+ }
+ }
+
+ void endSwitchStatement(bool hasDefault) {
+ var beforeCasesSet = _stack.removeLast();
+ _stack.removeLast(); // continue set
+ var breakSet = _stack.removeLast();
+ if (hasDefault) {
+ _current = breakSet;
+ } else {
+ _current = beforeCasesSet;
+ }
+ }
+
+ void endSwitchStatementExpression() {
+ _stack.add(_current); // before cases
+ }
+
+ void endTryStatementBody() {
+ _stack.add(_current); // union of body and catches
+ }
+
+ void endTryStatementCatchClause() {
+ _stack.last = _stack.last.union(_current); // union of body and catches
+ }
+
+ void endTryStatementCatchClauses() {
+ _current = _stack.removeLast(); // union of body and catches
+ _stack.removeLast(); // before body
+ }
+
+ void endWhileStatement() {
+ _stack.removeLast(); // continue set
+ _current = _stack.removeLast(); // break set
+ }
+
+ /// Handle a `break` statement with the given [target].
+ void handleBreak(AstNode target) {
+ var breakSetIndex = _statementToStackIndex[target];
+ if (breakSetIndex != null) {
+ _stack[breakSetIndex] = _stack[breakSetIndex].union(_current);
+ }
+ _current = _ElementSet.empty;
+ }
+
+ /// Handle a `continue` statement with the given [target].
+ void handleContinue(AstNode target) {
+ var breakSetIndex = _statementToStackIndex[target];
+ if (breakSetIndex != null) {
+ var continueSetIndex = breakSetIndex + 1;
+ _stack[continueSetIndex] = _stack[continueSetIndex].union(_current);
+ }
+ _current = _ElementSet.empty;
+ }
+
+ /// Register the fact that the current branch definitely exists, e.g. returns
+ /// from the body, throws an exception, etc. So, all variables in the branch
+ /// are vacuously definitely assigned.
+ void handleExit() {
+ _current = _ElementSet.empty;
+ }
+
+ /// Register read of the given [variable] in the current branch.
+ void read(LocalVariableElement variable) {
+ if (_current.contains(variable)) {
+ // Add to the list of violating variables, if not there yet.
+ for (var i = 0; i < readBeforeWritten.length; ++i) {
+ var violatingVariable = readBeforeWritten[i];
+ if (identical(violatingVariable, variable)) {
+ return;
+ }
+ }
+ readBeforeWritten.add(variable);
+ }
+ }
+
+ /// Register write of the given [variable] in the current branch.
+ void write(LocalVariableElement variable) {
+ _current = _current.remove(variable);
+ }
+}
+
+/// List based immutable set of elements.
+class _ElementSet {
+ static final empty = _ElementSet._(
+ List<LocalVariableElement>(0),
+ );
+
+ final List<LocalVariableElement> elements;
+
+ _ElementSet._(this.elements);
+
+ _ElementSet add(LocalVariableElement addedElement) {
+ if (contains(addedElement)) {
+ return this;
+ }
+
+ var length = elements.length;
+ var newElements = List<LocalVariableElement>(length + 1);
+ for (var i = 0; i < length; ++i) {
+ newElements[i] = elements[i];
+ }
+ newElements[length] = addedElement;
+ return _ElementSet._(newElements);
+ }
+
+ bool contains(LocalVariableElement element) {
+ var length = elements.length;
+ for (var i = 0; i < length; ++i) {
+ if (identical(elements[i], element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ _ElementSet remove(LocalVariableElement removedElement) {
+ if (!contains(removedElement)) {
+ return this;
+ }
+
+ var length = elements.length;
+ if (length == 1) {
+ return empty;
+ }
+
+ var newElements = List<LocalVariableElement>(length - 1);
+ var newIndex = 0;
+ for (var i = 0; i < length; ++i) {
+ var element = elements[i];
+ if (!identical(element, removedElement)) {
+ newElements[newIndex++] = element;
+ }
+ }
+
+ return _ElementSet._(newElements);
+ }
+
+ _ElementSet union(_ElementSet other) {
+ if (other == null || other.elements.isEmpty) {
+ return this;
+ }
+
+ var result = this;
+ var otherElements = other.elements;
+ for (var i = 0; i < otherElements.length; ++i) {
+ var otherElement = otherElements[i];
+ result = result.add(otherElement);
+ }
+ return result;
+ }
+}
diff --git a/pkg/analyzer/test/src/dart/resolution/assignment_test.dart b/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
index 6bf04e5..a2abdd1 100644
--- a/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
@@ -65,7 +65,7 @@
assertType(assignment, 'num'); // num + int = num
SimpleIdentifier left = assignment.leftHandSide;
- assertElement(left, findElement.localVar('x'));
+ assertElement(left, findElement.localVar('v'));
assertType(left, 'num');
Expression right = assignment.rightHandSide;
diff --git a/pkg/analyzer/test/src/dart/resolution/definite_assignment_test.dart b/pkg/analyzer/test/src/dart/resolution/definite_assignment_test.dart
new file mode 100644
index 0000000..c9d840d
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/resolution/definite_assignment_test.dart
@@ -0,0 +1,1533 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/resolver/definite_assignment.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'driver_resolution.dart';
+
+main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(DefiniteAssignmentTrackerTest);
+ });
+}
+
+@reflectiveTest
+class DefiniteAssignmentTrackerTest extends DriverResolutionTest {
+ DefiniteAssignmentTracker tracker;
+
+ /// Assert that only local variables with the given names are marked as read
+ /// before being written. All the other local variables are implicitly
+ /// considered definitely assigned.
+ void assertReadBeforeWritten(
+ [String name1, String name2, String name3, String name4]) {
+ var expected = [name1, name2, name3, name4]
+ .where((i) => i != null)
+ .map((name) => findElement.localVar(name))
+ .toList();
+ expect(tracker.readBeforeWritten, unorderedEquals(expected));
+ }
+
+ test_assert() async {
+ await trackCode(r'''
+main() {
+ int v;
+ assert((v = 0) >= 0, v);
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_assignment_leftExpression() async {
+ await trackCode(r'''
+main() {
+ List<int> v;
+ v[0] = (v = [1, 2])[1];
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_assignment_leftLocal_compound() async {
+ await trackCode(r'''
+main() {
+ int v;
+ v += 1;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_assignment_leftLocal_compound_assignInRight() async {
+ await trackCode(r'''
+main() {
+ int v;
+ v += (v = v);
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_assignment_leftLocal_pure_eq() async {
+ await trackCode(r'''
+main() {
+ int v;
+ v = 0;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_assignment_leftLocal_pure_eq_self() async {
+ await trackCode(r'''
+main() {
+ int v;
+ v = v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_assignment_leftLocal_pure_questionEq() async {
+ await trackCode(r'''
+main() {
+ int v;
+ v ??= 0;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_assignment_leftLocal_pure_questionEq_self() async {
+ await trackCode(r'''
+main() {
+ int v;
+ v ??= v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_binaryExpression_ifNull_left() async {
+ await trackCode(r'''
+main() {
+ int v;
+ (v = 0) ?? 0;
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_binaryExpression_ifNull_right() async {
+ await trackCode(r'''
+main(int a) {
+ int v;
+ a ?? (v = 0);
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_binaryExpression_logicalAnd_left() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ ((v = 0) >= 0) && c;
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_binaryExpression_logicalAnd_right() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ c && ((v = 0) >= 0);
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_binaryExpression_logicalOr_left() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ ((v = 0) >= 0) || c;
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_binaryExpression_logicalOr_right() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ c || ((v = 0) >= 0);
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_binaryExpression_plus_left() async {
+ await trackCode(r'''
+main() {
+ int v;
+ (v = 0) + 1;
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_binaryExpression_plus_right() async {
+ await trackCode(r'''
+main() {
+ int v;
+ 1 + (v = 0);
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_conditionalExpression_both() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ c ? (v = 0) : (v = 0);
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_conditionalExpression_condition() async {
+ await trackCode(r'''
+main() {
+ int v;
+ (v = 0) >= 0 ? 1 : 2;
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_conditionalExpression_else() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ c ? (v = 0) : 2;
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_conditionalExpression_then() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ c ? (v = 0) : 2;
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_doWhile_break_afterAssignment() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ do {
+ v = 0;
+ v;
+ if (c) break;
+ } while (c);
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_doWhile_break_beforeAssignment() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ do {
+ if (c) break;
+ v = 0;
+ } while (c);
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_doWhile_breakOuterFromInner() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2, v3;
+ L1: do {
+ do {
+ v1 = 0;
+ if (c) break L1;
+ v2 = 0;
+ v3 = 0;
+ } while (c);
+ v2;
+ } while (c);
+ v1;
+ v3;
+}
+''');
+ assertReadBeforeWritten('v3');
+ }
+
+ test_doWhile_condition() async {
+ await trackCode(r'''
+main() {
+ int v1, v2;
+ do {
+ v1; // assigned in the condition, but not yet
+ } while ((v1 = 0) + (v2 = 0) >= 0);
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1');
+ }
+
+ test_doWhile_condition_break() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ do {
+ if (c) break;
+ } while ((v = 0) >= 0);
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_doWhile_condition_break_continue() async {
+ await trackCode(r'''
+main(bool c1, bool c2) {
+ int v1, v2, v3, v4, v5, v6;
+ do {
+ v1 = 0; // visible outside, visible to the condition
+ if (c1) break;
+ v2 = 0; // not visible outside, visible to the condition
+ v3 = 0; // not visible outside, visible to the condition
+ if (c2) continue;
+ v4 = 0; // not visible
+ v5 = 0; // not visible
+ } while ((v6 = v1 + v2 + v4) == 0); // has break => v6 is not visible outside
+ v1;
+ v3;
+ v5;
+ v6;
+}
+''');
+ assertReadBeforeWritten('v3', 'v4', 'v5', 'v6');
+ }
+
+ test_doWhile_condition_continue() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2, v3, v4;
+ do {
+ v1 = 0; // visible outside, visible to the condition
+ if (c) continue;
+ v2 = 0; // not visible
+ v3 = 0; // not visible
+ } while ((v4 = v1 + v2) == 0); // no break => v4 visible outside
+ v1;
+ v3;
+ v4;
+}
+''');
+ assertReadBeforeWritten('v2', 'v3');
+ }
+
+ test_doWhile_continue_beforeAssignment() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ do {
+ if (c) continue;
+ v = 0;
+ } while (c);
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_for_body() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ for (; c;) {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_for_break() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ for (; c;) {
+ v1 = 0;
+ if (c) break;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2');
+ }
+
+ test_for_break_updaters() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ for (; c; v1 + v2) {
+ v1 = 0;
+ if (c) break;
+ v2 = 0;
+ }
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_for_condition() async {
+ await trackCode(r'''
+main() {
+ int v;
+ for (; (v = 0) >= 0;) {
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_for_continue() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ for (; c;) {
+ v1 = 0;
+ if (c) continue;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2');
+ }
+
+ test_for_continue_updaters() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ for (; c; v1 + v2) {
+ v1 = 0;
+ if (c) continue;
+ v2 = 0;
+ }
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_for_initializer_expression() async {
+ await trackCode(r'''
+main() {
+ int v;
+ for (v = 0;;) {
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_for_initializer_variable() async {
+ await trackCode(r'''
+main() {
+ int v;
+ for (var t = (v = 0);;) {
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_for_updaters() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2, v3, v4;
+ for (; c; v1 = 0, v2 = 0, v3 = 0, v4) {
+ v1;
+ }
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2', 'v4');
+ }
+
+ test_for_updaters_afterBody() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ for (; c; v) {
+ v = 0;
+ }
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_forEach() async {
+ await trackCode(r'''
+main() {
+ int v1, v2;
+ for (var _ in (v1 = [0, 1, 2])) {
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_forEach_break() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ for (var _ in [0, 1, 2]) {
+ v1 = 0;
+ if (c) break;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2');
+ }
+
+ test_forEach_continue() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ for (var _ in [0, 1, 2]) {
+ v1 = 0;
+ if (c) continue;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2');
+ }
+
+ test_functionExpression_closure_read() async {
+ await trackCode(r'''
+main() {
+ int v1, v2;
+
+ v1 = 0;
+
+ [0, 1, 2].forEach((t) {
+ v1;
+ v2;
+ });
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_functionExpression_closure_write() async {
+ await trackCode(r'''
+main() {
+ int v;
+
+ [0, 1, 2].forEach((t) {
+ v = t;
+ });
+
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_functionExpression_localFunction_local() async {
+ await trackCode(r'''
+main() {
+ int v;
+
+ v = 0;
+
+ void f() {
+ int v; // 1
+ v;
+ }
+}
+''');
+ var localV = findNode.simple('v; // 1').staticElement;
+ expect(tracker.readBeforeWritten, unorderedEquals([localV]));
+ }
+
+ test_functionExpression_localFunction_local2() async {
+ await trackCode(r'''
+main() {
+ int v1;
+
+ v1 = 0;
+
+ void f() {
+ int v2, v3;
+ v2 = 0;
+ v1;
+ v2;
+ v3;
+ }
+}
+''');
+ assertReadBeforeWritten('v3');
+ }
+
+ test_functionExpression_localFunction_read() async {
+ await trackCode(r'''
+main() {
+ int v1, v2, v3;
+
+ v1 = 0;
+
+ void f() {
+ v1;
+ v2;
+ }
+
+ v2 = 0;
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_functionExpression_localFunction_write() async {
+ await trackCode(r'''
+main() {
+ int v;
+
+ void f() {
+ v = 0;
+ }
+
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_if_condition() async {
+ await trackCode(r'''
+main() {
+ int v;
+ if ((v = 0) >= 0) {
+ v;
+ } else {
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_if_then() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ if (c) {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_if_thenElse_all() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ if (c) {
+ v = 0;
+ v;
+ } else {
+ v = 0;
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_if_thenElse_else() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ if (c) {
+ // not assigned
+ } else {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_if_thenElse_then() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ if (c) {
+ v = 0;
+ } else {
+ // not assigned
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_if_thenElse_then_exit_alwaysThrows() async {
+ addMetaPackage();
+ await trackCode(r'''
+import 'package:meta/meta.dart';
+
+main(bool c) {
+ int v;
+ if (c) {
+ v = 0;
+ } else {
+ foo();
+ }
+ v;
+}
+
+@alwaysThrows
+void foo() {}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_if_thenElse_then_exit_return() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ if (c) {
+ v = 0;
+ } else {
+ return;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_if_thenElse_then_exit_throw() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ if (c) {
+ v = 0;
+ } else {
+ throw 42;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_switch_case1_default() async {
+ await trackCode(r'''
+main(int e) {
+ int v;
+ switch (e) {
+ case 1:
+ v = 0;
+ break;
+ case 2:
+ // not assigned
+ break;
+ default:
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_switch_case2_default() async {
+ await trackCode(r'''
+main(int e) {
+ int v1, v2;
+ switch (e) {
+ case 1:
+ v1 = 0;
+ v2 = 0;
+ v1;
+ break;
+ default:
+ v1 = 0;
+ v1;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_switch_case_default_break() async {
+ await trackCode(r'''
+main(int e, bool c) {
+ int v1, v2;
+ switch (e) {
+ case 1:
+ v1 = 0;
+ if (c) break;
+ v2 = 0;
+ break;
+ default:
+ v1 = 0;
+ if (c) break;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_switch_case_default_continue() async {
+ await trackCode(r'''
+main(int e) {
+ int v;
+ switch (e) {
+ L: case 1:
+ v = 0;
+ break;
+ case 2:
+ continue L;
+ default:
+ v = 0;
+ }
+ v;
+}
+''');
+ // We don't analyze to which `case` we go from `continue L`,
+ // but we don't have to. If all cases assign, then the variable is
+ // not in the `breakSet`. And if there is a case when it is not assigned,
+ // we the variable will be left in the `breakSet`.
+ assertReadBeforeWritten();
+ }
+
+ test_switch_case_noDefault() async {
+ await trackCode(r'''
+main(int e) {
+ int v;
+ switch (e) {
+ case 1:
+ v = 0;
+ break;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_switch_condition() async {
+ await trackCode(r'''
+main() {
+ int v;
+ switch (v = 0) {}
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_tryCatch_all() async {
+ await trackCode(r'''
+main() {
+ int v;
+ try {
+ f();
+ v = 0;
+ } catch (_) {
+ v = 0;
+ }
+ v;
+}
+
+void f() {}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_tryCatch_catch() async {
+ await trackCode(r'''
+main() {
+ int v;
+ try {
+ // not assigned
+ } catch (_) {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_tryCatch_try() async {
+ await trackCode(r'''
+main() {
+ int v;
+ try {
+ v = 0;
+ } catch (_) {
+ // not assigned
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_tryCatchFinally_catch() async {
+ await trackCode(r'''
+main() {
+ int v;
+ try {
+ // not assigned
+ } catch (_) {
+ v = 0;
+ } finally {
+ // not assigned
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_tryCatchFinally_finally() async {
+ await trackCode(r'''
+main() {
+ int v;
+ try {
+ // not assigned
+ } catch (_) {
+ // not assigned
+ } finally {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_tryCatchFinally_try() async {
+ await trackCode(r'''
+main() {
+ int v;
+ try {
+ v = 0;
+ } catch (_) {
+ // not assigned
+ } finally {
+ // not assigned
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_tryFinally_finally() async {
+ await trackCode(r'''
+main() {
+ int v;
+ try {
+ // not assigned
+ } finally {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_tryFinally_try() async {
+ await trackCode(r'''
+main() {
+ int v;
+ try {
+ v = 0;
+ } finally {
+ // not assigned
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_while_condition() async {
+ await trackCode(r'''
+main() {
+ int v;
+ while ((v = 0) >= 0) {
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_while_condition_notTrue() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ while (c) {
+ v1 = 0;
+ v2 = 0;
+ v1;
+ }
+ v2;
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_while_true_break_afterAssignment() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ while (true) {
+ v1 = 0;
+ v1;
+ if (c) break;
+ v1;
+ v2 = 0;
+ v2;
+ }
+ v1;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_while_true_break_beforeAssignment() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ while (true) {
+ if (c) break;
+ v1 = 0;
+ v2 = 0;
+ v2;
+ }
+ v1;
+}
+''');
+ assertReadBeforeWritten('v1');
+ }
+
+ test_while_true_break_if() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ while (true) {
+ if (c) {
+ v = 0;
+ break;
+ } else {
+ v = 0;
+ break;
+ }
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_while_true_break_if2() async {
+ await trackCode(r'''
+main(bool c) {
+ var v;
+ while (true) {
+ if (c) {
+ break;
+ } else {
+ v = 0;
+ }
+ v;
+ }
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_while_true_break_if3() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2;
+ while (true) {
+ if (c) {
+ v1 = 0;
+ v2 = 0;
+ if (c) break;
+ } else {
+ if (c) break;
+ v1 = 0;
+ v2 = 0;
+ }
+ v1;
+ }
+ v2;
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_while_true_breakOuterFromInner() async {
+ await trackCode(r'''
+main(bool c) {
+ int v1, v2, v3;
+ L1: while (true) {
+ L2: while (true) {
+ v1 = 0;
+ if (c) break L1;
+ v2 = 0;
+ v3 = 0;
+ if (c) break L2;
+ }
+ v2;
+ }
+ v1;
+ v3;
+}
+''');
+ assertReadBeforeWritten('v3');
+ }
+
+ test_while_true_continue() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ while (true) {
+ if (c) continue;
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_while_true_noBreak() async {
+ await trackCode(r'''
+main(bool c) {
+ int v;
+ while (true) {
+ // No assignment, but not break.
+ // So, we don't exit the loop.
+ // So, all variables are assigned.
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ /// Resolve the given [code] and track assignments in the unit.
+ Future<void> trackCode(String code) async {
+ addTestFile(code);
+ await resolveTestFile();
+
+ tracker = DefiniteAssignmentTracker();
+
+ var visitor = _AstVisitor(tracker);
+ result.unit.accept(visitor);
+ }
+}
+
+/// [AstVisitor] that drives the [tracker] in the way we expect the resolver
+/// will do in production.
+class _AstVisitor extends RecursiveAstVisitor<void> {
+ final DefiniteAssignmentTracker tracker;
+
+ _AstVisitor(this.tracker);
+
+ @override
+ void visitAssertStatement(AssertStatement node) {
+ tracker.beginAssertStatement();
+ super.visitAssertStatement(node);
+ tracker.endAssertStatement();
+ }
+
+ @override
+ void visitAssignmentExpression(AssignmentExpression node) {
+ var left = node.leftHandSide;
+ var right = node.rightHandSide;
+
+ LocalVariableElement localElement;
+ if (left is SimpleIdentifier) {
+ var element = left.staticElement;
+ if (element is LocalVariableElement) {
+ localElement = element;
+ }
+ }
+
+ if (localElement != null) {
+ var isPure = node.operator.type == TokenType.EQ;
+ if (!isPure) {
+ tracker.read(localElement);
+ }
+ right.accept(this);
+ tracker.write(localElement);
+ } else {
+ left.accept(this);
+ right.accept(this);
+ }
+ }
+
+ @override
+ void visitBinaryExpression(BinaryExpression node) {
+ var left = node.leftOperand;
+ var right = node.rightOperand;
+
+ var operator = node.operator.type;
+ var isLogical = operator == TokenType.AMPERSAND_AMPERSAND ||
+ operator == TokenType.BAR_BAR ||
+ operator == TokenType.QUESTION_QUESTION;
+
+ left.accept(this);
+
+ if (isLogical) {
+ tracker.beginBinaryExpressionLogicalRight();
+ }
+
+ right.accept(this);
+
+ if (isLogical) {
+ tracker.endBinaryExpressionLogicalRight();
+ }
+ }
+
+ @override
+ void visitBreakStatement(BreakStatement node) {
+ var target = _getLabelTarget(node, node.label?.staticElement);
+ tracker.handleBreak(target);
+ super.visitBreakStatement(node);
+ }
+
+ @override
+ void visitConditionalExpression(ConditionalExpression node) {
+ var condition = node.condition;
+ var thenExpression = node.thenExpression;
+ var elseExpression = node.elseExpression;
+
+ condition.accept(this);
+
+ tracker.beginConditionalExpressionThen();
+ thenExpression.accept(this);
+
+ tracker.beginConditionalExpressionElse();
+ elseExpression.accept(this);
+
+ tracker.endConditionalExpression();
+ }
+
+ @override
+ void visitContinueStatement(ContinueStatement node) {
+ var target = _getLabelTarget(node, node.label?.staticElement);
+ tracker.handleContinue(target);
+ super.visitContinueStatement(node);
+ }
+
+ @override
+ void visitDoStatement(DoStatement node) {
+ var body = node.body;
+ var condition = node.condition;
+
+ tracker.beginDoWhileStatement(node);
+ body.accept(this);
+
+ tracker.beginDoWhileStatementCondition();
+ condition.accept(this);
+
+ tracker.endDoWhileStatement();
+ }
+
+ @override
+ void visitForEachStatement(ForEachStatement node) {
+ var iterable = node.iterable;
+ var body = node.body;
+
+ tracker.beginForEachStatement(node);
+ iterable.accept(this);
+
+ tracker.beginForEachStatementBody();
+ body.accept(this);
+
+ tracker.endForEachStatement();
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ var variables = node.variables;
+ var initialization = node.initialization;
+
+ var condition = node.condition;
+ var updaters = node.updaters;
+ var body = node.body;
+
+ tracker.beginForStatement(node);
+
+ variables?.accept(this);
+ initialization?.accept(this);
+ condition?.accept(this);
+
+ tracker.beginForStatementBody();
+ body?.accept(this);
+
+ tracker.beginForStatementUpdaters();
+ updaters?.accept(this);
+
+ tracker.endForStatement();
+ }
+
+ @override
+ void visitFunctionDeclaration(FunctionDeclaration node) {
+ super.visitFunctionDeclaration(node);
+ if (node.parent is CompilationUnit) {
+ expect(tracker.isRootBranch, isTrue);
+ }
+ }
+
+ @override
+ void visitFunctionExpression(FunctionExpression node) {
+ tracker.beginFunctionExpression();
+ super.visitFunctionExpression(node);
+ tracker.endFunctionExpression();
+ }
+
+ @override
+ void visitIfStatement(IfStatement node) {
+ var condition = node.condition;
+ var thenStatement = node.thenStatement;
+ var elseStatement = node.elseStatement;
+
+ condition.accept(this);
+
+ tracker.beginIfStatementThen();
+ thenStatement.accept(this);
+
+ if (elseStatement != null) {
+ tracker.beginIfStatementElse();
+ elseStatement.accept(this);
+ }
+
+ tracker.endIfStatement(elseStatement != null);
+ }
+
+ @override
+ void visitMethodInvocation(MethodInvocation node) {
+ super.visitMethodInvocation(node);
+ var element = node.methodName.staticElement;
+ if (element != null && element.hasAlwaysThrows) {
+ tracker.handleExit();
+ }
+ }
+
+ @override
+ void visitReturnStatement(ReturnStatement node) {
+ super.visitReturnStatement(node);
+ tracker.handleExit();
+ }
+
+ @override
+ void visitSimpleIdentifier(SimpleIdentifier node) {
+ var element = node.staticElement;
+ if (element is LocalVariableElement) {
+ if (node.inGetterContext()) {
+ tracker.read(element);
+ }
+ }
+
+ super.visitSimpleIdentifier(node);
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ tracker.beginSwitchStatement(node);
+
+ node.expression.accept(this);
+ tracker.endSwitchStatementExpression();
+
+ var members = node.members;
+ var membersLength = members.length;
+ var hasDefault = false;
+ for (var i = 0; i < membersLength; i++) {
+ var member = members[i];
+ tracker.beginSwitchStatementMember();
+ member.accept(this);
+ // Implicit `break` at the end of `default`.
+ if (member is SwitchDefault) {
+ hasDefault = true;
+ tracker.handleBreak(node);
+ }
+ }
+
+ tracker.endSwitchStatement(hasDefault);
+ }
+
+ @override
+ void visitThrowExpression(ThrowExpression node) {
+ super.visitThrowExpression(node);
+ tracker.handleExit();
+ }
+
+ @override
+ void visitTryStatement(TryStatement node) {
+ var body = node.body;
+ var catchClauses = node.catchClauses;
+
+ tracker.beginTryStatement();
+
+ body.accept(this);
+ tracker.endTryStatementBody();
+
+ var catchLength = catchClauses.length;
+ for (var i = 0; i < catchLength; ++i) {
+ var catchClause = catchClauses[i];
+ tracker.beginTryStatementCatchClause();
+ catchClause.accept(this);
+ tracker.endTryStatementCatchClause();
+ }
+
+ tracker.endTryStatementCatchClauses();
+
+ node.finallyBlock?.accept(this);
+ }
+
+ @override
+ void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
+ var variables = node.variables.variables;
+ for (var i = 0; i < variables.length; ++i) {
+ var variable = variables[i];
+ tracker.add(variable.declaredElement,
+ assigned: variable.initializer != null);
+ }
+
+ super.visitVariableDeclarationStatement(node);
+ }
+
+ @override
+ void visitWhileStatement(WhileStatement node) {
+ var condition = node.condition;
+ var body = node.body;
+
+ tracker.beginWhileStatement(node);
+ condition.accept(this);
+
+ var conditionIsLiteralTrue = condition is BooleanLiteral && condition.value;
+ tracker.beginWhileStatementBody(conditionIsLiteralTrue);
+ body.accept(this);
+
+ tracker.endWhileStatement();
+ }
+
+ /// This code has OK performance for tests, but think if there is something
+ /// better when using in production.
+ AstNode _getLabelTarget(AstNode node, LabelElement element) {
+ for (; node != null; node = node.parent) {
+ if (node is DoStatement ||
+ node is ForEachStatement ||
+ node is ForStatement ||
+ node is SwitchStatement ||
+ node is WhileStatement) {
+ if (element == null) {
+ return node;
+ }
+ var parent = node.parent;
+ if (parent is LabeledStatement) {
+ for (var nodeLabel in parent.labels) {
+ if (identical(nodeLabel.label.staticElement, element)) {
+ return node;
+ }
+ }
+ }
+ }
+ if (element != null && node is SwitchStatement) {
+ for (var member in node.members) {
+ for (var nodeLabel in member.labels) {
+ if (identical(nodeLabel.label.staticElement, element)) {
+ return node;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/pkg/analyzer/test/src/dart/resolution/driver_resolution.dart b/pkg/analyzer/test/src/dart/resolution/driver_resolution.dart
index dd1137b..081ef0e 100644
--- a/pkg/analyzer/test/src/dart/resolution/driver_resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/driver_resolution.dart
@@ -32,6 +32,14 @@
/// Override this to change the analysis options for a given set of tests.
AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl();
+ void addMetaPackage() {
+ newFile('/.pub-cache/meta/lib/meta.dart', content: r'''
+library meta;
+
+const alwaysThrows = const Object();
+''');
+ }
+
@override
Future<TestAnalysisResult> resolveFile(String path) async {
var result = await driver.getResult(path);
@@ -52,6 +60,7 @@
'test': [getFolder('/test/lib')],
'aaa': [getFolder('/aaa/lib')],
'bbb': [getFolder('/bbb/lib')],
+ 'meta': [getFolder('/.pub-cache/meta/lib')],
};
driver = new AnalysisDriver(
diff --git a/pkg/analyzer/test/src/dart/resolution/find_element.dart b/pkg/analyzer/test/src/dart/resolution/find_element.dart
index cbf7def..d0ac7bf 100644
--- a/pkg/analyzer/test/src/dart/resolution/find_element.dart
+++ b/pkg/analyzer/test/src/dart/resolution/find_element.dart
@@ -225,7 +225,7 @@
unit.accept(new FunctionAstVisitor(
variableDeclaration: (node) {
var element = node.declaredElement;
- if (element is LocalVariableElement) {
+ if (element is LocalVariableElement && element.name == name) {
if (result != null) {
fail('Not unique: $name');
}
diff --git a/pkg/analyzer/test/src/dart/resolution/resolution.dart b/pkg/analyzer/test/src/dart/resolution/resolution.dart
index 52e8428..7e7c6b3 100644
--- a/pkg/analyzer/test/src/dart/resolution/resolution.dart
+++ b/pkg/analyzer/test/src/dart/resolution/resolution.dart
@@ -284,15 +284,6 @@
invocationImpl.methodNameType, expectedMethodNameType);
}
- void assertPropertyAccess(
- PropertyAccess access,
- Element expectedElement,
- String expectedType,
- ) {
- assertElement(access.propertyName, expectedElement);
- assertType(access, expectedType);
- }
-
void assertNamedParameterRef(String search, String name) {
var ref = findNode.simple(search);
assertElement(ref, findElement.parameter(name));
@@ -303,6 +294,15 @@
assertTestErrors(const <ErrorCode>[]);
}
+ void assertPropertyAccess(
+ PropertyAccess access,
+ Element expectedElement,
+ String expectedType,
+ ) {
+ assertElement(access.propertyName, expectedElement);
+ assertType(access, expectedType);
+ }
+
void assertSuperExpression(SuperExpression superExpression) {
// TODO(scheglov) I think `super` does not have type itself.
// It is just a signal to look for implemented method in the supertype.