Version 2.12.0-156.0.dev

Merge commit 'c272975f92acf954b546caf38ac7fe68a7fa90f1' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index 1a1df17..6239f0b 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -651,6 +651,12 @@
   /// is currently promoted.  Otherwise returns `null`.
   Type? promotedType(Variable variable);
 
+  /// Retrieves the SSA node associated with [variable], or `null` if [variable]
+  /// is not associated with an SSA node because it is write captured.  For
+  /// testing only.
+  @visibleForTesting
+  SsaNode? ssaNodeForTesting(Variable variable);
+
   /// Call this method just before visiting one of the cases in the body of a
   /// switch statement.  See [switchStatement_expressionEnd] for details.
   ///
@@ -1139,6 +1145,13 @@
   }
 
   @override
+  SsaNode? ssaNodeForTesting(Variable variable) {
+    return _wrap('ssaNodeForTesting($variable)',
+        () => _wrapped.ssaNodeForTesting(variable),
+        isQuery: true);
+  }
+
+  @override
   void switchStatement_beginCase(bool hasLabel, Node node) {
     _wrap('switchStatement_beginCase($hasLabel, $node)',
         () => _wrapped.switchStatement_beginCase(hasLabel, node));
@@ -1354,7 +1367,7 @@
         (newVariableInfo ??=
             new Map<Variable, VariableModel<Variable, Type>>.from(
                 variableInfo))[variable] = new VariableModel<Variable, Type>(
-            null, const [], false, false, true);
+            null, const [], false, false, null);
       } else if (!info.writeCaptured) {
         (newVariableInfo ??=
             new Map<Variable, VariableModel<Variable, Type>>.from(
@@ -1685,7 +1698,7 @@
         : _updateVariableInfo(
             variable,
             new VariableModel<Variable, Type>(newPromotedTypes, newTested,
-                info.assigned, info.unassigned, info.writeCaptured),
+                info.assigned, info.unassigned, info.ssaNode),
             reachable: newReachable);
   }
 
@@ -1949,6 +1962,29 @@
   }
 }
 
+/// Data structure representing a unique value that a variable might take on
+/// during execution of the code being analyzed.  SSA nodes are immutable (so
+/// they can be safety shared among data structures) and have identity (so that
+/// it is possible to tell whether one SSA node is the same as another).
+///
+/// This is similar to the nodes used in traditional single assignment analysis
+/// (https://en.wikipedia.org/wiki/Static_single_assignment_form) except that it
+/// does not store a complete IR of the code being analyzed.
+@visibleForTesting
+class SsaNode {
+  /// Expando mapping SSA nodes to debug ids.  Only used by `toString`.
+  static final Expando<int> _debugIds = new Expando<int>();
+
+  static int _nextDebugId = 0;
+
+  @override
+  String toString() {
+    SsaNode self = this; // Work around #44475
+    int id = _debugIds[self] ??= _nextDebugId++;
+    return 'ssa$id';
+  }
+}
+
 /// Enum representing the different classifications of types that can be
 /// returned by [TypeOperations.classifyType].
 enum TypeClassification {
@@ -2043,11 +2079,17 @@
   /// Indicates whether the variable is unassigned.
   final bool unassigned;
 
-  /// Indicates whether the variable has been write captured.
-  final bool writeCaptured;
+  /// SSA node associated with this variable.  Every time the variable's value
+  /// potentially changes (either through an explicit write or a join with a
+  /// control flow path that contains a write), this field is updated to point
+  /// to a fresh node.  Thus, it can be used to detect whether a variable's
+  /// value has changed since a time in the past.
+  ///
+  /// `null` if the variable has been write captured.
+  final SsaNode? ssaNode;
 
   VariableModel(this.promotedTypes, this.tested, this.assigned, this.unassigned,
-      this.writeCaptured) {
+      this.ssaNode) {
     assert(!(assigned && unassigned),
         "Can't be both definitely assigned and unassigned");
     assert(promotedTypes == null || promotedTypes!.isNotEmpty);
@@ -2065,16 +2107,19 @@
       : promotedTypes = null,
         tested = const [],
         unassigned = !assigned,
-        writeCaptured = false;
+        ssaNode = new SsaNode();
+
+  /// Indicates whether the variable has been write captured.
+  bool get writeCaptured => ssaNode == null;
 
   /// Returns a new [VariableModel] in which any promotions present have been
   /// dropped, and the variable has been marked as "not unassigned".
+  ///
+  /// Used by [conservativeJoin] to update the state of variables at the top of
+  /// loops whose bodies write to them.
   VariableModel<Variable, Type> discardPromotionsAndMarkNotUnassigned() {
-    if (promotedTypes == null && !unassigned) {
-      return this;
-    }
     return new VariableModel<Variable, Type>(
-        null, tested, assigned, false, writeCaptured);
+        null, tested, assigned, false, writeCaptured ? null : new SsaNode());
   }
 
   /// Returns an updated model reflect a control path that is known to have
@@ -2141,7 +2186,7 @@
       }
     }
     return _identicalOrNew(this, otherModel, newPromotedTypes, tested,
-        newAssigned, newUnassigned, newWriteCaptured);
+        newAssigned, newUnassigned, newWriteCaptured ? null : ssaNode);
   }
 
   @override
@@ -2171,7 +2216,7 @@
       TypeOperations<Variable, Type> typeOperations) {
     if (writeCaptured) {
       return new VariableModel<Variable, Type>(
-          promotedTypes, tested, true, false, writeCaptured);
+          promotedTypes, tested, true, false, null);
     }
 
     List<Type>? newPromotedTypes = _demoteViaAssignment(
@@ -2182,7 +2227,10 @@
     Type declaredType = typeOperations.variableType(variable);
     newPromotedTypes = _tryPromoteToTypeOfInterest(
         typeOperations, declaredType, newPromotedTypes, writtenType);
-    if (identical(promotedTypes, newPromotedTypes) && assigned) return this;
+    if (identical(promotedTypes, newPromotedTypes) && assigned) {
+      return new VariableModel<Variable, Type>(
+          promotedTypes, tested, assigned, unassigned, new SsaNode());
+    }
 
     List<Type> newTested;
     if (newPromotedTypes == null && promotedTypes != null) {
@@ -2192,14 +2240,14 @@
     }
 
     return new VariableModel<Variable, Type>(
-        newPromotedTypes, newTested, true, false, writeCaptured);
+        newPromotedTypes, newTested, true, false, new SsaNode());
   }
 
   /// Returns a new [VariableModel] reflecting the fact that the variable has
   /// been write-captured.
   VariableModel<Variable, Type> writeCapture() {
     return new VariableModel<Variable, Type>(
-        null, const [], assigned, false, true);
+        null, const [], assigned, false, null);
   }
 
   List<Type>? _demoteViaAssignment(
@@ -2356,7 +2404,7 @@
     List<Type> newTested = joinTested(tested, model.tested, typeOperations);
     if (identical(newTested, model.tested)) return model;
     return new VariableModel<Variable, Type>(model.promotedTypes, newTested,
-        model.assigned, model.unassigned, model.writeCaptured);
+        model.assigned, model.unassigned, model.ssaNode);
   }
 
   /// Joins two variable models.  See [FlowModel.join] for details.
@@ -2375,8 +2423,13 @@
     List<Type> newTested = newWriteCaptured
         ? const []
         : joinTested(first.tested, second.tested, typeOperations);
+    SsaNode? newSsaNode = newWriteCaptured
+        ? null
+        : first.ssaNode == second.ssaNode
+            ? first.ssaNode
+            : new SsaNode();
     return _identicalOrNew(first, second, newPromotedTypes, newTested,
-        newAssigned, newUnassigned, newWriteCaptured);
+        newAssigned, newUnassigned, newWriteCaptured ? null : newSsaNode);
   }
 
   /// Performs the portion of the "join" algorithm that applies to promotion
