Abstract FlowAnalysis from concrete types of nodes and elements.

R=paulberry@google.com

Change-Id: I8a42eb260d2c99c4ca24cb8b9059c1d22f9430e9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106683
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@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 1c8dc31..b711bcb 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
@@ -2,62 +2,60 @@
 // 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';
+/// Sets of variables that are potentially assigned in a statement.
+class AssignedVariables<Statement, Element> {
+  final emptySet = Set<Element>();
 
-/// Sets of variables that are potentially assigned in a node.
-class AssignedVariables {
-  static final emptySet = Set<VariableElement>();
+  /// Mapping from a [Statement] representing a loop to the set of variables
+  /// that are potentially assigned in that loop.
+  final Map<Statement, Set<Element>> _map = {};
 
-  /// Mapping from a loop [AstNode] to the set of variables that are
-  /// potentially assigned in this loop.
-  final Map<AstNode, Set<VariableElement>> _map = {};
+  /// The stack of nested nodes.
+  final List<Set<Element>> _stack = [];
 
-  /// The stack of nested loops.
-  final List<Set<VariableElement>> _stack = [];
+  AssignedVariables();
 
-  /// Return the set of variables that are potentially assigned in the [loop].
-  Set<VariableElement> operator [](AstNode loop) {
-    return _map[loop] ?? emptySet;
+  /// Return the set of variables that are potentially assigned in the
+  /// [statement].
+  Set<Element> operator [](Statement statement) {
+    return _map[statement] ?? emptySet;
   }
 
   void beginLoop() {
-    var set = Set<VariableElement>.identity();
+    var set = Set<Element>.identity();
     _stack.add(set);
   }
 
-  void endLoop(AstNode loop) {
-    _map[loop] = _stack.removeLast();
+  void endLoop(Statement node) {
+    _map[node] = _stack.removeLast();
   }
 
-  void write(VariableElement variable) {
+  void write(Element variable) {
     for (var i = 0; i < _stack.length; ++i) {
       _stack[i].add(variable);
     }
   }
 }
 
-class FlowAnalysis<T> {
-  final _identity = _State<T>(
-    false,
-    _ElementSet.empty,
-    _ElementSet.empty,
-    _ElementSet.empty,
-    const {},
-  );
+class FlowAnalysis<Statement, Expression, Element, Type> {
+  final _ElementSet<Element> _emptySet;
+  final _State<Element, Type> _identity;
 
   /// The output list of variables that were read before they were written.
   /// TODO(scheglov) use _ElementSet?
-  final List<LocalVariableElement> readBeforeWritten = [];
+  final List<Element> readBeforeWritten = [];
+
+  /// The [NodeOperations], used to manipulate expressions.
+  final NodeOperations<Expression> nodeOperations;
 
   /// The [TypeOperations], used to access types, and check subtyping.
-  final TypeOperations<T> typeOperations;
+  final TypeOperations<Element, Type> typeOperations;
 
-  /// The enclosing [FunctionBody], used to check for potential mutations.
-  final FunctionBody functionBody;
+  /// The enclosing function body, used to check for potential mutations.
+  final FunctionBodyAccess functionBody;
 
   /// The stack of states of variables that are not definitely assigned.
-  final List<_State> _stack = [];
+  final List<_State<Element, Type>> _stack = [];
 
   /// The mapping from labeled [Statement]s to the index in the [_stack]
   /// where the first related element is located.  The number of elements
@@ -66,25 +64,55 @@
   final Map<Statement, int> _statementToStackIndex = {};
 
   /// The list of all variables.
-  final List<VariableElement> _variables = [];
+  final List<Element> _variables = [];
 
-  _State<T> _current;
+  _State<Element, Type> _current;
 
   /// The last boolean condition, for [_conditionTrue] and [_conditionFalse].
   Expression _condition;
 
   /// The state when [_condition] evaluates to `true`.
-  _State<T> _conditionTrue;
+  _State<Element, Type> _conditionTrue;
 
   /// The state when [_condition] evaluates to `false`.
-  _State<T> _conditionFalse;
+  _State<Element, Type> _conditionFalse;
 
-  FlowAnalysis(this.typeOperations, this.functionBody) {
-    _current = _State<T>(
+  factory FlowAnalysis(
+    NodeOperations<Expression> nodeOperations,
+    TypeOperations<Element, Type> typeOperations,
+    FunctionBodyAccess functionBody,
+  ) {
+    var emptySet = _ElementSet<Element>._(
+      List<Element>(0),
+    );
+    var identifyState = _State<Element, Type>(
+      false,
+      emptySet,
+      emptySet,
+      emptySet,
+      const {},
+    );
+    return FlowAnalysis._(
+      nodeOperations,
+      typeOperations,
+      functionBody,
+      emptySet,
+      identifyState,
+    );
+  }
+
+  FlowAnalysis._(
+    this.nodeOperations,
+    this.typeOperations,
+    this.functionBody,
+    this._emptySet,
+    this._identity,
+  ) {
+    _current = _State<Element, Type>(
       true,
-      _ElementSet.empty,
-      _ElementSet.empty,
-      _ElementSet.empty,
+      _emptySet,
+      _emptySet,
+      _emptySet,
       const {},
     );
   }
@@ -93,17 +121,18 @@
   bool get isReachable => _current.reachable;
 
   /// Add a new [variable], which might be already [assigned].
-  void add(VariableElement variable, {bool assigned: false}) {
+  void add(Element variable, {bool assigned: false}) {
     _variables.add(variable);
     _current = _current.add(variable, assigned: assigned);
   }
 
-  void conditional_elseBegin(ConditionalExpression node, bool isBool) {
+  void conditional_elseBegin(Expression conditionalExpression,
+      Expression thenExpression, bool isBool) {
     var afterThen = _current;
     var falseCondition = _stack.removeLast();
 
     if (isBool) {
-      _conditionalEnd(node.thenExpression);
+      _conditionalEnd(thenExpression);
       // Tail of the stack: falseThen, trueThen
     }
 
@@ -111,12 +140,13 @@
     _current = falseCondition;
   }
 
-  void conditional_end(ConditionalExpression node, bool isBool) {
+  void conditional_end(Expression conditionalExpression,
+      Expression elseExpression, bool isBool) {
     var afterThen = _stack.removeLast();
     var afterElse = _current;
 
     if (isBool) {
-      _conditionalEnd(node.elseExpression);
+      _conditionalEnd(elseExpression);
       // Tail of the stack: falseThen, trueThen, falseElse, trueElse
 
       var trueElse = _stack.removeLast();
@@ -128,7 +158,7 @@
       var trueResult = _join(trueThen, trueElse);
       var falseResult = _join(falseThen, falseElse);
 
-      _condition = node;
+      _condition = conditionalExpression;
       _conditionTrue = trueResult;
       _conditionFalse = falseResult;
     }
@@ -136,41 +166,41 @@
     _current = _join(afterThen, afterElse);
   }
 
-  void conditional_thenBegin(ConditionalExpression node) {
-    _conditionalEnd(node.condition);
+  void conditional_thenBegin(
+      Expression conditionalExpression, Expression condition) {
+    _conditionalEnd(condition);
     // Tail of the stack: falseCondition, trueCondition
 
     var trueCondition = _stack.removeLast();
     _current = trueCondition;
   }
 
-  /// The [node] checks that the [variable] is equal to `null`.
-  void conditionEqNull(BinaryExpression node, VariableElement variable) {
+  /// The [binaryExpression] checks that the [variable] is equal to `null`.
+  void conditionEqNull(Expression binaryExpression, Element variable) {
     if (functionBody.isPotentiallyMutatedInClosure(variable)) {
       return;
     }
 
-    _condition = node;
-    _conditionTrue = _current.markNullable(variable);
-    _conditionFalse = _current.markNonNullable(variable);
+    _condition = binaryExpression;
+    _conditionTrue = _current.markNullable(_emptySet, variable);
+    _conditionFalse = _current.markNonNullable(_emptySet, variable);
   }
 
-  /// The [node] checks that the [variable] is not equal to `null`.
-  void conditionNotEqNull(BinaryExpression node, VariableElement variable) {
+  /// The [binaryExpression] checks that the [variable] is not equal to `null`.
+  void conditionNotEqNull(Expression binaryExpression, Element variable) {
     if (functionBody.isPotentiallyMutatedInClosure(variable)) {
       return;
     }
 
-    _condition = node;
-    _conditionTrue = _current.markNonNullable(variable);
-    _conditionFalse = _current.markNullable(variable);
+    _condition = binaryExpression;
+    _conditionTrue = _current.markNonNullable(_emptySet, variable);
+    _conditionFalse = _current.markNullable(_emptySet, variable);
   }
 
-  void doStatement_bodyBegin(
-      DoStatement node, Set<VariableElement> loopAssigned) {
+  void doStatement_bodyBegin(Statement doStatement, Set<Element> loopAssigned) {
     _current = _current.removePromotedAll(loopAssigned);
 
-    _statementToStackIndex[node] = _stack.length;
+    _statementToStackIndex[doStatement] = _stack.length;
     _stack.add(_identity); // break
     _stack.add(_identity); // continue
   }
@@ -182,8 +212,8 @@
     _current = _join(_current, continueState);
   }
 
-  void doStatement_end(DoStatement node) {
-    _conditionalEnd(node.condition);
+  void doStatement_end(Statement doStatement, Expression condition) {
+    _conditionalEnd(condition);
     // Tail of the stack:  break, falseCondition, trueCondition
 
     _stack.removeLast(); // trueCondition
@@ -193,13 +223,13 @@
     _current = _join(falseCondition, breakState);
   }
 
-  void falseLiteral(BooleanLiteral expression) {
+  void falseLiteral(Expression expression) {
     _condition = expression;
     _conditionTrue = _identity;
     _conditionFalse = _current;
   }
 
-  void forEachStatement_bodyBegin(Set<VariableElement> loopAssigned) {
+  void forEachStatement_bodyBegin(Set<Element> loopAssigned) {
     _stack.add(_current);
     _current = _current.removePromotedAll(loopAssigned);
   }
@@ -222,7 +252,7 @@
     _current = trueCondition;
   }
 
-  void forStatement_conditionBegin(Set<VariableElement> loopAssigned) {
+  void forStatement_conditionBegin(Set<Element> loopAssigned) {
     _current = _current.removePromotedAll(loopAssigned);
   }
 
@@ -245,10 +275,10 @@
   void functionExpression_begin() {
     _stack.add(_current);
 
-    Set<VariableElement> notPromoted = null;
+    Set<Element> notPromoted = null;
     for (var variable in _current.promoted.keys) {
       if (functionBody.isPotentiallyMutatedInScope(variable)) {
-        notPromoted ??= Set<VariableElement>.identity();
+        notPromoted ??= Set<Element>.identity();
         notPromoted.add(variable);
       }
     }
@@ -262,7 +292,7 @@
     _current = _stack.removeLast();
   }
 
-  void handleBreak(AstNode target) {
+  void handleBreak(Statement target) {
     var breakIndex = _statementToStackIndex[target];
     if (breakIndex != null) {
       _stack[breakIndex] = _join(_stack[breakIndex], _current);
@@ -270,7 +300,7 @@
     _current = _current.exit();
   }
 
-  void handleContinue(AstNode target) {
+  void handleContinue(Statement target) {
     var breakIndex = _statementToStackIndex[target];
     if (breakIndex != null) {
       var continueIndex = breakIndex + 1;
@@ -302,8 +332,8 @@
   }
 
   void ifStatement_end(bool hasElse) {
-    _State<T> afterThen;
-    _State<T> afterElse;
+    _State<Element, Type> afterThen;
+    _State<Element, Type> afterElse;
     if (hasElse) {
       afterThen = _stack.removeLast();
       afterElse = _current;
@@ -314,8 +344,8 @@
     _current = _join(afterThen, afterElse);
   }
 
-  void ifStatement_thenBegin(IfStatement ifStatement) {
-    _conditionalEnd(ifStatement.condition);
+  void ifStatement_thenBegin(Statement ifStatement, Expression condition) {
+    _conditionalEnd(condition);
     // Tail of the stack:  falseCondition, trueCondition
 
     var trueCondition = _stack.removeLast();
@@ -323,33 +353,33 @@
   }
 
   void isExpression_end(
-      IsExpression isExpression, VariableElement variable, T type) {
+      Expression isExpression, Element variable, bool isNot, Type type) {
     if (functionBody.isPotentiallyMutatedInClosure(variable)) {
       return;
     }
 
     _condition = isExpression;
-    if (isExpression.notOperator == null) {
-      _conditionTrue = _current.promote(typeOperations, variable, type);
-      _conditionFalse = _current;
-    } else {
+    if (isNot) {
       _conditionTrue = _current;
       _conditionFalse = _current.promote(typeOperations, variable, type);
+    } else {
+      _conditionTrue = _current.promote(typeOperations, variable, type);
+      _conditionFalse = _current;
     }
   }
 
   /// Return `true` if the [variable] is known to be be nullable.
-  bool isNonNullable(VariableElement variable) {
+  bool isNonNullable(Element variable) {
     return !_current.notNonNullable.contains(variable);
   }
 
   /// Return `true` if the [variable] is known to be be non-nullable.
-  bool isNullable(VariableElement variable) {
+  bool isNullable(Element variable) {
     return !_current.notNullable.contains(variable);
   }
 
-  void logicalAnd_end(BinaryExpression andExpression) {
-    _conditionalEnd(andExpression.rightOperand);
+  void logicalAnd_end(Expression andExpression, Expression rightOperand) {
+    _conditionalEnd(rightOperand);
     // Tail of the stack: falseLeft, trueLeft, falseRight, trueRight
 
     var trueRight = _stack.removeLast();
@@ -369,16 +399,16 @@
     _current = afterResult;
   }
 
-  void logicalAnd_rightBegin(BinaryExpression andExpression) {
-    _conditionalEnd(andExpression.leftOperand);
+  void logicalAnd_rightBegin(Expression andExpression, Expression leftOperand) {
+    _conditionalEnd(leftOperand);
     // Tail of the stack: falseLeft, trueLeft
 
     var trueLeft = _stack.last;
     _current = trueLeft;
   }
 
-  void logicalNot_end(PrefixExpression notExpression) {
-    _conditionalEnd(notExpression.operand);
+  void logicalNot_end(Expression notExpression, Expression operand) {
+    _conditionalEnd(operand);
     var trueExpr = _stack.removeLast();
     var falseExpr = _stack.removeLast();
 
@@ -387,8 +417,8 @@
     _conditionFalse = trueExpr;
   }
 
-  void logicalOr_end(BinaryExpression orExpression) {
-    _conditionalEnd(orExpression.rightOperand);
+  void logicalOr_end(Expression orExpression, Expression rightOperand) {
+    _conditionalEnd(rightOperand);
     // Tail of the stack: falseLeft, trueLeft, falseRight, trueRight
 
     var trueRight = _stack.removeLast();
@@ -408,8 +438,8 @@
     _current = afterResult;
   }
 
-  void logicalOr_rightBegin(BinaryExpression orExpression) {
-    _conditionalEnd(orExpression.leftOperand);
+  void logicalOr_rightBegin(Expression orExpression, Expression leftOperand) {
+    _conditionalEnd(leftOperand);
     // Tail of the stack: falseLeft, trueLeft
 
     var falseLeft = _stack[_stack.length - 2];
@@ -418,12 +448,12 @@
 
   /// Retrieves the type that the [variable] is promoted to, if the [variable]
   /// is currently promoted.  Otherwise returns `null`.
-  T promotedType(VariableElement variable) {
+  Type promotedType(Element variable) {
     return _current.promoted[variable];
   }
 
   /// Register read of the given [variable] in the current state.
-  void read(LocalVariableElement variable) {
+  void read(Element variable) {
     if (_current.notAssigned.contains(variable)) {
       // Add to the list of violating variables, if not there yet.
       for (var i = 0; i < readBeforeWritten.length; ++i) {
@@ -440,11 +470,11 @@
   /// 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) {
+  void switchStatement_beginCase(Set<Element> notPromoted) {
     _current = _stack.last.removePromotedAll(notPromoted);
   }
 
-  void switchStatement_end(SwitchStatement node, bool hasDefault) {
+  void switchStatement_end(Statement switchStatement, bool hasDefault) {
     // Tail of the stack: break, continue, afterExpression
     var afterExpression = _current = _stack.removeLast();
     _stack.removeLast(); // continue
@@ -457,14 +487,14 @@
     }
   }
 
-  void switchStatement_expressionEnd(SwitchStatement node) {
-    _statementToStackIndex[node] = _stack.length;
+  void switchStatement_expressionEnd(Statement switchStatement) {
+    _statementToStackIndex[switchStatement] = _stack.length;
     _stack.add(_identity); // break
     _stack.add(_identity); // continue
     _stack.add(_current); // afterExpression
   }
 
-  void trueLiteral(BooleanLiteral expression) {
+  void trueLiteral(Expression expression) {
     _condition = expression;
     _conditionTrue = _current;
     _conditionFalse = _identity;
@@ -475,7 +505,7 @@
     // Tail of the stack: beforeBody
   }
 
-  void tryCatchStatement_bodyEnd(Set<VariableElement> assignedInBody) {
+  void tryCatchStatement_bodyEnd(Set<Element> assignedInBody) {
     var beforeBody = _stack.removeLast();
     var beforeCatch = beforeBody.removePromotedAll(assignedInBody);
     _stack.add(beforeCatch);
@@ -503,12 +533,17 @@
     _stack.add(_current); // beforeTry
   }
 
-  void tryFinallyStatement_end(Set<VariableElement> assignedInFinally) {
+  void tryFinallyStatement_end(Set<Element> assignedInFinally) {
     var afterBody = _stack.removeLast();
-    _current = _current.restrict(typeOperations, afterBody, assignedInFinally);
+    _current = _current.restrict(
+      typeOperations,
+      _emptySet,
+      afterBody,
+      assignedInFinally,
+    );
   }
 
-  void tryFinallyStatement_finallyBegin(Set<VariableElement> assignedInBody) {
+  void tryFinallyStatement_finallyBegin(Set<Element> assignedInBody) {
     var beforeTry = _stack.removeLast();
     var afterBody = _current;
     _stack.add(afterBody);
@@ -519,20 +554,21 @@
     assert(_stack.isEmpty);
   }
 
-  void whileStatement_bodyBegin(WhileStatement node) {
-    _conditionalEnd(node.condition);
+  void whileStatement_bodyBegin(
+      Statement whileStatement, Expression condition) {
+    _conditionalEnd(condition);
     // Tail of the stack: falseCondition, trueCondition
 
     var trueCondition = _stack.removeLast();
 
-    _statementToStackIndex[node] = _stack.length;
+    _statementToStackIndex[whileStatement] = _stack.length;
     _stack.add(_identity); // break
     _stack.add(_identity); // continue
 
     _current = trueCondition;
   }
 
-  void whileStatement_conditionBegin(Set<VariableElement> loopAssigned) {
+  void whileStatement_conditionBegin(Set<Element> loopAssigned) {
     _current = _current.removePromotedAll(loopAssigned);
   }
 
@@ -546,17 +582,16 @@
 
   /// Register write of the given [variable] in the current state.
   void write(
-    VariableElement variable, {
+    Element variable, {
     bool isNull = false,
     bool isNonNull = false,
   }) {
-    _current = _current.write(variable, isNull: isNull, isNonNull: isNonNull);
+    _current = _current.write(typeOperations, _emptySet, variable,
+        isNull: isNull, isNonNull: isNonNull);
   }
 
   void _conditionalEnd(Expression condition) {
-    while (condition is ParenthesizedExpression) {
-      condition = (condition as ParenthesizedExpression).expression;
-    }
+    condition = nodeOperations.unwrapParenthesized(condition);
     if (identical(condition, _condition)) {
       _stack.add(_conditionFalse);
       _stack.add(_conditionTrue);
@@ -566,7 +601,10 @@
     }
   }
 
-  _State<T> _join(_State<T> first, _State<T> second) {
+  _State<Element, Type> _join(
+    _State<Element, Type> first,
+    _State<Element, Type> second,
+  ) {
     if (identical(first, _identity)) return second;
     if (identical(second, _identity)) return first;
 
@@ -590,14 +628,14 @@
     );
   }
 
-  Map<VariableElement, T> _joinPromoted(
-    Map<VariableElement, T> first,
-    Map<VariableElement, T> second,
+  Map<Element, Type> _joinPromoted(
+    Map<Element, Type> first,
+    Map<Element, Type> second,
   ) {
     if (identical(first, second)) return first;
     if (first.isEmpty || second.isEmpty) return const {};
 
-    var result = <VariableElement, T>{};
+    var result = <Element, Type>{};
     var alwaysFirst = true;
     var alwaysSecond = true;
     for (var element in first.keys) {
@@ -627,32 +665,44 @@
   }
 }
 
+/// Accessor for function body information.
+abstract class FunctionBodyAccess<Element> {
+  bool isPotentiallyMutatedInClosure(Element variable);
+
+  bool isPotentiallyMutatedInScope(Element variable);
+}
+
+/// Operations on nodes, abstracted from concrete node interfaces.
+abstract class NodeOperations<Expression> {
+  /// If the [node] is a parenthesized expression, recursively unwrap it.
+  Expression unwrapParenthesized(Expression node);
+}
+
 /// Operations on types, abstracted from concrete type interfaces.
-abstract class TypeOperations<T> {
-  /// Return the static type of with the given [element].
-  T elementType(VariableElement element);
+abstract class TypeOperations<Element, Type> {
+  /// Return the static type of the given [element].
+  Type elementType(Element element);
+
+  /// Return `true` if the [element] is a local variable, not a parameter.
+  bool isLocalVariable(Element element);
 
   /// Return `true` if the [leftType] is a subtype of the [rightType].
-  bool isSubtypeOf(T leftType, T rightType);
+  bool isSubtypeOf(Type leftType, Type rightType);
 }
 
 /// List based immutable set of elements.
-class _ElementSet {
-  static final empty = _ElementSet._(
-    List<VariableElement>(0),
-  );
-
-  final List<VariableElement> elements;
+class _ElementSet<Element> {
+  final List<Element> elements;
 
   _ElementSet._(this.elements);
 
-  _ElementSet add(VariableElement addedElement) {
+  _ElementSet<Element> add(Element addedElement) {
     if (contains(addedElement)) {
       return this;
     }
 
     var length = elements.length;
-    var newElements = List<VariableElement>(length + 1);
+    var newElements = List<Element>(length + 1);
     for (var i = 0; i < length; ++i) {
       newElements[i] = elements[i];
     }
@@ -660,7 +710,7 @@
     return _ElementSet._(newElements);
   }
 
-  _ElementSet addAll(Iterable<VariableElement> elements) {
+  _ElementSet<Element> addAll(Iterable<Element> elements) {
     var result = this;
     for (var element in elements) {
       result = result.add(element);
@@ -668,7 +718,7 @@
     return result;
   }
 
-  bool contains(VariableElement element) {
+  bool contains(Element element) {
     var length = elements.length;
     for (var i = 0; i < length; ++i) {
       if (identical(elements[i], element)) {
@@ -678,7 +728,10 @@
     return false;
   }
 
-  _ElementSet intersect(_ElementSet other) {
+  _ElementSet<Element> intersect({
+    _ElementSet<Element> empty,
+    _ElementSet<Element> other,
+  }) {
     if (identical(other, empty)) return empty;
 
     // TODO(scheglov) optimize
@@ -689,7 +742,10 @@
     return _ElementSet._(newElements);
   }
 
-  _ElementSet remove(VariableElement removedElement) {
+  _ElementSet<Element> remove(
+    _ElementSet<Element> empty,
+    Element removedElement,
+  ) {
     if (!contains(removedElement)) {
       return this;
     }
@@ -699,7 +755,7 @@
       return empty;
     }
 
-    var newElements = List<VariableElement>(length - 1);
+    var newElements = List<Element>(length - 1);
     var newIndex = 0;
     for (var i = 0; i < length; ++i) {
       var element = elements[i];
@@ -711,7 +767,7 @@
     return _ElementSet._(newElements);
   }
 
-  _ElementSet union(_ElementSet other) {
+  _ElementSet<Element> union(_ElementSet<Element> other) {
     if (other.elements.isEmpty) {
       return this;
     }
@@ -726,12 +782,12 @@
   }
 }
 
-class _State<T> {
+class _State<Element, Type> {
   final bool reachable;
-  final _ElementSet notAssigned;
-  final _ElementSet notNullable;
-  final _ElementSet notNonNullable;
-  final Map<VariableElement, T> promoted;
+  final _ElementSet<Element> notAssigned;
+  final _ElementSet<Element> notNullable;
+  final _ElementSet<Element> notNonNullable;
+  final Map<Element, Type> promoted;
 
   _State(
     this.reachable,
@@ -742,7 +798,7 @@
   );
 
   /// Add a new [variable] to track definite assignment.
-  _State<T> add(VariableElement variable, {bool assigned: false}) {
+  _State<Element, Type> add(Element variable, {bool assigned: false}) {
     var newNotAssigned = assigned ? notAssigned : notAssigned.add(variable);
     var newNotNullable = notNullable.add(variable);
     var newNotNonNullable = notNonNullable.add(variable);
@@ -753,7 +809,7 @@
       return this;
     }
 
-    return _State<T>(
+    return _State<Element, Type>(
       reachable,
       newNotAssigned,
       newNotNullable,
@@ -762,20 +818,27 @@
     );
   }
 
-  _State<T> exit() {
-    return _State<T>(false, notAssigned, notNullable, notNonNullable, promoted);
+  _State<Element, Type> exit() {
+    return _State<Element, Type>(
+      false,
+      notAssigned,
+      notNullable,
+      notNonNullable,
+      promoted,
+    );
   }
 
-  _State<T> markNonNullable(VariableElement variable) {
+  _State<Element, Type> markNonNullable(
+      _ElementSet<Element> emptySet, Element variable) {
     var newNotNullable = notNullable.add(variable);
-    var newNotNonNullable = notNonNullable.remove(variable);
+    var newNotNonNullable = notNonNullable.remove(emptySet, variable);
 
     if (identical(newNotNullable, notNullable) &&
         identical(newNotNonNullable, notNonNullable)) {
       return this;
     }
 
-    return _State<T>(
+    return _State<Element, Type>(
       reachable,
       notAssigned,
       newNotNullable,
@@ -784,8 +847,9 @@
     );
   }
 
-  _State<T> markNullable(VariableElement variable) {
-    var newNotNullable = notNullable.remove(variable);
+  _State<Element, Type> markNullable(
+      _ElementSet<Element> emptySet, Element variable) {
+    var newNotNullable = notNullable.remove(emptySet, variable);
     var newNotNonNullable = notNonNullable.add(variable);
 
     if (identical(newNotNullable, notNullable) &&
@@ -793,7 +857,7 @@
       return this;
     }
 
-    return _State<T>(
+    return _State<Element, Type>(
       reachable,
       notAssigned,
       newNotNullable,
@@ -802,19 +866,19 @@
     );
   }
 
-  _State<T> promote(
-    TypeOperations<T> typeOperations,
-    VariableElement variable,
-    T type,
+  _State<Element, Type> promote(
+    TypeOperations<Element, Type> typeOperations,
+    Element variable,
+    Type type,
   ) {
     var previousType = promoted[variable];
     previousType ??= typeOperations.elementType(variable);
 
     if (typeOperations.isSubtypeOf(type, previousType) &&
         type != previousType) {
-      var newPromoted = <VariableElement, T>{}..addAll(promoted);
+      var newPromoted = <Element, Type>{}..addAll(promoted);
       newPromoted[variable] = type;
-      return _State<T>(
+      return _State<Element, Type>(
         reachable,
         notAssigned,
         notNullable,
@@ -826,7 +890,7 @@
     return this;
   }
 
-  _State<T> removePromotedAll(Set<VariableElement> variables) {
+  _State<Element, Type> removePromotedAll(Set<Element> variables) {
     var newNotNullable = notNullable.addAll(variables);
     var newNotNonNullable = notNonNullable.addAll(variables);
     var newPromoted = _removePromotedAll(promoted, variables);
@@ -835,7 +899,7 @@
         identical(newNotNonNullable, notNonNullable) &&
         identical(newPromoted, promoted)) return this;
 
-    return _State<T>(
+    return _State<Element, Type>(
       reachable,
       notAssigned,
       newNotNullable,
@@ -844,22 +908,26 @@
     );
   }
 
-  _State<T> restrict(
-    TypeOperations<T> typeOperations,
-    _State<T> other,
-    Set<VariableElement> unsafe,
+  _State<Element, Type> restrict(
+    TypeOperations<Element, Type> typeOperations,
+    _ElementSet<Element> emptySet,
+    _State<Element, Type> other,
+    Set<Element> unsafe,
   ) {
     var newReachable = reachable && other.reachable;
-    var newNotAssigned = notAssigned.intersect(other.notAssigned);
+    var newNotAssigned = notAssigned.intersect(
+      empty: emptySet,
+      other: other.notAssigned,
+    );
 
-    var newNotNullable = _ElementSet.empty;
+    var newNotNullable = emptySet;
     for (var variable in notNullable.elements) {
       if (unsafe.contains(variable) || other.notNullable.contains(variable)) {
         newNotNullable = newNotNullable.add(variable);
       }
     }
 
-    var newNotNonNullable = _ElementSet.empty;
+    var newNotNonNullable = emptySet;
     for (var variable in notNonNullable.elements) {
       if (unsafe.contains(variable) ||
           other.notNonNullable.contains(variable)) {
@@ -867,7 +935,7 @@
       }
     }
 
-    var newPromoted = <VariableElement, T>{};
+    var newPromoted = <Element, Type>{};
     for (var variable in promoted.keys) {
       var thisType = promoted[variable];
       if (!unsafe.contains(variable)) {
@@ -892,10 +960,10 @@
     );
   }
 
-  _State<T> setReachable(bool reachable) {
+  _State<Element, Type> setReachable(bool reachable) {
     if (this.reachable == reachable) return this;
 
-    return _State<T>(
+    return _State<Element, Type>(
       reachable,
       notAssigned,
       notNullable,
@@ -904,20 +972,23 @@
     );
   }
 
-  _State<T> write(
-    VariableElement variable, {
+  _State<Element, Type> write(
+    TypeOperations<Element, Type> typeOperations,
+    _ElementSet<Element> emptySet,
+    Element variable, {
     bool isNull = false,
     bool isNonNull = false,
   }) {
-    var newNotAssigned = variable is LocalVariableElement
-        ? notAssigned.remove(variable)
+    var newNotAssigned = typeOperations.isLocalVariable(variable)
+        ? notAssigned.remove(emptySet, variable)
         : notAssigned;
 
-    var newNotNullable =
-        isNull ? notNullable.remove(variable) : notNullable.add(variable);
+    var newNotNullable = isNull
+        ? notNullable.remove(emptySet, variable)
+        : notNullable.add(variable);
 
     var newNotNonNullable = isNonNull
-        ? notNonNullable.remove(variable)
+        ? notNonNullable.remove(emptySet, variable)
         : notNonNullable.add(variable);
 
     var newPromoted = _removePromoted(promoted, variable);
@@ -929,7 +1000,7 @@
       return this;
     }
 
-    return _State<T>(
+    return _State<Element, Type>(
       reachable,
       newNotAssigned,
       newNotNullable,
@@ -938,13 +1009,10 @@
     );
   }
 
-  Map<VariableElement, T> _removePromoted(
-    Map<VariableElement, T> map,
-    VariableElement variable,
-  ) {
+  Map<Element, Type> _removePromoted(Map<Element, Type> map, Element variable) {
     if (map.isEmpty) return const {};
 
-    var result = <VariableElement, T>{};
+    var result = <Element, Type>{};
     for (var key in map.keys) {
       if (!identical(key, variable)) {
         result[key] = map[key];
@@ -955,14 +1023,14 @@
     return result;
   }
 
-  Map<VariableElement, T> _removePromotedAll(
-    Map<VariableElement, T> map,
-    Set<VariableElement> variables,
+  Map<Element, Type> _removePromotedAll(
+    Map<Element, Type> map,
+    Set<Element> variables,
   ) {
     if (map.isEmpty) return const {};
     if (variables.isEmpty) return map;
 
-    var result = <VariableElement, T>{};
+    var result = <Element, Type>{};
     var noChanges = true;
     for (var key in map.keys) {
       if (variables.contains(key)) {
@@ -977,14 +1045,14 @@
     return result;
   }
 
-  static _State<T> _identicalOrNew<T>(
-    _State<T> first,
-    _State<T> second,
+  static _State<Element, Type> _identicalOrNew<Element, Type>(
+    _State<Element, Type> first,
+    _State<Element, Type> second,
     bool newReachable,
-    _ElementSet newNotAssigned,
-    _ElementSet newNotNullable,
-    _ElementSet newNotNonNullable,
-    Map<VariableElement, T> newPromoted,
+    _ElementSet<Element> newNotAssigned,
+    _ElementSet<Element> newNotNullable,
+    _ElementSet<Element> newNotNonNullable,
+    Map<Element, Type> newPromoted,
   ) {
     if (first.reachable == newReachable &&
         identical(first.notAssigned, newNotAssigned) &&
@@ -1001,7 +1069,7 @@
       return second;
     }
 
-    return _State<T>(
+    return _State<Element, Type>(
       newReachable,
       newNotAssigned,
       newNotNullable,
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 5dd9420..6a39734 100644
--- a/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
@@ -1308,13 +1308,13 @@
 
     var unit = result.unit;
 
-    var loopAssignedVariables = AssignedVariables();
-    unit.accept(_AssignedVariablesVisitor(loopAssignedVariables));
+    var assignedVariables = AssignedVariables<Statement, VariableElement>();
+    unit.accept(_AssignedVariablesVisitor(assignedVariables));
 
     var typeSystem = unit.declaredElement.context.typeSystem;
     unit.accept(_AstVisitor(
       typeSystem,
-      loopAssignedVariables,
+      assignedVariables,
       {},
       readBeforeWritten,
       [],
@@ -1623,13 +1623,13 @@
 
     var unit = result.unit;
 
-    var loopAssignedVariables = AssignedVariables();
-    unit.accept(_AssignedVariablesVisitor(loopAssignedVariables));
+    var assignedVariables = AssignedVariables<Statement, VariableElement>();
+    unit.accept(_AssignedVariablesVisitor(assignedVariables));
 
     var typeSystem = unit.declaredElement.context.typeSystem;
     unit.accept(_AstVisitor(
       typeSystem,
-      loopAssignedVariables,
+      assignedVariables,
       {},
       [],
       nullableNodes,
@@ -2070,13 +2070,13 @@
 
     var unit = result.unit;
 
-    var loopAssignedVariables = AssignedVariables();
-    unit.accept(_AssignedVariablesVisitor(loopAssignedVariables));
+    var assignedVariables = AssignedVariables<Statement, VariableElement>();
+    unit.accept(_AssignedVariablesVisitor(assignedVariables));
 
     var typeSystem = unit.declaredElement.context.typeSystem;
     unit.accept(_AstVisitor(
       typeSystem,
-      loopAssignedVariables,
+      assignedVariables,
       {},
       [],
       [],
@@ -2903,13 +2903,13 @@
 
     var unit = result.unit;
 
-    var loopAssignedVariables = AssignedVariables();
-    unit.accept(_AssignedVariablesVisitor(loopAssignedVariables));
+    var assignedVariables = AssignedVariables<Statement, VariableElement>();
+    unit.accept(_AssignedVariablesVisitor(assignedVariables));
 
     var typeSystem = unit.declaredElement.context.typeSystem;
     unit.accept(_AstVisitor(
       typeSystem,
-      loopAssignedVariables,
+      assignedVariables,
       promotedTypes,
       [],
       [],
@@ -3018,7 +3018,8 @@
 class _AstVisitor extends GeneralizingAstVisitor<void> {
   static final trueLiteral = astFactory.booleanLiteral(null, true);
 
-  final TypeOperations<DartType> typeOperations;
+  final NodeOperations<Expression> nodeOperations;
+  final TypeOperations<VariableElement, DartType> typeOperations;
   final AssignedVariables assignedVariables;
   final Map<AstNode, DartType> promotedTypes;
   final List<LocalVariableElement> readBeforeWritten;
@@ -3027,7 +3028,7 @@
   final List<AstNode> unreachableNodes;
   final List<FunctionBody> functionBodiesThatDontComplete;
 
-  FlowAnalysis<DartType> flow;
+  FlowAnalysis<Statement, Expression, VariableElement, DartType> flow;
 
   _AstVisitor(
       TypeSystem typeSystem,
@@ -3038,7 +3039,8 @@
       this.nonNullableNodes,
       this.unreachableNodes,
       this.functionBodiesThatDontComplete)
-      : typeOperations = _TypeSystemTypeOperations(typeSystem);
+      : nodeOperations = _NodeOperations(),
+        typeOperations = _TypeSystemTypeOperations(typeSystem);
 
   @override
   void visitAssignmentExpression(AssignmentExpression node) {
@@ -3080,19 +3082,19 @@
     if (operator == TokenType.AMPERSAND_AMPERSAND) {
       left.accept(this);
 
-      flow.logicalAnd_rightBegin(node);
+      flow.logicalAnd_rightBegin(node, node.leftOperand);
       _checkUnreachableNode(node.rightOperand);
       right.accept(this);
 
-      flow.logicalAnd_end(node);
+      flow.logicalAnd_end(node, node.rightOperand);
     } else if (operator == TokenType.BAR_BAR) {
       left.accept(this);
 
-      flow.logicalOr_rightBegin(node);
+      flow.logicalOr_rightBegin(node, node.leftOperand);
       _checkUnreachableNode(node.rightOperand);
       right.accept(this);
 
-      flow.logicalOr_end(node);
+      flow.logicalOr_end(node, node.rightOperand);
     } else if (operator == TokenType.BANG_EQ) {
       left.accept(this);
       right.accept(this);
@@ -3133,7 +3135,11 @@
     var isFlowOwner = flow == null;
 
     if (isFlowOwner) {
-      flow = FlowAnalysis<DartType>(typeOperations, node);
+      flow = FlowAnalysis<Statement, Expression, VariableElement, DartType>(
+        nodeOperations,
+        typeOperations,
+        _FunctionBodyAccess(node),
+      );
 
       var function = node.parent;
       if (function is FunctionExpression) {
@@ -3149,7 +3155,10 @@
     super.visitBlockFunctionBody(node);
 
     if (isFlowOwner) {
-      readBeforeWritten.addAll(flow.readBeforeWritten);
+      for (var variable in flow.readBeforeWritten) {
+        assert(variable is LocalVariableElement);
+        readBeforeWritten.add(variable);
+      }
 
       if (!flow.isReachable) {
         functionBodiesThatDontComplete.add(node);
@@ -3186,16 +3195,16 @@
 
     condition.accept(this);
 
-    flow.conditional_thenBegin(node);
+    flow.conditional_thenBegin(node, node.condition);
     _checkUnreachableNode(node.thenExpression);
     thenExpression.accept(this);
     var isBool = thenExpression.staticType.isDartCoreBool;
 
-    flow.conditional_elseBegin(node, isBool);
+    flow.conditional_elseBegin(node, node.thenExpression, isBool);
     _checkUnreachableNode(node.elseExpression);
     elseExpression.accept(this);
 
-    flow.conditional_end(node, isBool);
+    flow.conditional_end(node, node.elseExpression, isBool);
   }
 
   @override
@@ -3218,7 +3227,7 @@
     flow.doStatement_conditionBegin();
     condition.accept(this);
 
-    flow.doStatement_end(node);
+    flow.doStatement_end(node, node.condition);
   }
 
   @override
@@ -3285,7 +3294,7 @@
 
     condition.accept(this);
 
-    flow.ifStatement_thenBegin(node);
+    flow.ifStatement_thenBegin(node, node.condition);
     thenStatement.accept(this);
 
     if (elseStatement != null) {
@@ -3305,7 +3314,12 @@
     if (expression is SimpleIdentifier) {
       var element = expression.staticElement;
       if (element is VariableElement) {
-        flow.isExpression_end(node, element, typeAnnotation.type);
+        flow.isExpression_end(
+          node,
+          element,
+          node.notOperator != null,
+          typeAnnotation.type,
+        );
       }
     }
   }
@@ -3317,7 +3331,7 @@
     var operator = node.operator.type;
     if (operator == TokenType.BANG) {
       operand.accept(this);
-      flow.logicalNot_end(node);
+      flow.logicalNot_end(node, node.operand);
     } else {
       operand.accept(this);
     }
@@ -3385,7 +3399,7 @@
       var member = members[i];
 
       flow.switchStatement_beginCase(
-        member.labels.isNotEmpty ? assignedInCases : AssignedVariables.emptySet,
+        member.labels.isNotEmpty ? assignedInCases : assignedVariables.emptySet,
       );
       member.accept(this);
 
@@ -3460,7 +3474,7 @@
     flow.whileStatement_conditionBegin(assignedVariables[node]);
     condition.accept(this);
 
-    flow.whileStatement_bodyBegin(node);
+    flow.whileStatement_bodyBegin(node, node.condition);
     body.accept(this);
 
     flow.whileStatement_end();
@@ -3532,7 +3546,34 @@
   }
 }
 
-class _TypeSystemTypeOperations implements TypeOperations<DartType> {
+class _FunctionBodyAccess implements FunctionBodyAccess<VariableElement> {
+  final FunctionBody node;
+
+  _FunctionBodyAccess(this.node);
+
+  @override
+  bool isPotentiallyMutatedInClosure(VariableElement variable) {
+    return node.isPotentiallyMutatedInClosure(variable);
+  }
+
+  @override
+  bool isPotentiallyMutatedInScope(VariableElement variable) {
+    return node.isPotentiallyMutatedInScope(variable);
+  }
+}
+
+class _NodeOperations implements NodeOperations<Expression> {
+  @override
+  Expression unwrapParenthesized(Expression node) {
+    while (node is ParenthesizedExpression) {
+      node = (node as ParenthesizedExpression).expression;
+    }
+    return node;
+  }
+}
+
+class _TypeSystemTypeOperations
+    implements TypeOperations<VariableElement, DartType> {
   final TypeSystem typeSystem;
 
   _TypeSystemTypeOperations(this.typeSystem);
@@ -3546,4 +3587,9 @@
   bool isSubtypeOf(DartType leftType, DartType rightType) {
     return typeSystem.isSubtypeOf(leftType, rightType);
   }
+
+  @override
+  bool isLocalVariable(VariableElement element) {
+    return element is LocalVariableElement;
+  }
 }
diff --git a/pkg/nnbd_migration/lib/src/decorated_type_operations.dart b/pkg/nnbd_migration/lib/src/decorated_type_operations.dart
index b1e3fa8..0b7a04c 100644
--- a/pkg/nnbd_migration/lib/src/decorated_type_operations.dart
+++ b/pkg/nnbd_migration/lib/src/decorated_type_operations.dart
@@ -9,7 +9,8 @@
 import 'package:nnbd_migration/src/node_builder.dart';
 
 /// [TypeOperations] that works with [DecoratedType]s.
-class DecoratedTypeOperations implements TypeOperations<DecoratedType> {
+class DecoratedTypeOperations
+    implements TypeOperations<VariableElement, DecoratedType> {
   final TypeSystem _typeSystem;
   final VariableRepository _variableRepository;
 
@@ -21,6 +22,11 @@
   }
 
   @override
+  bool isLocalVariable(VariableElement element) {
+    return element is LocalVariableElement;
+  }
+
+  @override
   bool isSubtypeOf(DecoratedType leftType, DecoratedType rightType) {
     return _typeSystem.isSubtypeOf(leftType.type, rightType.type);
   }