Extract current TypePromotionManager, make clients use LocalVariableTypeProvider.

The clients now use SimpleIdentifier to ask for the type,
which is compatible with information provided in FlowAnalysisResult.

R=brianwilkerson@google.com, paulberry@google.com

Change-Id: Id5165b5a519d2cc737e1bdcb0267b5d155fa698f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107312
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
index 184d379..3bb5ec4 100644
--- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -13,6 +13,7 @@
 import 'package:analyzer/src/dart/resolver/scope.dart';
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/resolver.dart';
+import 'package:analyzer/src/generated/variable_type_provider.dart';
 
 class MethodInvocationResolver {
   static final _nameCall = new Name(null, 'call');
@@ -35,8 +36,8 @@
   /// The URI of [_definingLibrary].
   final Uri _definingLibraryUri;
 
-  /// The object keeping track of which elements have had their types promoted.
-  final TypePromotionManager _promoteManager;
+  /// The object providing promoted or declared types of variables.
+  final LocalVariableTypeProvider _localVariableTypeProvider;
 
   /// The invocation being resolved.
   MethodInvocationImpl _invocation;
@@ -49,7 +50,7 @@
         _inheritance = _resolver.inheritance,
         _definingLibrary = _resolver.definingLibrary,
         _definingLibraryUri = _resolver.definingLibrary.source.uri,
-        _promoteManager = _resolver.promoteManager;
+        _localVariableTypeProvider = _resolver.localVariableTypeProvider;
 
   /// The scope used to resolve identifiers.
   Scope get nameScope => _resolver.nameScope;
@@ -400,7 +401,7 @@
         return _setResolution(node, calleeType);
       }
       if (element is VariableElement) {
-        var targetType = _promoteManager.getStaticType(element);
+        var targetType = _localVariableTypeProvider.getType(nameNode);
         return _setResolution(node, targetType);
       }
       // TODO(scheglov) This is a questionable distinction.
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index b5b6b72..bf962bb 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -35,7 +35,9 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/generated/static_type_analyzer.dart';
 import 'package:analyzer/src/generated/testing/element_factory.dart';
+import 'package:analyzer/src/generated/type_promotion_manager.dart';
 import 'package:analyzer/src/generated/type_system.dart';
+import 'package:analyzer/src/generated/variable_type_provider.dart';
 import 'package:analyzer/src/lint/linter.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:meta/meta.dart';
@@ -3715,7 +3717,7 @@
   InferenceContext inferenceContext = null;
 
   /// The object keeping track of which elements have had their types promoted.
-  TypePromotionManager _promoteManager = new TypePromotionManager();
+  TypePromotionManager _promoteManager;
 
   /// A comment before a function should be resolved in the context of the
   /// function. But when we incrementally resolve a comment, we don't want to
@@ -3725,9 +3727,6 @@
   /// be built and the comment resolved.
   bool resolveOnlyCommentInFunctionBody = false;
 
-  /// Body of the function currently being analyzed, if any.
-  FunctionBody _currentFunctionBody;
-
   /// The type of the expression of the immediately enclosing [SwitchStatement],
   /// or `null` if not in a [SwitchStatement].
   DartType _enclosingSwitchStatementExpressionType;