@@ -2487,22 +2540,22 @@
           List<Type> newTested,
           bool newAssigned,
           bool newUnassigned,
-          bool newWriteCaptured) {
+          SsaNode? newSsaNode) {
     if (identical(first.promotedTypes, newPromotedTypes) &&
         identical(first.tested, newTested) &&
         first.assigned == newAssigned &&
         first.unassigned == newUnassigned &&
-        first.writeCaptured == newWriteCaptured) {
+        first.ssaNode == newSsaNode) {
       return first;
     } else if (identical(second.promotedTypes, newPromotedTypes) &&
         identical(second.tested, newTested) &&
         second.assigned == newAssigned &&
         second.unassigned == newUnassigned &&
-        second.writeCaptured == newWriteCaptured) {
+        second.ssaNode == newSsaNode) {
       return second;
     } else {
-      return new VariableModel<Variable, Type>(newPromotedTypes, newTested,
-          newAssigned, newUnassigned, newWriteCaptured);
+      return new VariableModel<Variable, Type>(
+          newPromotedTypes, newTested, newAssigned, newUnassigned, newSsaNode);
     }
   }
 
@@ -3138,6 +3191,10 @@
   }
 
   @override
+  SsaNode? ssaNodeForTesting(Variable variable) =>
+      _current.variableInfo[variable]?.ssaNode;
+
+  @override
   void switchStatement_beginCase(bool hasLabel, Node node) {
     AssignedVariablesNodeInfo<Variable> info =
         _assignedVariables._getInfoForNode(node);
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
index 70f50e4..ac6b69b 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
@@ -67,8 +67,14 @@
 Statement continue_(BranchTargetPlaceholder branchTargetPlaceholder) =>
     new _Continue(branchTargetPlaceholder);
 
-Statement declare(Var variable, {required bool initialized}) =>
-    new _Declare(variable, initialized);
+Statement declare(Var variable,
+        {required bool initialized, bool isFinal = false}) =>
+    new _Declare(
+        variable, initialized ? expr(variable.type.type) : null, isFinal);
+
+Statement declareInitialized(Var variable, Expression initializer,
+        {bool isFinal = false}) =>
+    new _Declare(variable, initializer, isFinal);
 
 Statement do_(List<Statement> body, Expression condition) =>
     _Do(body, condition);
@@ -126,6 +132,12 @@
   return new _ForEach(variable, iterable, body, false);
 }
 
+/// Creates a [Statement] that, when analyzed, will cause [callback] to be
+/// passed an [SsaNodeHarness] allowing the test to examine the values of
+/// variables' SSA nodes.
+Statement getSsaNodes(void Function(SsaNodeHarness) callback) =>
+    new _GetSsaNodes(callback);
+
 Statement if_(Expression condition, List<Statement> ifTrue,
         [List<Statement>? ifFalse]) =>
     new _If(condition, ifTrue, ifFalse);
@@ -258,6 +270,8 @@
 /// needed for testing.
 class Harness extends TypeOperations<Var, Type> {
   static const Map<String, bool> _coreSubtypes = const {
+    'bool <: int': false,
+    'bool <: Object': true,
     'double <: Object': true,
     'double <: num': true,
     'double <: num?': true,
@@ -326,6 +340,7 @@
     'String <: int': false,
     'String <: int?': false,
     'String <: num?': false,
+    'String <: Object': true,
     'String <: Object?': true,
   };
 
@@ -335,7 +350,9 @@
     'Object? - num?': Type('Object'),
     'Object? - Object?': Type('Never?'),
     'Object? - String': Type('Object?'),
+    'Object - bool': Type('Object'),
     'Object - int': Type('Object'),
+    'Object - String': Type('Object'),
     'int - Object': Type('Never'),
     'int - String': Type('int'),
     'int - int': Type('Never'),
@@ -489,6 +506,17 @@
   Node._();
 }
 
+/// Helper class allowing tests to examine the values of variables' SSA nodes.
+class SsaNodeHarness {
+  final FlowAnalysis<Node, Statement, Expression, Var, Type> _flow;
+
+  SsaNodeHarness(this._flow);
+
+  /// Gets the SSA node associated with [variable] at the current point in
+  /// control flow, or `null` if the variable has been write captured.
+  SsaNode? operator [](Var variable) => _flow.ssaNodeForTesting(variable);
+}
+
 /// Representation of a statement in the pseudo-Dart language used for flow
 /// analysis testing.
 abstract class Statement extends Node implements _Visitable<void> {
@@ -812,20 +840,33 @@
 
 class _Declare extends Statement {
   final Var variable;
-  final bool initialized;
+  final Expression? initializer;
+  final bool isFinal;
 
-  _Declare(this.variable, this.initialized) : super._();
+  _Declare(this.variable, this.initializer, this.isFinal) : super._();
 
   @override
-  String toString() => '$variable${initialized ? ' = ...' : ''};';
+  String toString() {
+    var finalPart = isFinal ? 'final ' : '';
+    var initializerPart = initializer != null ? ' = $initializer' : '';
+    return '$finalPart$variable${initializerPart};';
+  }
 
   @override
-  void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
+  void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
+    initializer?._preVisit(assignedVariables);
+  }
 
   @override
   void _visit(
       Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
-    flow.declare(variable, initialized);
+    var initializer = this.initializer;
+    if (initializer == null) {
+      flow.declare(variable, false);
+    } else {
+      initializer._visit(h, flow);
+      flow.declare(variable, true);
+    }
   }
 }
 
@@ -1006,6 +1047,21 @@
   }
 }
 
+class _GetSsaNodes extends Statement {
+  final void Function(SsaNodeHarness) callback;
+
+  _GetSsaNodes(this.callback) : super._();
+
+  @override
+  void _preVisit(AssignedVariables<Node, Var> assignedVariables) {}
+
+  @override
+  void _visit(
+      Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
+    callback(SsaNodeHarness(flow));
+  }
+}
+
 class _If extends Statement {
   final Expression condition;
   final List<Statement> ifTrue;
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
index 35f7d10..f9dfea6 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
@@ -12,10 +12,13 @@
     test('asExpression_end promotes variables', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforePromotion;
       h.run([
         declare(x, initialized: true),
+        getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
         x.read.as_('int').stmt,
         checkPromoted(x, 'int'),
+        getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
       ]);
     });
 
@@ -164,17 +167,33 @@
       ]);
     });
 
+    test('declare() sets Ssa', () {
+      var h = Harness();
+      var x = Var('x', 'Object');
+      var y = Var('y', 'int?');
+      h.run([
+        declareInitialized(x, y.read.eq(nullLiteral)),
+        getSsaNodes((nodes) {
+          expect(nodes[x], isNotNull);
+        }),
+      ]);
+    });
+
     test('equalityOp(x != null) promotes true branch', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforePromotion;
       h.run([
         declare(x, initialized: true),
+        getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
         if_(x.read.notEq(nullLiteral), [
           checkReachable(true),
           checkPromoted(x, 'int'),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ], [
           checkReachable(true),
           checkNotPromoted(x),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ]),
       ]);
     });
@@ -232,14 +251,18 @@
     test('equalityOp(x == null) promotes false branch', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforePromotion;
       h.run([
         declare(x, initialized: true),
+        getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
         if_(x.read.eq(nullLiteral), [
           checkReachable(true),
           checkNotPromoted(x),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ], [
           checkReachable(true),
           checkPromoted(x, 'int'),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ]),
       ]);
     });
@@ -262,12 +285,16 @@
     test('equalityOp(null != x) promotes true branch', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforePromotion;
       h.run([
         declare(x, initialized: true),
+        getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
         if_(nullLiteral.notEq(x.read), [
           checkPromoted(x, 'int'),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ], [
           checkNotPromoted(x),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ]),
       ]);
     });
