Improvements for flow analysis.
Support for 'for'.
Support for 'for-each'.
Support for 'switch'.
Support for local functions and closures.
Support for 'try/catch/finally'.
R=paulberry@google.com
Change-Id: Idd0e528b706c1094e2bfdf32f84a9bcd7c80968a
Reviewed-on: https://dart-review.googlesource.com/c/89921
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
index 5867f95..e33f899 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
@@ -128,6 +128,69 @@
_conditionFalse = _current;
}
+ void forEachStatement_bodyBegin(Set<VariableElement> loopAssigned) {
+ _stack.add(_current);
+ _current = _current.removePromotedAll(loopAssigned);
+ }
+
+ void forEachStatement_end() {
+ var afterIterable = _stack.removeLast();
+ _current = _current.combine(typeSystem, afterIterable);
+ }
+
+ void forStatement_bodyBegin(ForStatement node, Expression condition) {
+ _conditionalEnd(condition);
+ // Tail of the stack: falseCondition, trueCondition
+
+ var trueCondition = _stack.removeLast();
+
+ _statementToStackIndex[node] = _stack.length;
+ _stack.add(_State.identity); // break
+ _stack.add(_State.identity); // continue
+
+ _current = trueCondition;
+ }
+
+ void forStatement_conditionBegin(Set<VariableElement> loopAssigned) {
+ _current = _current.removePromotedAll(loopAssigned);
+ }
+
+ void forStatement_end() {
+ // Tail of the stack: falseCondition, break
+ var breakState = _stack.removeLast();
+ var falseCondition = _stack.removeLast();
+
+ _current = falseCondition.combine(typeSystem, breakState);
+ }
+
+ void forStatement_updaterBegin() {
+ // Tail of the stack: falseCondition, break, continue
+ var afterBody = _current;
+ var continueState = _stack.removeLast();
+
+ _current = afterBody.combine(typeSystem, continueState);
+ }
+
+ void functionExpression_begin() {
+ _stack.add(_current);
+
+ Set<VariableElement> notPromoted = null;
+ for (var variable in _current.promoted.keys) {
+ if (functionBody.isPotentiallyMutatedInScope(variable)) {
+ notPromoted ??= Set<VariableElement>.identity();
+ notPromoted.add(variable);
+ }
+ }
+
+ if (notPromoted != null) {
+ _current = _current.removePromotedAll(notPromoted);
+ }
+ }
+
+ void functionExpression_end() {
+ _current = _stack.removeLast();
+ }
+
void handleBreak(AstNode target) {
var breakIndex = _statementToStackIndex[target];
if (breakIndex != null) {
@@ -284,19 +347,77 @@
}
}
+ /// The [notPromoted] set contains all variables that are potentially
+ /// assigned in other cases that might target this with `continue`, so
+ /// these variables might have different types and are "un-promoted" from
+ /// the "afterExpression" state.
+ void switchStatement_beginCase(Set<VariableElement> notPromoted) {
+ _current = _stack.last.removePromotedAll(notPromoted);
+ }
+
+ void switchStatement_end(SwitchStatement node, bool hasDefault) {
+ // Tail of the stack: break, continue, afterExpression
+ var afterExpression = _current = _stack.removeLast();
+ _stack.removeLast(); // continue
+ var breakState = _stack.removeLast();
+
+ if (hasDefault) {
+ _current = breakState;
+ } else {
+ _current = breakState.combine(typeSystem, afterExpression);
+ }
+ }
+
+ void switchStatement_expressionEnd(SwitchStatement node) {
+ _statementToStackIndex[node] = _stack.length;
+ _stack.add(_State.identity); // break
+ _stack.add(_State.identity); // continue
+ _stack.add(_current); // afterExpression
+ }
+
void trueLiteral(BooleanLiteral expression) {
_condition = expression;
_conditionTrue = _current;
_conditionFalse = _State.identity;
}
+ void tryStatement_bodyBegin() {
+ _stack.add(_current);
+ // Tail of the stack: beforeBody
+ }
+
+ void tryStatement_bodyEnd(Set<VariableElement> notPromoted) {
+ var beforeBody = _stack.removeLast();
+ var beforeCatch = beforeBody.removePromotedAll(notPromoted);
+ _stack.add(beforeCatch);
+ _stack.add(_current); // afterBodyAndCatches
+ // Tail of the stack: beforeCatch, afterBodyAndCatches
+ }
+
+ void tryStatement_catchBegin() {
+ var beforeCatch = _stack[_stack.length - 2];
+ _current = beforeCatch;
+ }
+
+ void tryStatement_catchEnd() {
+ var afterBodyAndCatches = _stack.last;
+ _stack.last = afterBodyAndCatches.combine(typeSystem, _current);
+ }
+
+ void tryStatement_finallyBegin() {
+ var afterBodyAndCatches = _stack.removeLast();
+ _stack.removeLast(); // beforeCatch
+
+ _current = afterBodyAndCatches;
+ }
+
void verifyStackEmpty() {
assert(_stack.isEmpty);
}
void whileStatement_bodyBegin(WhileStatement node) {
_conditionalEnd(node.condition);
- // Tail of the stack: falseCondition, trueCondition
+ // Tail of the stack: falseCondition, trueCondition
var trueCondition = _stack.removeLast();
@@ -307,8 +428,7 @@
_current = trueCondition;
}
- void whileStatement_conditionBegin(
- WhileStatement node, Set<VariableElement> loopAssigned) {
+ void whileStatement_conditionBegin(Set<VariableElement> loopAssigned) {
_current = _current.removePromotedAll(loopAssigned);
}
@@ -341,7 +461,7 @@
/// Sets of variables that are potentially assigned in loops.
class LoopAssignedVariables {
- static final _emptySet = Set<VariableElement>();
+ static final emptySet = Set<VariableElement>();
/// Mapping from a loop [AstNode] to the set of variables that are
/// potentially assigned in this loop.
@@ -352,7 +472,7 @@
/// Return the set of variables that are potentially assigned in the [loop].
Set<VariableElement> operator [](AstNode loop) {
- return _map[loop] ?? _emptySet;
+ return _map[loop] ?? emptySet;
}
void beginLoop() {
diff --git a/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
index 7873fce..9710109 100644
--- a/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/standard_ast_factory.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
@@ -281,6 +282,289 @@
assertReadBeforeWritten();
}
+ test_for_body() async {
+ await trackCode(r'''
+void f(bool b) {
+ int v;
+ for (; b;) {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_for_break() async {
+ await trackCode(r'''
+void f(bool b) {
+ int v1, v2;
+ for (; b;) {
+ v1 = 0;
+ if (b) break;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2');
+ }
+
+ test_for_break_updaters() async {
+ await trackCode(r'''
+void f(bool b) {
+ int v1, v2;
+ for (; b; v1 + v2) {
+ v1 = 0;
+ if (b) break;
+ v2 = 0;
+ }
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_for_condition() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ for (; (v = 0) >= 0;) {
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_for_continue() async {
+ await trackCode(r'''
+void f(bool b) {
+ int v1, v2;
+ for (; b;) {
+ v1 = 0;
+ if (b) continue;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2');
+ }
+
+ test_for_continue_updaters() async {
+ await trackCode(r'''
+void f(bool b) {
+ int v1, v2;
+ for (; b; v1 + v2) {
+ v1 = 0;
+ if (b) continue;
+ v2 = 0;
+ }
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_for_initializer_expression() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ for (v = 0;;) {
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_for_initializer_variable() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ for (var t = (v = 0);;) {
+ v;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_for_updaters() async {
+ await trackCode(r'''
+void f(bool b) {
+ int v1, v2, v3, v4;
+ for (; b; v1 = 0, v2 = 0, v3 = 0, v4) {
+ v1;
+ }
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2', 'v4');
+ }
+
+ test_for_updaters_afterBody() async {
+ await trackCode(r'''
+void f(bool b) {
+ int v;
+ for (; b; v) {
+ v = 0;
+ }
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_forEach() async {
+ await trackCode(r'''
+void f() {
+ int v1, v2;
+ for (var _ in (v1 = [0, 1, 2])) {
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_forEach_break() async {
+ await trackCode(r'''
+void f(bool b) {
+ int v1, v2;
+ for (var _ in [0, 1, 2]) {
+ v1 = 0;
+ if (b) break;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2');
+ }
+
+ test_forEach_continue() async {
+ await trackCode(r'''
+void f(bool b) {
+ int v1, v2;
+ for (var _ in [0, 1, 2]) {
+ v1 = 0;
+ if (b) continue;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v1', 'v2');
+ }
+
+ test_functionExpression_closure_read() async {
+ await trackCode(r'''
+void f() {
+ int v1, v2;
+
+ v1 = 0;
+
+ [0, 1, 2].forEach((t) {
+ v1;
+ v2;
+ });
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_functionExpression_closure_write() async {
+ await trackCode(r'''
+void f() {
+ int v;
+
+ [0, 1, 2].forEach((t) {
+ v = t;
+ });
+
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_functionExpression_localFunction_local() async {
+ await trackCode(r'''
+void f() {
+ int v;
+
+ v = 0;
+
+ void f() {
+ int v; // 1
+ v;
+ }
+}
+''');
+ var localV = findNode.simple('v; // 1').staticElement;
+ expect(readBeforeWritten, unorderedEquals([localV]));
+ }
+
+ test_functionExpression_localFunction_local2() async {
+ await trackCode(r'''
+void f() {
+ int v1;
+
+ v1 = 0;
+
+ void f() {
+ int v2, v3;
+ v2 = 0;
+ v1;
+ v2;
+ v3;
+ }
+}
+''');
+ assertReadBeforeWritten('v3');
+ }
+
+ test_functionExpression_localFunction_read() async {
+ await trackCode(r'''
+void f() {
+ int v1, v2, v3;
+
+ v1 = 0;
+
+ void f() {
+ v1;
+ v2;
+ }
+
+ v2 = 0;
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_functionExpression_localFunction_write() async {
+ await trackCode(r'''
+void f() {
+ int v;
+
+ void f() {
+ v = 0;
+ }
+
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
test_if_condition() async {
await trackCode(r'''
main() {
@@ -463,6 +747,249 @@
assertReadBeforeWritten('v');
}
+ test_switch_case1_default() async {
+ await trackCode(r'''
+void f(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'''
+void f(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'''
+void f(bool b, int e) {
+ int v1, v2;
+ switch (e) {
+ case 1:
+ v1 = 0;
+ if (b) break;
+ v2 = 0;
+ break;
+ default:
+ v1 = 0;
+ if (b) break;
+ v2 = 0;
+ }
+ v1;
+ v2;
+}
+''');
+ assertReadBeforeWritten('v2');
+ }
+
+ test_switch_case_default_continue() async {
+ await trackCode(r'''
+void f(int e) {
+ int v;
+ switch (e) {
+ L: case 1:
+ v = 0;
+ break;
+ case 2:
+ continue L;
+ break;
+ 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
+ // removed from the unassigned set in the `breakState`. And if there is a
+ // case when it is not assigned, then the variable will be left unassigned
+ // in the `breakState`.
+ assertReadBeforeWritten();
+ }
+
+ test_switch_case_noDefault() async {
+ await trackCode(r'''
+void f(int e) {
+ int v;
+ switch (e) {
+ case 1:
+ v = 0;
+ break;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_switch_expression() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ switch (v = 0) {}
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_tryCatch_body() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ try {
+ v = 0;
+ } catch (_) {
+ // not assigned
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_tryCatch_body_catch() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ try {
+ g();
+ v = 0;
+ } catch (_) {
+ v = 0;
+ }
+ v;
+}
+
+void g() {}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_tryCatch_catch() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ try {
+ // not assigned
+ } catch (_) {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_tryCatchFinally_body() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ try {
+ v = 0;
+ } catch (_) {
+ // not assigned
+ } finally {
+ // not assigned
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_tryCatchFinally_catch() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ try {
+ // not assigned
+ } catch (_) {
+ v = 0;
+ } finally {
+ // not assigned
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten('v');
+ }
+
+ test_tryCatchFinally_finally() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ try {
+ // not assigned
+ } catch (_) {
+ // not assigned
+ } finally {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_tryFinally_body() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ try {
+ v = 0;
+ } finally {
+ // not assigned
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
+ test_tryFinally_finally() async {
+ await trackCode(r'''
+void f() {
+ int v;
+ try {
+ // not assigned
+ } finally {
+ v = 0;
+ }
+ v;
+}
+''');
+ assertReadBeforeWritten();
+ }
+
test_while_condition() async {
await trackCode(r'''
void f() {
@@ -668,7 +1195,7 @@
var node = findNode.simple(search);
var actualType = promotedTypes[node];
if (actualType == null) {
- fail('$expectedType expected, but actually not promoted');
+ fail('$expectedType expected, but actually not promoted\n$search');
}
assertElementTypeString(actualType, expectedType);
}
@@ -785,6 +1312,137 @@
assertNotPromoted('x; // 2');
}
+ test_for_outerIsType() async {
+ await trackCode(r'''
+void f(bool b, Object x) {
+ if (x is String) {
+ for (; b;) {
+ x; // 1
+ }
+ x; // 2
+ }
+}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertPromoted('x; // 2', 'String');
+ }
+
+ test_for_outerIsType_loopAssigned_body() async {
+ await trackCode(r'''
+void f(bool b, Object x) {
+ if (x is String) {
+ for (; b;) {
+ x; // 1
+ x = 42;
+ }
+ x; // 2
+ }
+}
+''');
+ assertNotPromoted('x; // 1');
+ assertNotPromoted('x; // 2');
+ }
+
+ test_for_outerIsType_loopAssigned_condition() async {
+ await trackCode(r'''
+void f(Object x) {
+ if (x is String) {
+ for (; (x = 42) > 0;) {
+ x; // 1
+ }
+ x; // 2
+ }
+}
+''');
+ assertNotPromoted('x; // 1');
+ assertNotPromoted('x; // 2');
+ }
+
+ test_for_outerIsType_loopAssigned_updaters() async {
+ await trackCode(r'''
+void f(bool b, Object x) {
+ if (x is String) {
+ for (; b; x = 42) {
+ x; // 1
+ }
+ x; // 2
+ }
+}
+''');
+ assertNotPromoted('x; // 1');
+ assertNotPromoted('x; // 2');
+ }
+
+ test_forEach_outerIsType_loopAssigned() async {
+ await trackCode(r'''
+void f(Object x) {
+ if (x is String) {
+ for (var _ in (v1 = [0, 1, 2])) {
+ x; // 1
+ x = 42;
+ }
+ x; // 2
+ }
+}
+''');
+ assertNotPromoted('x; // 1');
+ assertNotPromoted('x; // 2');
+ }
+
+ test_functionExpression_isType() async {
+ await trackCode(r'''
+void f() {
+ void g(Object x) {
+ if (x is String) {
+ x; // 1
+ }
+ x = 42;
+ }
+}
+''');
+ assertPromoted('x; // 1', 'String');
+ }
+
+ test_functionExpression_isType_mutatedInClosure2() async {
+ await trackCode(r'''
+void f() {
+ void g(Object x) {
+ if (x is String) {
+ x; // 1
+ }
+
+ void h() {
+ x = 42;
+ }
+ }
+}
+''');
+ assertNotPromoted('x; // 1');
+ }
+
+ test_functionExpression_outerIsType_assignedOutside() async {
+ await trackCode(r'''
+void f(Object x) {
+ void Function() g;
+
+ if (x is String) {
+ x; // 1
+
+ g = () {
+ x; // 2
+ }
+ }
+
+ x = 42;
+ x; // 3
+ g();
+}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertNotPromoted('x; // 2');
+ assertNotPromoted('x; // 3');
+ }
+
test_if_combine_empty() async {
await trackCode(r'''
main(bool b, Object v) {
@@ -949,6 +1607,193 @@
assertPromoted('x; // 1', 'String');
}
+ test_switch_outerIsType_assignedInCase() async {
+ await trackCode(r'''
+void f(int e, Object x) {
+ if (x is String) {
+ switch (e) {
+ L: case 1:
+ x; // 1
+ break;
+ case 2: // no label
+ x; // 2
+ break;
+ case 3:
+ x = 42;
+ continue L;
+ }
+ x; // 3
+ }
+}
+''');
+ assertNotPromoted('x; // 1');
+ assertPromoted('x; // 2', 'String');
+ assertNotPromoted('x; // 3');
+ }
+
+ test_try_assigned_body() async {
+ await trackCode(r'''
+void f(Object x) {
+ if (x is! String) return;
+ x; // 1
+ try {
+ x = 42;
+ g(); // might throw
+ if (x is! String) return;
+ x; // 2
+ } catch (_) {}
+ x; // 3
+}
+
+void g() {}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertPromoted('x; // 2', 'String');
+ assertNotPromoted('x; // 3');
+ }
+
+ test_try_isNotType_exit_body() async {
+ await trackCode(r'''
+void f(Object x) {
+ try {
+ if (x is! String) return;
+ x; // 1
+ } catch (_) {}
+ x; // 2
+}
+
+void g() {}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertNotPromoted('x; // 2');
+ }
+
+ test_try_isNotType_exit_body_catch() async {
+ await trackCode(r'''
+void f(Object x) {
+ try {
+ if (x is! String) return;
+ x; // 1
+ } catch (_) {
+ if (x is! String) return;
+ x; // 2
+ }
+ x; // 3
+}
+
+void g() {}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertPromoted('x; // 2', 'String');
+ assertPromoted('x; // 3', 'String');
+ }
+
+ test_try_isNotType_exit_catch() async {
+ await trackCode(r'''
+void f(Object x) {
+ try {
+ } catch (_) {
+ if (x is! String) return;
+ x; // 1
+ }
+ x; // 2
+}
+
+void g() {}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertNotPromoted('x; // 2');
+ }
+
+ test_try_outerIsType() async {
+ await trackCode(r'''
+void f(Object x) {
+ if (x is String) {
+ try {
+ x; // 1
+ } catch (_) {
+ x; // 2
+ } finally {
+ x; // 3
+ }
+ x; // 4
+ }
+}
+
+void g() {}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertPromoted('x; // 2', 'String');
+ assertPromoted('x; // 3', 'String');
+ assertPromoted('x; // 4', 'String');
+ }
+
+ test_try_outerIsType_assigned_body() async {
+ await trackCode(r'''
+void f(Object x) {
+ if (x is String) {
+ try {
+ x; // 1
+ x = 42;
+ g();
+ } catch (_) {
+ x; // 2
+ } finally {
+ x; // 3
+ }
+ x; // 4
+ }
+}
+
+void g() {}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertNotPromoted('x; // 2');
+ assertNotPromoted('x; // 3');
+ assertNotPromoted('x; // 4');
+ }
+
+ test_try_outerIsType_assigned_catch() async {
+ await trackCode(r'''
+void f(Object x) {
+ if (x is String) {
+ try {
+ x; // 1
+ } catch (_) {
+ x; // 2
+ x = 42;
+ } finally {
+ x; // 3
+ }
+ x; // 4
+ }
+}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertPromoted('x; // 2', 'String');
+ assertNotPromoted('x; // 3');
+ assertNotPromoted('x; // 4');
+ }
+
+ test_try_outerIsType_assigned_finally() async {
+ await trackCode(r'''
+void f(Object x) {
+ if (x is String) {
+ try {
+ x; // 1
+ } finally {
+ x; // 2
+ x = 42;
+ }
+ x; // 3
+ }
+}
+''');
+ assertPromoted('x; // 1', 'String');
+ assertPromoted('x; // 2', 'String');
+ assertNotPromoted('x; // 3');
+ }
+
test_while_condition_false() async {
await trackCode(r'''
void f(Object x) {
@@ -1046,6 +1891,8 @@
/// [AstVisitor] that drives the [flow] in the way we expect the resolver
/// will do in production.
class _AstVisitor extends RecursiveAstVisitor<void> {
+ static final trueLiteral = astFactory.booleanLiteral(null, true);
+
final TypeSystem typeSystem;
final LoopAssignedVariables loopAssignedVariables;
final Map<AstNode, DartType> promotedTypes;
@@ -1197,6 +2044,49 @@
}
@override
+ void visitForEachStatement(ForEachStatement node) {
+ var iterable = node.iterable;
+ var body = node.body;
+
+ iterable.accept(this);
+ flow.forEachStatement_bodyBegin(loopAssignedVariables[node]);
+
+ body.accept(this);
+
+ flow.forEachStatement_end();
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ var condition = node.condition;
+
+ node.initialization?.accept(this);
+ node.variables?.accept(this);
+
+ flow.forStatement_conditionBegin(loopAssignedVariables[node]);
+ if (condition != null) {
+ condition.accept(this);
+ } else {
+ flow.trueLiteral(trueLiteral);
+ }
+
+ flow.forStatement_bodyBegin(node, condition ?? trueLiteral);
+ node.body.accept(this);
+
+ flow.forStatement_updaterBegin();
+ node.updaters?.accept(this);
+
+ flow.forStatement_end();
+ }
+
+ @override
+ void visitFunctionExpression(FunctionExpression node) {
+ flow?.functionExpression_begin();
+ super.visitFunctionExpression(node);
+ flow?.functionExpression_end();
+ }
+
+ @override
void visitIfStatement(IfStatement node) {
var condition = node.condition;
var thenStatement = node.thenStatement;
@@ -1269,12 +2159,63 @@
}
@override
+ void visitSwitchStatement(SwitchStatement node) {
+ node.expression.accept(this);
+ flow.switchStatement_expressionEnd(node);
+
+ var assignedInCases = loopAssignedVariables[node];
+
+ var members = node.members;
+ var membersLength = members.length;
+ var hasDefault = false;
+ for (var i = 0; i < membersLength; i++) {
+ var member = members[i];
+
+ flow.switchStatement_beginCase(
+ member.labels.isNotEmpty
+ ? assignedInCases
+ : LoopAssignedVariables.emptySet,
+ );
+ member.accept(this);
+
+ // Implicit `break` at the end of `default`.
+ if (member is SwitchDefault) {
+ hasDefault = true;
+ flow.handleBreak(node);
+ }
+ }
+
+ flow.switchStatement_end(node, hasDefault);
+ }
+
+ @override
void visitThrowExpression(ThrowExpression node) {
super.visitThrowExpression(node);
flow.handleExit();
}
@override
+ void visitTryStatement(TryStatement node) {
+ var body = node.body;
+ var catchClauses = node.catchClauses;
+
+ flow.tryStatement_bodyBegin();
+ body.accept(this);
+ flow.tryStatement_bodyEnd(loopAssignedVariables[node.body]);
+
+ var catchLength = catchClauses.length;
+ for (var i = 0; i < catchLength; ++i) {
+ var catchClause = catchClauses[i];
+ flow.tryStatement_catchBegin();
+ catchClause.accept(this);
+ flow.tryStatement_catchEnd();
+ }
+
+ flow.tryStatement_finallyBegin();
+ node.finallyBlock?.accept(this);
+ }
+
+ @override
void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
var variables = node.variables.variables;
for (var i = 0; i < variables.length; ++i) {
@@ -1291,7 +2232,7 @@
var condition = node.condition;
var body = node.body;
- flow.whileStatement_conditionBegin(node, loopAssignedVariables[node]);
+ flow.whileStatement_conditionBegin(loopAssignedVariables[node]);
condition.accept(this);
flow.whileStatement_bodyBegin(node);
@@ -1370,6 +2311,52 @@
}
@override
+ void visitForEachStatement(ForEachStatement node) {
+ var iterable = node.iterable;
+ var body = node.body;
+
+ iterable.accept(this);
+
+ loopAssignedVariables.beginLoop();
+ body.accept(this);
+ loopAssignedVariables.endLoop(node);
+ }
+
+ @override
+ void visitForStatement(ForStatement node) {
+ node.initialization?.accept(this);
+ node.variables?.accept(this);
+
+ loopAssignedVariables.beginLoop();
+ node.condition?.accept(this);
+ node.body.accept(this);
+ node.updaters?.accept(this);
+ loopAssignedVariables.endLoop(node);
+ }
+
+ @override
+ void visitSwitchStatement(SwitchStatement node) {
+ var expression = node.expression;
+ var members = node.members;
+
+ expression.accept(this);
+
+ loopAssignedVariables.beginLoop();
+ members.accept(this);
+ loopAssignedVariables.endLoop(node);
+ }
+
+ @override
+ void visitTryStatement(TryStatement node) {
+ loopAssignedVariables.beginLoop();
+ node.body.accept(this);
+ loopAssignedVariables.endLoop(node.body);
+
+ node.catchClauses.accept(this);
+ node.finallyBlock?.accept(this);
+ }
+
+ @override
void visitWhileStatement(WhileStatement node) {
loopAssignedVariables.beginLoop();
super.visitWhileStatement(node);