@@ -3784,9 +3783,10 @@
                 featureSet.isEnabled(Feature.spread_collections),
         super(definingLibrary, source, typeProvider, errorListener,
             nameScope: nameScope) {
+    this.typeSystem = definingLibrary.context.typeSystem;
+    this._promoteManager = TypePromotionManager(typeSystem);
     this.elementResolver = new ElementResolver(this,
         reportConstEvaluationErrors: reportConstEvaluationErrors);
-    this.typeSystem = definingLibrary.context.typeSystem;
     bool strongModeHints = false;
     AnalysisOptions options = _analysisOptions;
     if (options is AnalysisOptionsImpl) {
@@ -3803,12 +3803,10 @@
   /// @return the element representing the function containing the current node
   ExecutableElement get enclosingFunction => _enclosingFunction;
 
-  /// Return the object keeping track of which elements have had their types
-  /// promoted.
-  ///
-  /// @return the object keeping track of which elements have had their types
-  ///         promoted
-  TypePromotionManager get promoteManager => _promoteManager;
+  /// Return the object providing promoted or declared types of variables.
+  LocalVariableTypeProvider get localVariableTypeProvider {
+    return _promoteManager.localVariableTypeProvider;
+  }
 
   /// Return the static element associated with the given expression whose type
   /// can be overridden, or `null` if there is no element whose type can be
@@ -3831,24 +3829,6 @@
     return null;
   }
 
-  /// Return the static element associated with the given expression whose type
-  /// can be promoted, or `null` if there is no element whose type can be
-  /// promoted.
-  VariableElement getPromotionStaticElement(Expression expression) {
-    expression = expression?.unParenthesized;
-    if (expression is SimpleIdentifier) {
-      Element element = expression.staticElement;
-      if (element is VariableElement) {
-        ElementKind kind = element.kind;
-        if (kind == ElementKind.LOCAL_VARIABLE ||
-            kind == ElementKind.PARAMETER) {
-          return element;
-        }
-      }
-    }
-    return null;
-  }
-
   /// Given a downward inference type [fnType], and the declared
   /// [typeParameterList] for a function expression, determines if we can enable
   /// downward inference and if so, returns the function type to use for
@@ -3907,7 +3887,7 @@
 
   /// Set the enclosing function body when partial AST is resolved.
   void prepareCurrentFunctionBody(FunctionBody body) {
-    _currentFunctionBody = body;
+    _promoteManager.enterFunctionBody(body);
   }
 
   /// Set information about enclosing declarations.
@@ -4049,22 +4029,15 @@
     if (operatorType == TokenType.AMPERSAND_AMPERSAND) {
       InferenceContext.setType(leftOperand, typeProvider.boolType);
       InferenceContext.setType(rightOperand, typeProvider.boolType);
+      // TODO(scheglov) Do we need these checks for null?
       leftOperand?.accept(this);
-      if (rightOperand != null) {
-        _promoteManager.enterScope();
-        try {
-          // Type promotion.
-          _promoteTypes(leftOperand);
-          _clearTypePromotionsIfPotentiallyMutatedIn(leftOperand);
-          _clearTypePromotionsIfPotentiallyMutatedIn(rightOperand);
-          _clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(
-              rightOperand);
-          // Visit right operand.
+      _promoteManager.visitBinaryExpression_and_rhs(
+        leftOperand,
+        rightOperand,
+        () {
           rightOperand.accept(this);
-        } finally {
-          _promoteManager.exitScope();
-        }
-      }
+        },
+      );
       node.accept(elementResolver);
     } else if (operatorType == TokenType.BAR_BAR) {
       InferenceContext.setType(leftOperand, typeProvider.boolType);
@@ -4215,23 +4188,17 @@
   @override
   void visitConditionalExpression(ConditionalExpression node) {
     Expression condition = node.condition;
+    // TODO(scheglov) Do we need these checks for null?
     condition?.accept(this);
     Expression thenExpression = node.thenExpression;
-    if (thenExpression != null) {
-      _promoteManager.enterScope();
-      try {
-        // Type promotion.
-        _promoteTypes(condition);
-        _clearTypePromotionsIfPotentiallyMutatedIn(thenExpression);
-        _clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(
-            thenExpression);
-        // Visit "then" expression.
+    _promoteManager.visitConditionalExpression_then(
+      condition,
+      thenExpression,
+      () {
         InferenceContext.setTypeFromNode(thenExpression, node);
         thenExpression.accept(this);
-      } finally {
-        _promoteManager.exitScope();
-      }
-    }
+      },
+    );
     Expression elseExpression = node.elseExpression;
     if (elseExpression != null) {
       InferenceContext.setTypeFromNode(elseExpression, node);
@@ -4244,15 +4211,14 @@
   @override
   void visitConstructorDeclaration(ConstructorDeclaration node) {
     ExecutableElement outerFunction = _enclosingFunction;
-    FunctionBody outerFunctionBody = _currentFunctionBody;
     try {
-      _currentFunctionBody = node.body;
+      _promoteManager.enterFunctionBody(node.body);
       _enclosingFunction = node.declaredElement;
       FunctionType type = _enclosingFunction.type;
       InferenceContext.setType(node.body, type.returnType);
       super.visitConstructorDeclaration(node);
     } finally {
-      _currentFunctionBody = outerFunctionBody;
+      _promoteManager.exitFunctionBody();
       _enclosingFunction = outerFunction;
     }
     ConstructorElementImpl constructor = node.declaredElement;
@@ -4505,16 +4471,15 @@
   @override
   void visitFunctionDeclaration(FunctionDeclaration node) {
     ExecutableElement outerFunction = _enclosingFunction;
-    FunctionBody outerFunctionBody = _currentFunctionBody;
     try {
       SimpleIdentifier functionName = node.name;
-      _currentFunctionBody = node.functionExpression.body;
+      _promoteManager.enterFunctionBody(node.functionExpression.body);
       _enclosingFunction = functionName.staticElement as ExecutableElement;
       InferenceContext.setType(
           node.functionExpression, _enclosingFunction.type);
       super.visitFunctionDeclaration(node);
     } finally {
-      _currentFunctionBody = outerFunctionBody;
+      _promoteManager.exitFunctionBody();
       _enclosingFunction = outerFunction;
     }
   }
@@ -4528,9 +4493,8 @@
   @override
   void visitFunctionExpression(FunctionExpression node) {
     ExecutableElement outerFunction = _enclosingFunction;
-    FunctionBody outerFunctionBody = _currentFunctionBody;
     try {
-      _currentFunctionBody = node.body;
+      _promoteManager.enterFunctionBody(node.body);
       _enclosingFunction = node.declaredElement;
       DartType functionType = InferenceContext.getContext(node);
       if (functionType is FunctionType) {
@@ -4544,7 +4508,7 @@
       }
       super.visitFunctionExpression(node);
     } finally {
-      _currentFunctionBody = outerFunctionBody;
+      _promoteManager.exitFunctionBody();
       _enclosingFunction = outerFunction;
     }
   }
@@ -4592,22 +4556,16 @@
   void visitIfElement(IfElement node) {
     Expression condition = node.condition;
     InferenceContext.setType(condition, typeProvider.boolType);
+    // TODO(scheglov) Do we need these checks for null?
     condition?.accept(this);
     CollectionElement thenElement = node.thenElement;
-    if (thenElement != null) {
-      _promoteManager.enterScope();
-      try {
-        // Type promotion.
-        _promoteTypes(condition);
-        _clearTypePromotionsIfPotentiallyMutatedIn(thenElement);
-        _clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(
-            thenElement);
-        // Visit "then".
+    _promoteManager.visitIfElement_thenElement(
+      condition,
+      thenElement,
+      () {
         thenElement.accept(this);
-      } finally {
-        _promoteManager.exitScope();
-      }
-    }
+      },
+    );
     node.elseElement?.accept(this);
 
     node.accept(elementResolver);
@@ -4620,20 +4578,13 @@
     InferenceContext.setType(condition, typeProvider.boolType);
     condition?.accept(this);
     Statement thenStatement = node.thenStatement;
-    if (thenStatement != null) {
-      _promoteManager.enterScope();
-      try {
-        // Type promotion.
-        _promoteTypes(condition);
-        _clearTypePromotionsIfPotentiallyMutatedIn(thenStatement);
-        _clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(
-            thenStatement);
-        // Visit "then".
+    _promoteManager.visitIfStatement_thenStatement(
+      condition,
+      thenStatement,
+      () {
         visitStatementInScope(thenStatement);
-      } finally {
-        _promoteManager.exitScope();
-      }
-    }
+      },
+    );
     Statement elseStatement = node.elseStatement;
     if (elseStatement != null) {
       visitStatementInScope(elseStatement);
@@ -4701,16 +4652,15 @@
   @override
   void visitMethodDeclaration(MethodDeclaration node) {
     ExecutableElement outerFunction = _enclosingFunction;
-    FunctionBody outerFunctionBody = _currentFunctionBody;
     try {
-      _currentFunctionBody = node.body;
+      _promoteManager.enterFunctionBody(node.body);
       _enclosingFunction = node.declaredElement;
       DartType returnType =
           _computeReturnOrYieldType(_enclosingFunction.type?.returnType);
       InferenceContext.setType(node.body, returnType);
       super.visitMethodDeclaration(node);
     } finally {
-      _currentFunctionBody = outerFunctionBody;
+      _promoteManager.exitFunctionBody();
       _enclosingFunction = outerFunction;
     }
   }
@@ -5021,36 +4971,6 @@
     }
   }
 
-  /// Checks each promoted variable in the current scope for compliance with the
-  /// following specification statement:
-  ///
-  /// If the variable <i>v</i> is accessed by a closure in <i>s<sub>1</sub></i>
-  /// then the variable <i>v</i> is not potentially mutated anywhere in the
-  /// scope of <i>v</i>.
-  void _clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(
-      AstNode target) {
-    for (Element element in _promoteManager.promotedElements) {
-      if (_currentFunctionBody.isPotentiallyMutatedInScope(element)) {
-        if (_isVariableAccessedInClosure(element, target)) {
-          _promoteManager.setType(element, null);
-        }
-      }
-    }
-  }
-
-  /// Checks each promoted variable in the current scope for compliance with the
-  /// following specification statement:
-  ///
-  /// <i>v</i> is not potentially mutated in <i>s<sub>1</sub></i> or within a
-  /// closure.
-  void _clearTypePromotionsIfPotentiallyMutatedIn(AstNode target) {
-    for (Element element in _promoteManager.promotedElements) {
-      if (_isVariablePotentiallyMutatedIn(element, target)) {
-        _promoteManager.setType(element, null);
-      }
-    }
-  }
-
   /// Given the declared return type of a function, compute the type of the
   /// values which should be returned or yielded as appropriate.  If a type
   /// cannot be computed from the declared return type, return null.
@@ -5333,86 +5253,6 @@
     }
   }
 
-  /// Return `true` if the given variable is accessed within a closure in the
-  /// given [AstNode] and also mutated somewhere in variable scope. This
-  /// information is only available for local variables (including parameters).
-  ///
-  /// @param variable the variable to check
-  /// @param target the [AstNode] to check within
-  /// @return `true` if this variable is potentially mutated somewhere in the
-  ///         given ASTNode
-  bool _isVariableAccessedInClosure(Element variable, AstNode target) {
-    _ResolverVisitor_isVariableAccessedInClosure visitor =
-        new _ResolverVisitor_isVariableAccessedInClosure(variable);
-    target.accept(visitor);
-    return visitor.result;
-  }
-
-  /// Return `true` if the given variable is potentially mutated somewhere in
-  /// the given [AstNode]. This information is only available for local
-  /// variables (including parameters).
-  ///
-  /// @param variable the variable to check
-  /// @param target the [AstNode] to check within
-  /// @return `true` if this variable is potentially mutated somewhere in the
-  ///         given ASTNode
-  bool _isVariablePotentiallyMutatedIn(Element variable, AstNode target) {
-    _ResolverVisitor_isVariablePotentiallyMutatedIn visitor =
-        new _ResolverVisitor_isVariablePotentiallyMutatedIn(variable);
-    target.accept(visitor);
-    return visitor.result;
-  }
-
-  /// If it is appropriate to do so, promotes the current type of the static
-  /// element associated with the given expression with the given type.
-  /// Generally speaking, it is appropriate if the given type is more specific
-  /// than the current type.
-  ///
-  /// @param expression the expression used to access the static element whose
-  ///        types might be promoted
-  /// @param potentialType the potential type of the elements
-  void _promote(Expression expression, DartType potentialType) {
-    VariableElement element = getPromotionStaticElement(expression);
-    if (element != null) {
-      // may be mutated somewhere in closure
-      if (_currentFunctionBody.isPotentiallyMutatedInClosure(element)) {
-        return;
-      }
-      // prepare current variable type
-      DartType type = _promoteManager.getType(element) ??
-          expression.staticType ??
-          DynamicTypeImpl.instance;
-
-      potentialType ??= DynamicTypeImpl.instance;
-
-      // Check if we can promote to potentialType from type.
-      DartType promoteType = typeSystem.tryPromoteToType(potentialType, type);
-      if (promoteType != null) {
-        // Do promote type of variable.
-        _promoteManager.setType(element, promoteType);
-      }
-    }
-  }
-
-  /// Promotes type information using given condition.
-  void _promoteTypes(Expression condition) {
-    if (condition is BinaryExpression) {
-      if (condition.operator.type == TokenType.AMPERSAND_AMPERSAND) {
-        Expression left = condition.leftOperand;
-        Expression right = condition.rightOperand;
-        _promoteTypes(left);
-        _promoteTypes(right);
-        _clearTypePromotionsIfPotentiallyMutatedIn(right);
-      }
-    } else if (condition is IsExpression) {
-      if (condition.notOperator == null) {
-        _promote(condition.expression, condition.type.type);
-      }
-    } else if (condition is ParenthesizedExpression) {
-      _promoteTypes(condition.expression);
-    }
-  }
-
   void _pushCollectionTypesDown(CollectionElement element,
       {DartType elementType,
       @required DartType iterableType,
@@ -7110,93 +6950,6 @@
   }
 }
 
-/// Instances of the class `TypePromotionManager` manage the ability to promote
-/// types of local variables and formal parameters from their declared types
-/// based on control flow.
-class TypePromotionManager {
-  /// The current promotion scope, or `null` if no scope has been entered.
-  TypePromotionManager_TypePromoteScope currentScope;
-
-  /// Returns the elements with promoted types.
-  Iterable<Element> get promotedElements => currentScope.promotedElements;
-
-  /// Enter a new promotions scope.
-  void enterScope() {
-    currentScope = new TypePromotionManager_TypePromoteScope(currentScope);
-  }
-
-  /// Exit the current promotion scope.
-  void exitScope() {
-    if (currentScope == null) {
-      throw new StateError("No scope to exit");
-    }
-    currentScope = currentScope._outerScope;
-  }
-
-  /// Return the static type of the given [variable] - declared or promoted.
-  DartType getStaticType(VariableElement variable) =>
-      getType(variable) ?? variable.type;
-
-  /// Return the promoted type of the given [element], or `null` if the type of
-  /// the element has not been promoted.
-  DartType getType(Element element) => currentScope?.getType(element);
-
-  /// Set the promoted type of the given element to the given type.
-  ///
-  /// @param element the element whose type might have been promoted
-  /// @param type the promoted type of the given element
-  void setType(Element element, DartType type) {
-    if (currentScope == null) {
-      throw new StateError("Cannot promote without a scope");
-    }
-    currentScope.setType(element, type);
-  }
-}
-
-/// Instances of the class `TypePromoteScope` represent a scope in which the
-/// types of elements can be promoted.
-class TypePromotionManager_TypePromoteScope {
-  /// The outer scope in which types might be promoter.
-  final TypePromotionManager_TypePromoteScope _outerScope;
-
-  /// A table mapping elements to the promoted type of that element.
-  Map<Element, DartType> _promotedTypes = new HashMap<Element, DartType>();
-
-  /// Initialize a newly created scope to be an empty child of the given scope.
-  ///
-  /// @param outerScope the outer scope in which types might be promoted
-  TypePromotionManager_TypePromoteScope(this._outerScope);
-
-  /// Returns the elements with promoted types.
-  Iterable<Element> get promotedElements => _promotedTypes.keys.toSet();
-
-  /// Return the promoted type of the given element, or `null` if the type of
-  /// the element has not been promoted.
-  ///
-  /// @param element the element whose type might have been promoted
-  /// @return the promoted type of the given element
-  DartType getType(Element element) {
-    DartType type = _promotedTypes[element];
-    if (type == null && element is PropertyAccessorElement) {
-      type = _promotedTypes[element.variable];
-    }
-    if (type != null) {
-      return type;
-    } else if (_outerScope != null) {
-      return _outerScope.getType(element);
-    }
-    return null;
-  }
-
-  /// Set the promoted type of the given element to the given type.
-  ///
-  /// @param element the element whose type might have been promoted
-  /// @param type the promoted type of the given element
-  void setType(Element element, DartType type) {
-    _promotedTypes[element] = type;
-  }
-}
-
 /// The interface `TypeProvider` defines the behavior of objects that provide
 /// access to types defined by the language.
 abstract class TypeProvider {
@@ -9076,56 +8829,3 @@
 
 /// The kind of literal to which an unknown literal should be resolved.
 enum _LiteralResolutionKind { ambiguous, map, set }
-
-class _ResolverVisitor_isVariableAccessedInClosure
-    extends RecursiveAstVisitor<void> {
-  final Element variable;
-
-  bool result = false;
-
-  bool _inClosure = false;
-
-  _ResolverVisitor_isVariableAccessedInClosure(this.variable);
-
-  @override
-  void visitFunctionExpression(FunctionExpression node) {
-    bool inClosure = this._inClosure;
-    try {
-      this._inClosure = true;
-      super.visitFunctionExpression(node);
-    } finally {
-      this._inClosure = inClosure;
-    }
-  }
-
-  @override
-  void visitSimpleIdentifier(SimpleIdentifier node) {
-    if (result) {
-      return;
-    }
-    if (_inClosure && identical(node.staticElement, variable)) {
-      result = true;
-    }
-  }
-}
-
-class _ResolverVisitor_isVariablePotentiallyMutatedIn
-    extends RecursiveAstVisitor<void> {
-  final Element variable;
-
-  bool result = false;
-
-  _ResolverVisitor_isVariablePotentiallyMutatedIn(this.variable);
-
-  @override
-  void visitSimpleIdentifier(SimpleIdentifier node) {
-    if (result) {
-      return;
-    }
-    if (identical(node.staticElement, variable)) {
-      if (node.inSetterContext()) {
-        result = true;
-      }
-    }
-  }
-}
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index 0d28aae..efa1dc9 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -19,6 +19,7 @@
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/resolver.dart';
 import 'package:analyzer/src/generated/utilities_dart.dart';
+import 'package:analyzer/src/generated/variable_type_provider.dart';
 import 'package:analyzer/src/task/strong/checker.dart'
     show getExpressionType, getReadType;
 import 'package:meta/meta.dart';
@@ -70,9 +71,9 @@
   InterfaceType thisType;
 
   /**
-   * The object keeping track of which elements have had their types promoted.
+   * The object providing promoted or declared types of variables.
    */
-  TypePromotionManager _promoteManager;
+  LocalVariableTypeProvider _localVariableTypeProvider;
 
   /**
    * Initialize a newly created static type analyzer to analyze types for the
@@ -84,7 +85,7 @@
     _typeProvider = _resolver.typeProvider;
     _typeSystem = _resolver.typeSystem;
     _dynamicType = _typeProvider.dynamicType;
-    _promoteManager = _resolver.promoteManager;
+    _localVariableTypeProvider = _resolver.localVariableTypeProvider;
     AnalysisOptionsImpl analysisOptions =
         _resolver.definingLibrary.context.analysisOptions;
     _strictInference = analysisOptions.strictInference;
@@ -1040,8 +1041,7 @@
     } else if (element is TypeParameterElement) {
       staticType = _nonNullable(_typeProvider.typeType);
     } else if (element is VariableElement) {
-      VariableElement variable = element;
-      staticType = _promoteManager.getStaticType(variable);
+      staticType = _localVariableTypeProvider.getType(node);
     } else if (element is PrefixElement) {
       var parent = node.parent;
       if (parent is PrefixedIdentifier && parent.prefix == node ||
@@ -1294,9 +1294,6 @@
       }
     } else if (element is ExecutableElement) {
       return _computeInvokeReturnType(element.type, isNullableInvoke: false);
-    } else if (element is VariableElement) {
-      DartType variableType = _promoteManager.getStaticType(element);
-      return _computeInvokeReturnType(variableType, isNullableInvoke: false);
     }
     return _dynamicType;
   }
diff --git a/pkg/analyzer/lib/src/generated/type_promotion_manager.dart b/pkg/analyzer/lib/src/generated/type_promotion_manager.dart
new file mode 100644
index 0000000..14f1835
--- /dev/null
+++ b/pkg/analyzer/lib/src/generated/type_promotion_manager.dart
@@ -0,0 +1,395 @@
+// Copyright (c) 2019, 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/dart/element/type.dart';
+import 'package:analyzer/src/dart/element/type.dart';
+import 'package:analyzer/src/generated/resolver.dart';
+import 'package:analyzer/src/generated/variable_type_provider.dart';
+
+/// Instances of the class `TypePromotionManager` manage the ability to promote
+/// types of local variables and formal parameters from their declared types
+/// based on control flow.
+class TypePromotionManager {
+  final TypeSystem _typeSystem;
+
+  /// The current promotion scope, or `null` if no scope has been entered.
+  _TypePromoteScope _currentScope;
+
+  final List<FunctionBody> _functionBodyStack = [];
+
+  /// Body of the function currently being analyzed, if any.
+  FunctionBody _currentFunctionBody;
+
+  TypePromotionManager(this._typeSystem);
+
+  LocalVariableTypeProvider get localVariableTypeProvider {
+    return _LegacyLocalVariableTypeProvider(this);
+  }
+
+  /// Returns the elements with promoted types.
+  Iterable<Element> get _promotedElements {
+    return _currentScope.promotedElements;
+  }
+
+  void enterFunctionBody(FunctionBody body) {
+    _functionBodyStack.add(_currentFunctionBody);
+    _currentFunctionBody = body;
+  }
+
+  void exitFunctionBody() {
+    _currentFunctionBody = _functionBodyStack.removeLast();
+  }
+
+  void visitBinaryExpression_and_rhs(
+      Expression leftOperand, Expression rightOperand, void f()) {
+    if (rightOperand != null) {
+      _enterScope();
+      try {
+        // Type promotion.
+        _promoteTypes(leftOperand);
+        _clearTypePromotionsIfPotentiallyMutatedIn(leftOperand);
+        _clearTypePromotionsIfPotentiallyMutatedIn(rightOperand);
+        _clearTypePromotionsIfAccessedInClosureAndPotentiallyMutated(
+            rightOperand);
+        // Visit right operand.
+        f();
+      } finally {
+        _exitScope();
+      }
+    }
+  }
+
+  void visitConditionalExpression_then(
+      Expression condition, Expression thenExpression, void f()) {
+    if (thenExpression != null) {
+      _enterScope();
+      try {
+        // Type promotion.
+        _promoteTypes(condition);
+        _clearTypePromotionsIfPotentiallyMutatedIn(thenExpression);
+        _clearTypePromotionsIfAccessedInClosureAndPotentiallyMutated(
+          thenExpression,
+        );
+        // Visit "then" expression.
+        f();
+      } finally {
+        _exitScope();
+      }
+    }
+  }
+
+  void visitIfElement_thenElement(
+      Expression condition, CollectionElement thenElement, void f()) {
+    if (thenElement != null) {
+      _enterScope();
+      try {
+        // Type promotion.
+        _promoteTypes(condition);
+        _clearTypePromotionsIfPotentiallyMutatedIn(thenElement);
+        _clearTypePromotionsIfAccessedInClosureAndPotentiallyMutated(
+            thenElement);
+        // Visit "then".
+        f();
+      } finally {
+        _exitScope();
+      }
+    }
+  }
+
+  void visitIfStatement_thenStatement(
+      Expression condition, Statement thenStatement, void f()) {
+    if (thenStatement != null) {
+      _enterScope();
+      try {
+        // Type promotion.
+        _promoteTypes(condition);
+        _clearTypePromotionsIfPotentiallyMutatedIn(thenStatement);
+        _clearTypePromotionsIfAccessedInClosureAndPotentiallyMutated(
+            thenStatement);
+        // Visit "then".
+        f();
+      } finally {
+        _exitScope();
+      }
+    }
+  }
+
+  /// Checks each promoted variable in the current scope for compliance with the
+  /// following specification statement:
+  ///
+  /// If the variable <i>v</i> is accessed by a closure in <i>s<sub>1</sub></i>
+  /// then the variable <i>v</i> is not potentially mutated anywhere in the
+  /// scope of <i>v</i>.
+  void _clearTypePromotionsIfAccessedInClosureAndPotentiallyMutated(
+      AstNode target) {
+    for (Element element in _promotedElements) {
+      if (_currentFunctionBody.isPotentiallyMutatedInScope(element)) {
+        if (_isVariableAccessedInClosure(element, target)) {
+          _setType(element, null);
+        }
+      }
+    }
+  }
+
+  /// Checks each promoted variable in the current scope for compliance with the
+  /// following specification statement:
+  ///
+  /// <i>v</i> is not potentially mutated in <i>s<sub>1</sub></i> or within a
+  /// closure.
+  void _clearTypePromotionsIfPotentiallyMutatedIn(AstNode target) {
+    for (Element element in _promotedElements) {
+      if (_isVariablePotentiallyMutatedIn(element, target)) {
+        _setType(element, null);
+      }
+    }
+  }
+
+  /// Enter a new promotions scope.
+  void _enterScope() {
+    _currentScope = new _TypePromoteScope(_currentScope);
+  }
+
+  /// Exit the current promotion scope.
+  void _exitScope() {
+    if (_currentScope == null) {
+      throw new StateError("No scope to exit");
+    }
+    _currentScope = _currentScope._outerScope;
+  }
+
+  /// Return the promoted type of the given [element], or `null` if the type of
+  /// the element has not been promoted.
+  DartType _getPromotedType(Element element) {
+    return _currentScope?.getType(element);
+  }
+
+  /// Return the static element associated with the given expression whose type
+  /// can be promoted, or `null` if there is no element whose type can be
+  /// promoted.
+  VariableElement _getPromotionStaticElement(Expression expression) {
+    expression = expression?.unParenthesized;
+    if (expression is SimpleIdentifier) {
+      Element element = expression.staticElement;
+      if (element is VariableElement) {
+        ElementKind kind = element.kind;
+        if (kind == ElementKind.LOCAL_VARIABLE ||
+            kind == ElementKind.PARAMETER) {
+          return element;
+        }
+      }
+    }
+    return null;
+  }
+
+  /// Given that the [node] is a reference to a [VariableElement], return the
+  /// static type of the variable at this node - declared or promoted.
+  DartType _getType(SimpleIdentifier node) {
+    var variable = node.staticElement as VariableElement;
+    return _getPromotedType(variable) ?? variable.type;
+  }
+
+  /// Return `true` if the given variable is accessed within a closure in the
+  /// given [AstNode] and also mutated somewhere in variable scope. This
+  /// information is only available for local variables (including parameters).
+  ///
+  /// @param variable the variable to check
+  /// @param target the [AstNode] to check within
+  /// @return `true` if this variable is potentially mutated somewhere in the
+  ///         given ASTNode
+  bool _isVariableAccessedInClosure(Element variable, AstNode target) {
+    _ResolverVisitor_isVariableAccessedInClosure visitor =
+        new _ResolverVisitor_isVariableAccessedInClosure(variable);
+    target.accept(visitor);
+    return visitor.result;
+  }
+
+  /// Return `true` if the given variable is potentially mutated somewhere in
+  /// the given [AstNode]. This information is only available for local
+  /// variables (including parameters).
+  ///
+  /// @param variable the variable to check
+  /// @param target the [AstNode] to check within
+  /// @return `true` if this variable is potentially mutated somewhere in the
+  ///         given ASTNode
+  bool _isVariablePotentiallyMutatedIn(Element variable, AstNode target) {
+    _ResolverVisitor_isVariablePotentiallyMutatedIn visitor =
+        new _ResolverVisitor_isVariablePotentiallyMutatedIn(variable);
+    target.accept(visitor);
+    return visitor.result;
+  }
+
+  /// If it is appropriate to do so, promotes the current type of the static
+  /// element associated with the given expression with the given type.
+  /// Generally speaking, it is appropriate if the given type is more specific
+  /// than the current type.
+  ///
+  /// @param expression the expression used to access the static element whose
+  ///        types might be promoted
+  /// @param potentialType the potential type of the elements
+  void _promote(Expression expression, DartType potentialType) {
+    VariableElement element = _getPromotionStaticElement(expression);
+    if (element != null) {
+      // may be mutated somewhere in closure
+      if (_currentFunctionBody.isPotentiallyMutatedInClosure(element)) {
+        return;
+      }
+      // prepare current variable type
+      DartType type = _getPromotedType(element) ??
+          expression.staticType ??
+          DynamicTypeImpl.instance;
+
+      potentialType ??= DynamicTypeImpl.instance;
+
+      // Check if we can promote to potentialType from type.
+      DartType promoteType = _typeSystem.tryPromoteToType(potentialType, type);
+      if (promoteType != null) {
+        // Do promote type of variable.
+        _setType(element, promoteType);
+      }
+    }
+  }
+
+  /// Promotes type information using given condition.
+  void _promoteTypes(Expression condition) {
+    if (condition is BinaryExpression) {
+      if (condition.operator.type == TokenType.AMPERSAND_AMPERSAND) {
+        Expression left = condition.leftOperand;
+        Expression right = condition.rightOperand;
+        _promoteTypes(left);
+        _promoteTypes(right);
+        _clearTypePromotionsIfPotentiallyMutatedIn(right);
+      }
+    } else if (condition is IsExpression) {
+      if (condition.notOperator == null) {
+        _promote(condition.expression, condition.type.type);
+      }
+    } else if (condition is ParenthesizedExpression) {
+      _promoteTypes(condition.expression);
+    }
+  }
+
+  /// Set the promoted type of the given element to the given type.
+  ///
+  /// @param element the element whose type might have been promoted
+  /// @param type the promoted type of the given element
+  void _setType(Element element, DartType type) {
+    if (_currentScope == null) {
+      throw new StateError("Cannot promote without a scope");
+    }
+    _currentScope.setType(element, type);
+  }
+}
+
+/// The legacy, pre-NNBD implementation of [LocalVariableTypeProvider].
+class _LegacyLocalVariableTypeProvider implements LocalVariableTypeProvider {
+  final TypePromotionManager _manager;
+
+  _LegacyLocalVariableTypeProvider(this._manager);
+
+  @override
+  DartType getType(SimpleIdentifier node) {
+    return _manager._getType(node);
+  }
+}
+
+class _ResolverVisitor_isVariableAccessedInClosure
+    extends RecursiveAstVisitor<void> {
+  final Element variable;
+
+  bool result = false;
+
+  bool _inClosure = false;
+
+  _ResolverVisitor_isVariableAccessedInClosure(this.variable);
+
+  @override
+  void visitFunctionExpression(FunctionExpression node) {
+    bool inClosure = this._inClosure;
+    try {
+      this._inClosure = true;
+      super.visitFunctionExpression(node);
+    } finally {
+      this._inClosure = inClosure;
+    }
+  }
+
+  @override
+  void visitSimpleIdentifier(SimpleIdentifier node) {
+    if (result) {
+      return;
+    }
+    if (_inClosure && identical(node.staticElement, variable)) {
+      result = true;
+    }
+  }
+}
+
+class _ResolverVisitor_isVariablePotentiallyMutatedIn
+    extends RecursiveAstVisitor<void> {
+  final Element variable;
+
+  bool result = false;
+
+  _ResolverVisitor_isVariablePotentiallyMutatedIn(this.variable);
+
+  @override
+  void visitSimpleIdentifier(SimpleIdentifier node) {
+    if (result) {
+      return;
+    }
+    if (identical(node.staticElement, variable)) {
+      if (node.inSetterContext()) {
+        result = true;
+      }
+    }
+  }
+}
+
+/// Instances of the class `TypePromoteScope` represent a scope in which the
+/// types of elements can be promoted.
+class _TypePromoteScope {
+  /// The outer scope in which types might be promoter.
+  final _TypePromoteScope _outerScope;
+
+  /// A table mapping elements to the promoted type of that element.
+  Map<Element, DartType> _promotedTypes = {};
+
+  /// Initialize a newly created scope to be an empty child of the given scope.
+  ///
+  /// @param outerScope the outer scope in which types might be promoted
+  _TypePromoteScope(this._outerScope);
+
+  /// Returns the elements with promoted types.
+  Iterable<Element> get promotedElements => _promotedTypes.keys.toSet();
+
+  /// Return the promoted type of the given element, or `null` if the type of
+  /// the element has not been promoted.
+  ///
+  /// @param element the element whose type might have been promoted
+  /// @return the promoted type of the given element
+  DartType getType(Element element) {
+    DartType type = _promotedTypes[element];
+    if (type == null && element is PropertyAccessorElement) {
+      type = _promotedTypes[element.variable];
+    }
+    if (type != null) {
+      return type;
+    } else if (_outerScope != null) {
+      return _outerScope.getType(element);
+    }
+    return null;
+  }
+
+  /// Set the promoted type of the given element to the given type.
+  ///
+  /// @param element the element whose type might have been promoted
+  /// @param type the promoted type of the given element
+  void setType(Element element, DartType type) {
+    _promotedTypes[element] = type;
+  }
+}
diff --git a/pkg/analyzer/lib/src/generated/variable_type_provider.dart b/pkg/analyzer/lib/src/generated/variable_type_provider.dart
new file mode 100644
index 0000000..376d102
--- /dev/null
+++ b/pkg/analyzer/lib/src/generated/variable_type_provider.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2019, 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/type.dart';
+
+/// Provider of types for local variables and formal parameters.
+abstract class LocalVariableTypeProvider {
+  /// Given that the [node] is a reference to a local variable, or a parameter,
+  /// return the type of the variable at the node - declared or promoted.
+  DartType getType(SimpleIdentifier node);
+}