@@ -288,12 +315,16 @@
     test('equalityOp(null == x) promotes false branch', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforePromotion;
       h.run([
         declare(x, initialized: true),
+        getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
         if_(nullLiteral.eq(x.read), [
           checkNotPromoted(x),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ], [
           checkPromoted(x, 'int'),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ]),
       ]);
     });
@@ -381,14 +412,39 @@
       ]);
     });
 
+    test('declare(initialized: false) assigns new SSA ids', () {
+      var h = Harness();
+      var x = Var('x', 'int?');
+      var y = Var('y', 'int?');
+      h.run([
+        declare(x, initialized: false),
+        declare(y, initialized: false),
+        getSsaNodes((nodes) => expect(nodes[y], isNot(nodes[x]))),
+      ]);
+    });
+
+    test('declare(initialized: true) assigns new SSA ids', () {
+      var h = Harness();
+      var x = Var('x', 'int?');
+      var y = Var('y', 'int?');
+      h.run([
+        declare(x, initialized: true),
+        declare(y, initialized: true),
+        getSsaNodes((nodes) => expect(nodes[y], isNot(nodes[x]))),
+      ]);
+    });
+
     test('doStatement_bodyBegin() un-promotes', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforeLoop;
       h.run([
         declare(x, initialized: true),
         x.read.as_('int').stmt,
         checkPromoted(x, 'int'),
+        getSsaNodes((nodes) => ssaBeforeLoop = nodes[x]!),
         branchTarget((t) => do_([
+              getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeLoop))),
               checkNotPromoted(x),
               x.write(expr('Null')).stmt,
             ], expr('bool'))),
@@ -457,13 +513,22 @@
     test('for_conditionBegin() un-promotes', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforeLoop;
       h.run([
         declare(x, initialized: true),
         x.read.as_('int').stmt,
         checkPromoted(x, 'int'),
-        for_(null, checkNotPromoted(x).thenExpr(expr('bool')), null, [
-          x.write(expr('int?')).stmt,
-        ]),
+        getSsaNodes((nodes) => ssaBeforeLoop = nodes[x]!),
+        for_(
+            null,
+            block([
+              checkNotPromoted(x),
+              getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeLoop))),
+            ]).thenExpr(expr('bool')),
+            null,
+            [
+              x.write(expr('int?')).stmt,
+            ]),
       ]);
     });
 
@@ -587,15 +652,75 @@
       ]);
     });
 
+    test('for_end() with break updates Ssa of modified vars', () {
+      var h = Harness();
+      var x = Var('x', 'int?');
+      var y = Var('x', 'int?');
+      late SsaNode xSsaInsideLoop;
+      late SsaNode ySsaInsideLoop;
+      h.run([
+        declare(x, initialized: true),
+        declare(y, initialized: true),
+        branchTarget((t) => for_(null, expr('bool'), null, [
+              x.write(expr('int?')).stmt,
+              if_(expr('bool'), [break_(t)]),
+              getSsaNodes((nodes) {
+                xSsaInsideLoop = nodes[x]!;
+                ySsaInsideLoop = nodes[y]!;
+              }),
+            ])),
+        getSsaNodes((nodes) {
+          // x's Ssa should have been changed because of the join at the end of
+          // of the loop.  y's should not, since it retains the value it had
+          // prior to the loop.
+          expect(nodes[x], isNot(xSsaInsideLoop));
+          expect(nodes[y], same(ySsaInsideLoop));
+        }),
+      ]);
+    });
+
+    test(
+        'for_end() with break updates Ssa of modified vars when types were '
+        'tested', () {
+      var h = Harness();
+      var x = Var('x', 'int?');
+      var y = Var('x', 'int?');
+      late SsaNode xSsaInsideLoop;
+      late SsaNode ySsaInsideLoop;
+      h.run([
+        declare(x, initialized: true),
+        declare(y, initialized: true),
+        branchTarget((t) => for_(null, expr('bool'), null, [
+              x.write(expr('int?')).stmt,
+              if_(expr('bool'), [break_(t)]),
+              if_(x.read.is_('int'), []),
+              getSsaNodes((nodes) {
+                xSsaInsideLoop = nodes[x]!;
+                ySsaInsideLoop = nodes[y]!;
+              }),
+            ])),
+        getSsaNodes((nodes) {
+          // x's Ssa should have been changed because of the join at the end of
+          // the loop.  y's should not, since it retains the value it had prior
+          // to the loop.
+          expect(nodes[x], isNot(xSsaInsideLoop));
+          expect(nodes[y], same(ySsaInsideLoop));
+        }),
+      ]);
+    });
+
     test('forEach_bodyBegin() un-promotes', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforeLoop;
       h.run([
         declare(x, initialized: true),
         x.read.as_('int').stmt,
         checkPromoted(x, 'int'),
+        getSsaNodes((nodes) => ssaBeforeLoop = nodes[x]!),
         forEachWithNonVariable(expr('List<int?>'), [
           checkNotPromoted(x),
+          getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeLoop))),
           x.write(expr('int?')).stmt,
         ]),
       ]);
@@ -631,6 +756,20 @@
       ]);
     });
 
+    test('forEach_bodyBegin() does not write capture loop variable', () {
+      var h = Harness();
+      var x = Var('x', 'int?');
+      h.run([
+        declare(x, initialized: false),
+        checkAssigned(x, false),
+        forEachWithVariableSet(x, expr('List<int?>'), [
+          checkAssigned(x, true),
+          if_(x.read.notEq(nullLiteral), [checkPromoted(x, 'int')]),
+        ]),
+        checkAssigned(x, false),
+      ]);
+    });
+
     test('forEach_bodyBegin() pushes conservative join state', () {
       var h = Harness();
       var x = Var('x', 'int');
@@ -675,13 +814,25 @@
         y.read.as_('int').stmt,
         checkPromoted(x, 'int'),
         checkPromoted(y, 'int'),
+        getSsaNodes((nodes) {
+          expect(nodes[x], isNotNull);
+          expect(nodes[y], isNotNull);
+        }),
         localFunction([
           // x is unpromoted within the local function
           checkNotPromoted(x), checkPromoted(y, 'int'),
+          getSsaNodes((nodes) {
+            expect(nodes[x], isNull);
+            expect(nodes[y], isNotNull);
+          }),
           x.write(expr('int?')).stmt, x.read.as_('int').stmt,
         ]),
         // x is unpromoted after the local function too
         checkNotPromoted(x), checkPromoted(y, 'int'),
+        getSsaNodes((nodes) {
+          expect(nodes[x], isNull);
+          expect(nodes[y], isNotNull);
+        }),
       ]);
     });
 
@@ -721,21 +872,28 @@
       var h = Harness();
       var x = Var('x', 'int?');
       var y = Var('y', 'int?');
+      late SsaNode ssaBeforeFunction;
       h.run([
         declare(x, initialized: true), declare(y, initialized: true),
         x.read.as_('int').stmt, y.read.as_('int').stmt,
-        checkPromoted(x, 'int'), checkPromoted(y, 'int'),
+        checkPromoted(x, 'int'),
+        getSsaNodes((nodes) => ssaBeforeFunction = nodes[x]!),
+        checkPromoted(y, 'int'),
         localFunction([
           // x is unpromoted within the local function, because the write
           // might have happened by the time the local function executes.
-          checkNotPromoted(x), checkPromoted(y, 'int'),
+          checkNotPromoted(x),
+          getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeFunction))),
+          checkPromoted(y, 'int'),
           // But it can be re-promoted because the write isn't captured.
           x.read.as_('int').stmt,
           checkPromoted(x, 'int'), checkPromoted(y, 'int'),
         ]),
         // x is still promoted after the local function, though, because the
         // write hasn't occurred yet.
-        checkPromoted(x, 'int'), checkPromoted(y, 'int'),
+        checkPromoted(x, 'int'),
+        getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforeFunction))),
+        checkPromoted(y, 'int'),
         x.write(expr('int?')).stmt,
         // x is unpromoted now.
         checkNotPromoted(x), checkPromoted(y, 'int'),
@@ -762,7 +920,9 @@
       h.run([
         declare(y, initialized: true),
         y.read.as_('int').stmt,
+        getSsaNodes((nodes) => expect(nodes[x], null)),
         localFunction([
+          getSsaNodes((nodes) => expect(nodes[x], isNot(nodes[y]))),
           x.read.as_('int').stmt,
           // Promotion should not occur, because x might be write-captured by
           // the time this code is reached.
@@ -967,19 +1127,96 @@
       ]);
     });
 
+    test(
+        'ifStatement_end() discards non-matching expression info from joined '
+        'branches', () {
+      var h = Harness();
+      var w = Var('w', 'Object');
+      var x = Var('x', 'bool');
+      var y = Var('y', 'bool');
+      var z = Var('z', 'bool');
+      late SsaNode xSsaNodeBeforeIf;
+      h.run([
+        declare(w, initialized: true),
+        declare(x, initialized: true),
+        declare(y, initialized: true),
+        declare(z, initialized: true),
+        x.write(w.read.is_('int')).stmt,
+        getSsaNodes((nodes) {
+          xSsaNodeBeforeIf = nodes[x]!;
+        }),
+        if_(expr('bool'), [
+          y.write(w.read.is_('String')).stmt,
+        ], [
+          z.write(w.read.is_('bool')).stmt,
+        ]),
+        getSsaNodes((nodes) {
+          expect(nodes[x], same(xSsaNodeBeforeIf));
+          expect(nodes[y], isNotNull);
+          expect(nodes[z], isNotNull);
+        }),
+      ]);
+    });
+
+    test(
+        'ifStatement_end() ignores non-matching SSA info from "then" path if '
+        'unreachable', () {
+      var h = Harness();
+      var x = Var('x', 'Object');
+      late SsaNode xSsaNodeBeforeIf;
+      h.run([
+        declare(x, initialized: true),
+        getSsaNodes((nodes) {
+          xSsaNodeBeforeIf = nodes[x]!;
+        }),
+        if_(expr('bool'), [
+          x.write(expr('Object')).stmt,
+          return_(),
+        ]),
+        getSsaNodes((nodes) {
+          expect(nodes[x], same(xSsaNodeBeforeIf));
+        }),
+      ]);
+    });
+
+    test(
+        'ifStatement_end() ignores non-matching SSA info from "else" path if '
+        'unreachable', () {
+      var h = Harness();
+      var x = Var('x', 'Object');
+      late SsaNode xSsaNodeBeforeIf;
+      h.run([
+        declare(x, initialized: true),
+        getSsaNodes((nodes) {
+          xSsaNodeBeforeIf = nodes[x]!;
+        }),
+        if_(expr('bool'), [], [
+          x.write(expr('Object')).stmt,
+          return_(),
+        ]),
+        getSsaNodes((nodes) {
+          expect(nodes[x], same(xSsaNodeBeforeIf));
+        }),
+      ]);
+    });
+
     void _checkIs(String declaredType, String tryPromoteType,
         String? expectedPromotedTypeThen, String? expectedPromotedTypeElse,
         {bool inverted = false}) {
       var h = Harness();
       var x = Var('x', declaredType);
+      late SsaNode ssaBeforePromotion;
       h.run([
         declare(x, initialized: true),
+        getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
         if_(x.read.is_(tryPromoteType, isInverted: inverted), [
           checkReachable(true),
           checkPromoted(x, expectedPromotedTypeThen),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ], [
           checkReachable(true),
           checkPromoted(x, expectedPromotedTypeElse),
+          getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
         ])
       ]);
     }
@@ -1176,25 +1413,33 @@
     test('nonNullAssert_end(x) promotes', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforePromotion;
       h.run([
         declare(x, initialized: true),
+        getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
         x.read.nonNullAssert.stmt,
         checkPromoted(x, 'int'),
+        getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
       ]);
     });
 
     test('nullAwareAccess temporarily promotes', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforePromotion;
       h.run([
         declare(x, initialized: true),
+        getSsaNodes((nodes) => ssaBeforePromotion = nodes[x]!),
         x.read
             .nullAwareAccess(block([
               checkReachable(true),
               checkPromoted(x, 'int'),
+              getSsaNodes(
+                  (nodes) => expect(nodes[x], same(ssaBeforePromotion))),
             ]).thenExpr(expr('Null')))
             .stmt,
         checkNotPromoted(x),
+        getSsaNodes((nodes) => expect(nodes[x], same(ssaBeforePromotion))),
       ]);
     });
 
@@ -1380,14 +1625,20 @@
     test('switchStatement_beginCase(true) un-promotes', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforeSwitch;
       h.run([
         declare(x, initialized: true),
         x.read.as_('int').stmt,
         switch_(
-            expr('Null'),
+            expr('Null').thenStmt(block([
+              checkPromoted(x, 'int'),
+              getSsaNodes((nodes) => ssaBeforeSwitch = nodes[x]!),
+            ])),
             [
               case_([
                 checkNotPromoted(x),
+                getSsaNodes(
+                    (nodes) => expect(nodes[x], isNot(ssaBeforeSwitch))),
                 x.write(expr('int?')).stmt,
                 checkNotPromoted(x),
               ], hasLabel: true),
@@ -1527,6 +1778,7 @@
         () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaAfterTry;
       h.run([
         declare(x, initialized: true),
         x.read.as_('int').stmt,
@@ -1535,9 +1787,11 @@
           x.write(expr('int?')).stmt,
           x.read.as_('int').stmt,
           checkPromoted(x, 'int'),
+          getSsaNodes((nodes) => ssaAfterTry = nodes[x]!),
         ], [
           catch_(body: [
             checkNotPromoted(x),
+            getSsaNodes((nodes) => expect(nodes[x], isNot(ssaAfterTry))),
           ]),
         ]),
       ]);
@@ -1673,16 +1927,27 @@
         'body', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaAtStartOfTry;
+      late SsaNode ssaAfterTry;
       h.run([
         declare(x, initialized: true),
         x.read.as_('int').stmt,
         checkPromoted(x, 'int'),
         tryFinally([
+          getSsaNodes((nodes) => ssaAtStartOfTry = nodes[x]!),
           x.write(expr('int?')).stmt,
           x.read.as_('int').stmt,
           checkPromoted(x, 'int'),
+          getSsaNodes((nodes) => ssaAfterTry = nodes[x]!),
         ], [
           checkNotPromoted(x),
+          // The SSA node for X should be different from what it was at any time
+          // during the try block, because there is no telling at what point an
+          // exception might have occurred.
+          getSsaNodes((nodes) {
+            expect(nodes[x], isNot(ssaAtStartOfTry));
+            expect(nodes[x], isNot(ssaAfterTry));
+          }),
         ]),
       ]);
     });
@@ -1733,6 +1998,8 @@
       var h = Harness();
       var x = Var('x', 'int?');
       var y = Var('y', 'int?');
+      late SsaNode xSsaAtEndOfFinally;
+      late SsaNode ySsaAtEndOfFinally;
       h.run([
         declare(x, initialized: true), declare(y, initialized: true),
         tryFinally([
@@ -1744,24 +2011,42 @@
           y.write(expr('int?')).stmt,
           y.read.as_('int').stmt,
           checkPromoted(y, 'int'),
+          getSsaNodes((nodes) {
+            xSsaAtEndOfFinally = nodes[x]!;
+            ySsaAtEndOfFinally = nodes[y]!;
+          }),
         ]),
         // x should not be re-promoted, because it might have been assigned a
         // non-promoted value in the "finally" block.  But y's promotion still
         // stands, because y was promoted in the finally block.
         checkNotPromoted(x), checkPromoted(y, 'int'),
+        // Both x and y should have the same SSA nodes they had at the end of
+        // the finally block, since the finally block is guaranteed to have
+        // executed.
+        getSsaNodes((nodes) {
+          expect(nodes[x], same(xSsaAtEndOfFinally));
+          expect(nodes[y], same(ySsaAtEndOfFinally));
+        }),
       ]);
     });
 
     test('whileStatement_conditionBegin() un-promotes', () {
       var h = Harness();
       var x = Var('x', 'int?');
+      late SsaNode ssaBeforeLoop;
       h.run([
         declare(x, initialized: true),
         x.read.as_('int').stmt,
         checkPromoted(x, 'int'),
-        while_(checkNotPromoted(x).thenExpr(expr('bool')), [
-          x.write(expr('Null')).stmt,
-        ]),
+        getSsaNodes((nodes) => ssaBeforeLoop = nodes[x]!),
+        while_(
+            block([
+              checkNotPromoted(x),
+              getSsaNodes((nodes) => expect(nodes[x], isNot(ssaBeforeLoop))),
+            ]).thenExpr(expr('bool')),
+            [
+              x.write(expr('Null')).stmt,
+            ]),
       ]);
     });
 
@@ -1835,6 +2120,98 @@
       ]);
     });
 
+    test('whileStatement_end() with break updates Ssa of modified vars', () {
+      var h = Harness();
+      var x = Var('x', 'int?');
+      var y = Var('x', 'int?');
+      late SsaNode xSsaInsideLoop;
+      late SsaNode ySsaInsideLoop;
+      h.run([
+        declare(x, initialized: true),
+        declare(y, initialized: true),
+        branchTarget((t) => while_(expr('bool'), [
+              x.write(expr('int?')).stmt,
+              if_(expr('bool'), [break_(t)]),
+              getSsaNodes((nodes) {
+                xSsaInsideLoop = nodes[x]!;
+                ySsaInsideLoop = nodes[y]!;
+              }),
+            ])),
+        getSsaNodes((nodes) {
+          // x's Ssa should have been changed because of the join at the end of
+          // the loop.  y's should not, since it retains the value it had prior
+          // to the loop.
+          expect(nodes[x], isNot(xSsaInsideLoop));
+          expect(nodes[y], same(ySsaInsideLoop));
+        }),
+      ]);
+    });
+
+    test(
+        'whileStatement_end() with break updates Ssa of modified vars when '
+        'types were tested', () {
+      var h = Harness();
+      var x = Var('x', 'int?');
+      var y = Var('x', 'int?');
+      late SsaNode xSsaInsideLoop;
+      late SsaNode ySsaInsideLoop;
+      h.run([
+        declare(x, initialized: true),
+        declare(y, initialized: true),
+        branchTarget((t) => while_(expr('bool'), [
+              x.write(expr('int?')).stmt,
+              if_(expr('bool'), [break_(t)]),
+              if_(x.read.is_('int'), []),
+              getSsaNodes((nodes) {
+                xSsaInsideLoop = nodes[x]!;
+                ySsaInsideLoop = nodes[y]!;
+              }),
+            ])),
+        getSsaNodes((nodes) {
+          // x's Ssa should have been changed because of the join at the end of
+          // the loop.  y's should not, since it retains the value it had prior
+          // to the loop.
+          expect(nodes[x], isNot(xSsaInsideLoop));
+          expect(nodes[y], same(ySsaInsideLoop));
+        }),
+      ]);
+    });
+
+    test('write() de-promotes and updates Ssa of a promoted variable', () {
+      var h = Harness();
+      var x = Var('x', 'Object');
+      var y = Var('y', 'int?');
+      late SsaNode ssaBeforeWrite;
+      h.run([
+        declare(x, initialized: true),
+        declare(y, initialized: true),
+        x.read.as_('int').stmt,
+        checkPromoted(x, 'int'),
+        getSsaNodes((nodes) => ssaBeforeWrite = nodes[x]!),
+        x.write(y.read.eq(nullLiteral)).stmt,
+        checkNotPromoted(x),
+        getSsaNodes((nodes) {
+          expect(nodes[x], isNot(ssaBeforeWrite));
+          expect(nodes[x], isNotNull);
+        }),
+      ]);
+    });
+
+    test('write() updates Ssa', () {
+      var h = Harness();
+      var x = Var('x', 'Object');
+      var y = Var('y', 'int?');
+      late SsaNode ssaBeforeWrite;
+      h.run([
+        declare(x, initialized: true),
+        getSsaNodes((nodes) => ssaBeforeWrite = nodes[x]!),
+        x.write(y.read.eq(nullLiteral)).stmt,
+        getSsaNodes((nodes) {
+          expect(nodes[x], isNot(ssaBeforeWrite));
+        }),
+      ]);
+    });
+
     test('Infinite loop does not implicitly assign variables', () {
       var h = Harness();
       var x = Var('x', 'int');
@@ -1868,8 +2245,10 @@
         localFunction([
           x.write(expr('Object')).stmt,
         ]),
+        getSsaNodes((nodes) => expect(nodes[x], isNull)),
         x.read.as_('int').stmt,
         checkNotPromoted(x),
+        getSsaNodes((nodes) => expect(nodes[x], isNull)),
       ]);
     });
 
@@ -2153,7 +2532,15 @@
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(objectQVar, true);
         var s2 = s1.write(objectQVar, Type('Object?'), h);
-        expect(s2, same(s1));
+        expect(s2, isNot(same(s1)));
+        expect(s2.reachable, same(s1.reachable));
+        expect(
+            s2.infoFor(objectQVar),
+            _matchVariableModel(
+                chain: null,
+                ofInterest: isEmpty,
+                assigned: true,
+                unassigned: false));
       });
 
       test('marks as assigned', () {
@@ -2260,7 +2647,14 @@
         });
         var s2 = s1.write(objectQVar, Type('num'), h);
         expect(s2.reachable.overallReachable, true);
-        expect(s2.variableInfo, same(s1.variableInfo));
+        expect(s2.variableInfo, isNot(same(s1.variableInfo)));
+        expect(s2.variableInfo, {
+          objectQVar: _matchVariableModel(
+              chain: ['num?', 'num'],
+              ofInterest: ['num?', 'num'],
+              assigned: true,
+              unassigned: false)
+        });
       });
 
       test('leaves promoted, when writing a subtype', () {
@@ -2280,7 +2674,14 @@
         });
         var s2 = s1.write(objectQVar, Type('int'), h);
         expect(s2.reachable.overallReachable, true);
-        expect(s2.variableInfo, same(s1.variableInfo));
+        expect(s2.variableInfo, isNot(same(s1.variableInfo)));
+        expect(s2.variableInfo, {
+          objectQVar: _matchVariableModel(
+              chain: ['num?', 'num'],
+              ofInterest: ['num?', 'num'],
+              assigned: true,
+              unassigned: false)
+        });
       });
 
       group('Promotes to NonNull of a type of interest', () {
@@ -2545,7 +2946,13 @@
             var s2 = s1.write(objectQVar, Type('int'), h);
             // It's ambiguous whether to promote to num? or num*, so we don't
             // promote.
-            expect(s2, same(s1));
+            expect(s2, isNot(same(s1)));
+            expect(s2.variableInfo, {
+              objectQVar: _matchVariableModel(
+                chain: ['Object'],
+                ofInterest: ['num?', 'num*'],
+              ),
+            });
           });
         });
 
@@ -2682,7 +3089,12 @@
             .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
             .ifTrue;
         var s2 = s1.conservativeJoin([intQVar], []);
-        expect(s2, same(s1));
+        expect(s2, isNot(same(s1)));
+        expect(s2.reachable, same(s1.reachable));
+        expect(s2.variableInfo, {
+          objectQVar: _matchVariableModel(chain: ['int'], ofInterest: ['int']),
+          intQVar: _matchVariableModel(chain: null, ofInterest: [])
+        });
       });
 
       test('written', () {
@@ -3075,12 +3487,11 @@
     VariableModel<Var, Type> model(List<Type>? promotionChain,
             {List<Type>? typesOfInterest, bool assigned = false}) =>
         VariableModel<Var, Type>(
-          promotionChain,
-          typesOfInterest ?? promotionChain ?? [],
-          assigned,
-          !assigned,
-          false,
-        );
+            promotionChain,
+            typesOfInterest ?? promotionChain ?? [],
+            assigned,
+            !assigned,
+            new SsaNode());
 
     group('without input reuse', () {
       test('promoted with unpromoted', () {
@@ -3259,18 +3670,12 @@
 
     VariableModel<Var, Type> varModel(List<Type>? promotionChain,
             {bool assigned = false}) =>
-        VariableModel<Var, Type>(
-          promotionChain,
-          promotionChain ?? [],
-          assigned,
-          !assigned,
-          false,
-        );
+        VariableModel<Var, Type>(promotionChain, promotionChain ?? [], assigned,
+            !assigned, new SsaNode());
 
     test('first is null', () {
       var h = Harness();
-      var s1 = FlowModel.withInfo(
-          Reachability.initial.split(), emptyMap);
+      var s1 = FlowModel.withInfo(Reachability.initial.split(), emptyMap);
       var result = FlowModel.merge(h, null, s1, emptyMap);
       expect(result.reachable, same(Reachability.initial));
     });
@@ -3353,7 +3758,8 @@
     const emptyMap = const <Var, VariableModel<Var, Type>>{};
 
     VariableModel<Var, Type> model(List<Type> typesOfInterest) =>
-        VariableModel<Var, Type>(null, typesOfInterest, true, false, false);
+        VariableModel<Var, Type>(
+            null, typesOfInterest, true, false, new SsaNode());
 
     test('inherits types of interest from other', () {
       var h = Harness();
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index e9b77bf..5ab1bfa 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -1297,6 +1297,14 @@
   var insertText = completion;
   var insertTextFormat = lsp.InsertTextFormat.PlainText;
 
+  // SuggestionBuilder already does the equiv of completeFunctionCalls for
+  // some methods (for example Flutter's setState). If the completion already
+  // includes any `(` then disable our own insertion as the special-cased code
+  // will likely provide better code.
+  if (completion.contains('(')) {
+    completeFunctionCalls = false;
+  }
+
   // If the client supports snippets, we can support completeFunctionCalls or
   // setting a selection.
   if (supportsSnippets) {
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 66ec3ce..2214c75 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -31,6 +31,15 @@
     );
   }
 
+  @override
+  void setUp() {
+    super.setUp();
+    writePackageConfig(
+      projectFolderPath,
+      flutter: true,
+    );
+  }
+
   Future<void> test_commitCharacter_completionItem() async {
     await provideConfig(
       () => initialize(
@@ -124,6 +133,55 @@
     );
   }
 
+  Future<void> test_completeFunctionCalls_flutterSetState() async {
+    // Flutter's setState method has special handling inside SuggestionBuilder
+    // that already adds in a selection (which overlaps with completeFunctionCalls).
+    // Ensure we don't end up with two sets of parens/placeholders in this case.
+    final content = '''
+import 'package:flutter/material.dart';
+
+class MyWidget extends StatefulWidget {
+  @override
+  _MyWidgetState createState() => _MyWidgetState();
+}
+
+class _MyWidgetState extends State<MyWidget> {
+  @override
+  Widget build(BuildContext context) {
+    [[setSt^]]
+    return Container();
+  }
+}
+    ''';
+
+    await provideConfig(
+      () => initialize(
+        textDocumentCapabilities: withCompletionItemSnippetSupport(
+            emptyTextDocumentClientCapabilities),
+        workspaceCapabilities:
+            withConfigurationSupport(emptyWorkspaceClientCapabilities),
+      ),
+      {'completeFunctionCalls': true},
+    );
+    await openFile(mainFileUri, withoutMarkers(content));
+    final res = await getCompletion(mainFileUri, positionFromMarker(content));
+    final item = res.singleWhere((c) => c.label.startsWith('setState('));
+
+    // Usually the label would be "setState(…)" but here it's slightly different
+    // to indicate a full statement is being inserted.
+    expect(item.label, equals('setState(() {});'));
+
+    // Ensure the snippet comes through in the expected format with the expected
+    // placeholders.
+    expect(item.insertTextFormat, equals(InsertTextFormat.Snippet));
+    expect(item.insertText, equals('setState(() {\n      \${0:}\n    \\});'));
+    expect(item.textEdit.newText, equals(item.insertText));
+    expect(
+      item.textEdit.range,
+      equals(rangeFromMarkers(content)),
+    );
+  }
+
   Future<void> test_completeFunctionCalls_noRequiredParameters() async {
     final content = '''
     void myFunction({int a}) {}
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index f782575..b4ca2b5 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -222,6 +222,7 @@
   CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_STATIC,
   CompileTimeErrorCode.INSTANTIATE_ABSTRACT_CLASS,
   CompileTimeErrorCode.INSTANTIATE_ENUM,
+  CompileTimeErrorCode.INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
   CompileTimeErrorCode.INTEGER_LITERAL_IMPRECISE_AS_DOUBLE,
   CompileTimeErrorCode.INTEGER_LITERAL_OUT_OF_RANGE,
   CompileTimeErrorCode.INVALID_ANNOTATION,
@@ -374,6 +375,7 @@
   CompileTimeErrorCode.REDIRECT_TO_MISSING_CONSTRUCTOR,
   CompileTimeErrorCode.REDIRECT_TO_NON_CLASS,
   CompileTimeErrorCode.REDIRECT_TO_NON_CONST_CONSTRUCTOR,
+  CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
   CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION,
   CompileTimeErrorCode.RETHROW_OUTSIDE_CATCH,
   CompileTimeErrorCode.RETURN_IN_GENERATIVE_CONSTRUCTOR,
diff --git a/pkg/analyzer/lib/src/dart/resolver/type_name_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/type_name_resolver.dart
index eef2897..37497f3 100644
--- a/pkg/analyzer/lib/src/dart/resolver/type_name_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/type_name_resolver.dart
@@ -79,7 +79,7 @@
         return;
       }
 
-      if (prefixElement is ClassElement) {
+      if (prefixElement is ClassElement || prefixElement is TypeAliasElement) {
         _rewriteToConstructorName(node, typeIdentifier);
         return;
       }
@@ -206,7 +206,7 @@
           nullabilitySuffix: nullability,
         );
         type = typeSystem.toLegacyType(type);
-        return _verifyTypeAliasForContext(node, type);
+        return _verifyTypeAliasForContext(node, element, type);
       } else if (_isInstanceCreation(node)) {
         _ErrorHelper(errorReporter).reportNewWithNonType(node);
         return dynamicType;
@@ -249,7 +249,7 @@
         typeAliasElement: element,
         nullabilitySuffix: nullability,
       );
-      return _verifyTypeAliasForContext(node, type);
+      return _verifyTypeAliasForContext(node, element, type);
     } else if (_isInstanceCreation(node)) {
       _ErrorHelper(errorReporter).reportNewWithNonType(node);
       return dynamicType;
@@ -367,7 +367,19 @@
     }
   }
 
-  DartType _verifyTypeAliasForContext(TypeName node, DartType type) {
+  DartType _verifyTypeAliasForContext(
+    TypeName node,
+    TypeAliasElement element,
+    DartType type,
+  ) {
+    if (element.aliasedType is TypeParameterType) {
+      var constructorName = node.parent;
+      if (constructorName is ConstructorName) {
+        _ErrorHelper(errorReporter)
+            .reportTypeAliasExpandsToTypeParameter(constructorName, element);
+        return dynamicType;
+      }
+    }
     if (type is! InterfaceType && _isInstanceCreation(node)) {
       _ErrorHelper(errorReporter).reportNewWithNonType(node);
       return dynamicType;
@@ -522,6 +534,28 @@
     );
   }
 
+  void reportTypeAliasExpandsToTypeParameter(
+    ConstructorName constructorName,
+    TypeAliasElement element,
+  ) {
+    var errorNode = _getErrorNode(constructorName.type);
+    var constructorUsage = constructorName.parent;
+    if (constructorUsage is InstanceCreationExpression) {
+      errorReporter.reportErrorForNode(
+        CompileTimeErrorCode.INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+        errorNode,
+      );
+    } else if (constructorUsage is ConstructorDeclaration &&
+        constructorUsage.redirectedConstructor == constructorName) {
+      errorReporter.reportErrorForNode(
+        CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+        errorNode,
+      );
+    } else {
+      throw UnimplementedError('${constructorUsage.runtimeType}');
+    }
+  }
+
   /// Returns the simple identifier of the given (maybe prefixed) identifier.
   static Identifier _getErrorNode(TypeName node) {
     Identifier identifier = node.name;
diff --git a/pkg/analyzer/lib/src/error/codes.dart b/pkg/analyzer/lib/src/error/codes.dart
index 22fbfd8..2acc9c4 100644
--- a/pkg/analyzer/lib/src/error/codes.dart
+++ b/pkg/analyzer/lib/src/error/codes.dart
@@ -4922,6 +4922,18 @@
       correction: "Try using one of the defined constants.");
 
   /**
+   * It is a compile-time error for an instance creation `C<T1, .. Tk>(...)` or
+   * `C<T1, .. Tk>.name()` (where `k` may be zero, which means that the type
+   * argument list is absent) if `C` denotes a type alias that expands to a
+   * type variable.
+   */
+  static const CompileTimeErrorCode
+      INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER = CompileTimeErrorCode(
+          'INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER',
+          "Type aliases that expand to a type parameter can't be instantiated.",
+          correction: "Try replacing it with a class.");
+
+  /**
    * An integer literal with static type `double` and numeric value `i`
    * evaluates to an instance of the `double` class representing the value `i`.
    * It is a compile-time error if the value `i` cannot be represented
@@ -8999,6 +9011,19 @@
           correction: "Try redirecting to a different constructor.");
 
   /**
+   * It is a compile-time error for a redirecting factory constructor to have
+   * a body which is a type alias that expands to a type variable, or a body
+   * which is a parameterized type of the form `F<T1, .. Tk>`, where `F` is
+   * a type alias that expands to a type variable.
+   */
+  static const CompileTimeErrorCode
+      REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER = CompileTimeErrorCode(
+          'REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER',
+          "Redirecting constructor can't redirect to a type alias "
+              "that expands to a type parameter.",
+          correction: "Try replacing it with a class.");
+
+  /**
    * No parameters.
    */
   // #### Description
diff --git a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
index f802202..f739c14 100644
--- a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
@@ -422,6 +422,19 @@
     );
   }
 
+  test_typeAlias_asParameterType_question() async {
+    await assertNoErrorsInCode(r'''
+typedef X<T> = T?;
+void f(X<int> a) {}
+''');
+
+    assertTypeName(
+      findNode.typeName('X<int>'),
+      findElement.typeAlias('X'),
+      'int?',
+    );
+  }
+
   test_typeAlias_asReturnType_interfaceType() async {
     await assertNoErrorsInCode(r'''
 typedef X<T> = Map<int, T>;
diff --git a/pkg/analyzer/test/src/diagnostics/instantiate_type_alias_expands_to_type_parameter_test.dart b/pkg/analyzer/test/src/diagnostics/instantiate_type_alias_expands_to_type_parameter_test.dart
new file mode 100644
index 0000000..0cb16f19
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/instantiate_type_alias_expands_to_type_parameter_test.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2020, 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/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(InstantiateTypeAliasExpandsToTypeParameterTest);
+  });
+}
+
+@reflectiveTest
+class InstantiateTypeAliasExpandsToTypeParameterTest
+    extends PubPackageResolutionTest with WithNonFunctionTypeAliasesMixin {
+  CompileTimeErrorCode get _errorCode =>
+      CompileTimeErrorCode.INSTANTIATE_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER;
+
+  test_const_generic_noArguments_unnamed_typeParameter() async {
+    await assertErrorsInCode(r'''
+typedef A<T> = T;
+
+void f() {
+  const A();
+}
+''', [
+      error(_errorCode, 38, 1),
+    ]);
+  }
+
+  test_const_notGeneric_unnamed_class() async {
+    await assertNoErrorsInCode(r'''
+class A {
+  const A();
+}
+
+typedef X = A;
+
+void f() {
+  const X();
+}
+''');
+  }
+
+  test_new_generic_noArguments_unnamed_typeParameter() async {
+    await assertErrorsInCode(r'''
+typedef A<T> = T;
+
+void f() {
+  new A();
+}
+''', [
+      error(_errorCode, 36, 1),
+    ]);
+  }
+
+  test_new_generic_withArgument_named_typeParameter() async {
+    await assertErrorsInCode(r'''
+class A {
+  A.named();
+}
+
+typedef B<T> = T;
+
+void f() {
+  new B<A>.named();
+}
+''', [
+      error(_errorCode, 62, 1),
+    ]);
+  }
+
+  test_new_generic_withArgument_unnamed_typeParameter() async {
+    await assertErrorsInCode(r'''
+class A {}
+
+typedef B<T> = T;
+
+void f() {
+  new B<A>();
+}
+''', [
+      error(_errorCode, 48, 1),
+    ]);
+  }
+
+  test_new_notGeneric_unnamed_class() async {
+    await assertNoErrorsInCode(r'''
+class A {}
+
+typedef X = A;
+
+void f() {
+  new X();
+}
+''');
+  }
+
+  test_new_notGeneric_unnamed_typeParameter2() async {
+    await assertErrorsInCode(r'''
+typedef A<T> = T;
+typedef B<T> = A<T>;
+
+void f() {
+  new B();
+}
+''', [
+      error(_errorCode, 57, 1),
+    ]);
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/redirect_to_type_alias_expands_to_type_parameter_test.dart b/pkg/analyzer/test/src/diagnostics/redirect_to_type_alias_expands_to_type_parameter_test.dart
new file mode 100644
index 0000000..dbe1342
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/redirect_to_type_alias_expands_to_type_parameter_test.dart
@@ -0,0 +1,97 @@
+// Copyright (c) 2020, 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/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(RedirectTypeAliasExpandsToTypeParameterTest);
+  });
+}
+
+@reflectiveTest
+class RedirectTypeAliasExpandsToTypeParameterTest
+    extends PubPackageResolutionTest with WithNonFunctionTypeAliasesMixin {
+  test_generic_typeParameter_withArgument_named() async {
+    await assertErrorsInCode(r'''
+class A implements C {
+  A.named();
+}
+
+typedef B<T> = T;
+
+class C {
+  factory C() = B<A>.named;
+}
+''', [
+      error(
+          CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+          84,
+          1),
+    ]);
+  }
+
+  test_generic_typeParameter_withArgument_unnamed() async {
+    await assertErrorsInCode(r'''
+class A implements C {}
+
+typedef B<T> = T;
+
+class C {
+  factory C() = B<A>;
+}
+''', [
+      error(
+          CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+          70,
+          1),
+    ]);
+  }
+
+  test_generic_typeParameter_withoutArgument_unnamed() async {
+    await assertErrorsInCode(r'''
+class A implements C {}
+
+typedef B<T> = T;
+
+class C {
+  factory C() = B;
+}
+''', [
+      error(
+          CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER,
+          70,
+          1),
+    ]);
+  }
+
+  test_notGeneric_class_named() async {
+    await assertNoErrorsInCode(r'''
+class A implements C {
+  A.named();
+}
+
+typedef B = A;
+
+class C {
+  factory C() = B.named;
+}
+''');
+  }
+
+  test_notGeneric_class_unnamed() async {
+    await assertNoErrorsInCode(r'''
+class A implements C {}
+
+typedef B = A;
+
+class C {
+  factory C() = B;
+}
+''');
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 05d7fb0..d8c3e4e 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -267,6 +267,8 @@
     as instance_member_access_from_static;
 import 'instantiate_abstract_class_test.dart' as instantiate_abstract_class;
 import 'instantiate_enum_test.dart' as instantiate_enum;
+import 'instantiate_type_alias_expands_to_type_parameter_test.dart'
+    as instantiate_type_alias_expands_to_type_parameter;
 import 'integer_literal_imprecise_as_double_test.dart'
     as integer_literal_imprecise_as_double;
 import 'integer_literal_out_of_range_test.dart' as integer_literal_out_of_range;
@@ -527,6 +529,8 @@
 import 'redirect_to_non_class_test.dart' as redirect_to_non_class;
 import 'redirect_to_non_const_constructor_test.dart'
     as redirect_to_non_const_constructor;
+import 'redirect_to_type_alias_expands_to_type_parameter_test.dart'
+    as redirect_to_type_alias_expands_to_type_parameter;
 import 'referenced_before_declaration_test.dart'
     as referenced_before_declaration;
 import 'rethrow_outside_catch_test.dart' as rethrow_outside_catch;
@@ -833,6 +837,7 @@
     instance_member_access_from_static.main();
     instantiate_abstract_class.main();
     instantiate_enum.main();
+    instantiate_type_alias_expands_to_type_parameter.main();
     integer_literal_imprecise_as_double.main();
     integer_literal_out_of_range.main();
     invalid_annotation.main();
@@ -1005,6 +1010,7 @@
     redirect_to_missing_constructor.main();
     redirect_to_non_class.main();
     redirect_to_non_const_constructor.main();
+    redirect_to_type_alias_expands_to_type_parameter.main();
     referenced_before_declaration.main();
     rethrow_outside_catch.main();
     return_in_generative_constructor.main();
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index 99a5202..a7c9e90 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -5119,6 +5119,30 @@
 ''');
   }
 
+  test_constructor_redirected_factory_named_generic_viaTypeAlias() async {
+    featureSet = FeatureSets.nonFunctionTypeAliases;
+    var library = await checkLibrary('''
+typedef A<T, U> = C<T, U>;
+class B<T, U> {
+  factory B() = A<U, T>.named;
+  B._();
+}
+class C<T, U> extends A<U, T> {
+  C.named() : super._();
+}
+''');
+    checkElementText(library, r'''
+typedef A<T, U> = C<T, U>;
+class B<T, U> {
+  factory B() = C<U, T>.named;
+  B._();
+}
+class C<T, U> extends C<U, T> {
+  C.named();
+}
+''');
+  }
+
   test_constructor_redirected_factory_named_imported() async {
     addLibrarySource('/foo.dart', '''
 import 'test.dart';
@@ -5282,6 +5306,30 @@
 ''');
   }
 
+  test_constructor_redirected_factory_unnamed_generic_viaTypeAlias() async {
+    featureSet = FeatureSets.nonFunctionTypeAliases;
+    var library = await checkLibrary('''
+typedef A<T, U> = C<T, U>;
+class B<T, U> {
+  factory B() = A<U, T>;
+  B_();
+}
+class C<T, U> extends B<U, T> {
+  C() : super._();
+}
+''');
+    checkElementText(library, r'''
+typedef A<T, U> = C<T, U>;
+class B<T, U> {
+  factory B() = C<U, T>;
+  dynamic B_();
+}
+class C<T, U> extends B<U, T> {
+  C();
+}
+''');
+  }
+
   test_constructor_redirected_factory_unnamed_imported() async {
     addLibrarySource('/foo.dart', '''
 import 'test.dart';
@@ -5328,6 +5376,31 @@
 ''');
   }
 
+  test_constructor_redirected_factory_unnamed_imported_viaTypeAlias() async {
+    featureSet = FeatureSets.nonFunctionTypeAliases;
+    addLibrarySource('/foo.dart', '''
+import 'test.dart';
+typedef A = B;
+class B extends C {
+  B() : super._();
+}
+''');
+    var library = await checkLibrary('''
+import 'foo.dart';
+class C {
+  factory C() = A;
+  C._();
+}
+''');
+    checkElementText(library, r'''
+import 'foo.dart';
+class C {
+  factory C() = B;
+  C._();
+}
+''');
+  }
+
   test_constructor_redirected_factory_unnamed_prefixed() async {
     addLibrarySource('/foo.dart', '''
 import 'test.dart';
@@ -5374,6 +5447,31 @@
 ''');
   }
 
+  test_constructor_redirected_factory_unnamed_prefixed_viaTypeAlias() async {
+    featureSet = FeatureSets.nonFunctionTypeAliases;
+    addLibrarySource('/foo.dart', '''
+import 'test.dart';
+typedef A = B;
+class B extends C {
+  B() : super._();
+}
+''');
+    var library = await checkLibrary('''
+import 'foo.dart' as foo;
+class C {
+  factory C() = foo.A;
+  C._();
+}
+''');
+    checkElementText(library, r'''
+import 'foo.dart' as foo;
+class C {
+  factory C() = B;
+  C._();
+}
+''');
+  }
+
   test_constructor_redirected_factory_unnamed_unresolved() async {
     var library = await checkLibrary('''
 class C<E> {
@@ -5387,6 +5485,30 @@
 ''');
   }
 
+  test_constructor_redirected_factory_unnamed_viaTypeAlias() async {
+    featureSet = FeatureSets.nonFunctionTypeAliases;
+    var library = await checkLibrary('''
+typedef A = C;
+class B {
+  factory B() = A;
+  B._();
+}
+class C extends B {
+  C() : super._();
+}
+''');
+    checkElementText(library, r'''
+typedef A = C;
+class B {
+  factory B() = C;
+  B._();
+}
+class C extends B {
+  C();
+}
+''');
+  }
+
   test_constructor_redirected_thisInvocation_named() async {
     var library = await checkLibrary('''
 class C {
diff --git a/pkg/analyzer/test/src/summary/top_level_inference_test.dart b/pkg/analyzer/test/src/summary/top_level_inference_test.dart
index 11b223e..9fbb5f4 100644
--- a/pkg/analyzer/test/src/summary/top_level_inference_test.dart
+++ b/pkg/analyzer/test/src/summary/top_level_inference_test.dart
@@ -2711,8 +2711,10 @@
 
   Future<LibraryElement> _encodeDecodeLibrary(String text) async {
     newFile(testFilePath, content: text);
-    var analysisSession = contextFor(testFilePath).currentSession;
-    var result = await analysisSession.getUnitElement(testFilePath);
+
+    var path = convertPath(testFilePath);
+    var analysisSession = contextFor(path).currentSession;
+    var result = await analysisSession.getUnitElement(path);
     return result.element.library /*!*/;
   }
 }
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 3d7c72e..4cbae00 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -381,6 +381,7 @@
 exhausted
 existentially
 exp
+expando
 expense
 exportable
 exportee
@@ -1057,6 +1058,7 @@
 sra
 srawlins
 src
+ssa
 st
 stability
 stacktrace
@@ -1171,6 +1173,7 @@
 tops
 tput
 tracker
+traditional
 transformers
 transforming
 translation
diff --git a/tools/VERSION b/tools/VERSION
index 0f9cb79..b9ae36a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 155
+PRERELEASE 156
 PRERELEASE_PATCH 0
\ No newline at end of file