Version 2.11.0-197.0.dev

Merge commit '0e9b2b8a365a53714aab1aac6a0820720ff93661' 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 1a1c1df..637e387 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
@@ -536,6 +536,25 @@
   bool ifNullExpression_rightBegin(
       Expression leftHandSide, Type leftHandSideType);
 
+  /// Call this method before visiting the condition part of an if statement.
+  ///
+  /// The order of visiting an if statement with no "else" part should be:
+  /// - Call [ifStatement_conditionBegin]
+  /// - Visit the condition
+  /// - Call [ifStatement_thenBegin]
+  /// - Visit the "then" statement
+  /// - Call [ifStatement_end], passing `false` for `hasElse`.
+  ///
+  /// The order of visiting an if statement with an "else" part should be:
+  /// - Call [ifStatement_conditionBegin]
+  /// - Visit the condition
+  /// - Call [ifStatement_thenBegin]
+  /// - Visit the "then" statement
+  /// - Call [ifStatement_elseBegin]
+  /// - Visit the "else" statement
+  /// - Call [ifStatement_end], passing `true` for `hasElse`.
+  void ifStatement_conditionBegin();
+
   /// Call this method after visiting the "then" part of an if statement, and
   /// before visiting the "else" part.
   void ifStatement_elseBegin();
@@ -545,20 +564,6 @@
 
   /// Call this method after visiting the condition part of an if statement.
   /// [condition] should be the if statement's condition.
-  ///
-  /// The order of visiting an if statement with no "else" part should be:
-  /// - Visit the condition
-  /// - Call [ifStatement_thenBegin]
-  /// - Visit the "then" statement
-  /// - Call [ifStatement_end], passing `false` for `hasElse`.
-  ///
-  /// The order of visiting an if statement with an "else" part should be:
-  /// - Visit the condition
-  /// - Call [ifStatement_thenBegin]
-  /// - Visit the "then" statement
-  /// - Call [ifStatement_elseBegin]
-  /// - Visit the "else" statement
-  /// - Call [ifStatement_end], passing `true` for `hasElse`.
   void ifStatement_thenBegin(Expression condition);
 
   /// Return whether the [variable] is definitely assigned in the current state.
@@ -1015,6 +1020,12 @@
   }
 
   @override
+  void ifStatement_conditionBegin() {
+    return _wrap('ifStatement_conditionBegin()',
+        () => _wrapped.ifStatement_conditionBegin());
+  }
+
+  @override
   void ifStatement_elseBegin() {
     return _wrap(
         'ifStatement_elseBegin()', () => _wrapped.ifStatement_elseBegin());
@@ -1658,6 +1669,17 @@
   FlowModel<Variable, Type> unsplit() =>
       new FlowModel<Variable, Type>.withInfo(reachable.unsplit(), variableInfo);
 
+  /// Removes control flow splits until a [FlowModel] is obtained whose
+  /// reachability has the given [parent].
+  FlowModel<Variable, Type> unsplitTo(Reachability parent) {
+    if (identical(this.reachable.parent, parent)) return this;
+    Reachability reachable = this.reachable.unsplit();
+    while (!identical(reachable.parent, parent)) {
+      reachable = reachable.unsplit();
+    }
+    return new FlowModel<Variable, Type>.withInfo(reachable, variableInfo);
+  }
+
   /// Updates the state to indicate that an assignment was made to the given
   /// [variable].  The variable is marked as definitely assigned, and any
   /// previous type promotion is removed.
@@ -1808,6 +1830,37 @@
     return result;
   }
 
+  /// Models the result of joining the flow models [first] and [second] at the
+  /// merge of two control flow paths.
+  static FlowModel<Variable, Type> merge<Variable, Type>(
+    TypeOperations<Variable, Type> typeOperations,
+    FlowModel<Variable, Type> first,
+    FlowModel<Variable, Type> second,
+    Map<Variable, VariableModel<Variable, Type>> emptyVariableMap,
+  ) {
+    if (first == null) return second.unsplit();
+    if (second == null) return first.unsplit();
+
+    assert(identical(first.reachable.parent, second.reachable.parent));
+    if (first.reachable.locallyReachable &&
+        !second.reachable.locallyReachable) {
+      return first.unsplit();
+    }
+    if (!first.reachable.locallyReachable &&
+        second.reachable.locallyReachable) {
+      return second.unsplit();
+    }
+
+    Reachability newReachable =
+        Reachability.join(first.reachable, second.reachable).unsplit();
+    Map<Variable, VariableModel<Variable, Type>> newVariableInfo =
+        FlowModel.joinVariableInfo(typeOperations, first.variableInfo,
+            second.variableInfo, emptyVariableMap);
+
+    return FlowModel._identicalOrNew(
+        first, second, newReachable, newVariableInfo);
+  }
+
   /// Creates a new [FlowModel] object, unless it is equivalent to either
   /// [first] or [second], in which case one of those objects is re-used.
   static FlowModel<Variable, Type> _identicalOrNew<Variable, Type>(
@@ -2554,9 +2607,17 @@
   /// `null` if no `continue` statements have been seen yet.
   FlowModel<Variable, Type> _continueModel;
 
+  /// The reachability checkpoint associated with this loop or switch statement.
+  /// When analyzing deeply nested `break` and `continue` statements, their flow
+  /// models need to be unsplit to this point before joining them to the control
+  /// flow paths for the loop or switch.
+  final Reachability _checkpoint;
+
+  _BranchTargetContext(this._checkpoint);
+
   @override
   String toString() => '_BranchTargetContext(breakModel: $_breakModel, '
-      'continueModel: $_continueModel)';
+      'continueModel: $_continueModel, checkpoint: $_checkpoint)';
 }
 
 /// [_FlowContext] representing a conditional expression.
@@ -2710,7 +2771,7 @@
     AssignedVariablesNodeInfo<Variable> info =
         _assignedVariables._getInfoForNode(doStatement);
     _BranchTargetContext<Variable, Type> context =
-        new _BranchTargetContext<Variable, Type>();
+        new _BranchTargetContext<Variable, Type>(_current.reachable.parent);
     _stack.add(context);
     _current = _current.conservativeJoin(info._written, info._captured);
     _statementToContext[doStatement] = context;
@@ -2794,8 +2855,8 @@
     ExpressionInfo<Variable, Type> conditionInfo = condition == null
         ? new ExpressionInfo(_current, _current, _current.setUnreachable())
         : _expressionEnd(condition);
-    _WhileContext<Variable, Type> context =
-        new _WhileContext<Variable, Type>(conditionInfo);
+    _WhileContext<Variable, Type> context = new _WhileContext<Variable, Type>(
+        _current.reachable.parent, conditionInfo);
     _stack.add(context);
     if (node != null) {
       _statementToContext[node] = context;
@@ -2835,7 +2896,8 @@
         _assignedVariables._getInfoForNode(node);
     _current = _current.conservativeJoin(info._written, info._captured);
     _SimpleStatementContext<Variable, Type> context =
-        new _SimpleStatementContext<Variable, Type>(_current);
+        new _SimpleStatementContext<Variable, Type>(
+            _current.reachable.parent, _current);
     _stack.add(context);
     if (loopVariable != null) {
       _current = _current.write(loopVariable, writtenType, typeOperations);
@@ -2880,7 +2942,8 @@
   void handleBreak(Statement target) {
     _BranchTargetContext<Variable, Type> context = _statementToContext[target];
     if (context != null) {
-      context._breakModel = _join(context._breakModel, _current);
+      context._breakModel =
+          _join(context._breakModel, _current.unsplitTo(context._checkpoint));
     }
     _current = _current.setUnreachable();
   }
@@ -2889,7 +2952,8 @@
   void handleContinue(Statement target) {
     _BranchTargetContext<Variable, Type> context = _statementToContext[target];
     if (context != null) {
-      context._continueModel = _join(context._continueModel, _current);
+      context._continueModel = _join(
+          context._continueModel, _current.unsplitTo(context._checkpoint));
     }
     _current = _current.setUnreachable();
   }
@@ -2924,6 +2988,11 @@
   }
 
   @override
+  void ifStatement_conditionBegin() {
+    _current = _current.split();
+  }
+
+  @override
   void ifStatement_elseBegin() {
     _IfContext<Variable, Type> context =
         _stack.last as _IfContext<Variable, Type>;
@@ -2944,7 +3013,7 @@
       afterThen = _current; // no `else`, so `then` is still current
       afterElse = context._conditionInfo.ifFalse;
     }
-    _current = _join(afterThen, afterElse);
+    _current = _merge(afterThen, afterElse);
   }
 
   @override
@@ -2985,7 +3054,7 @@
   @override
   void labeledStatement_begin(Node node) {
     _BranchTargetContext<Variable, Type> context =
-        new _BranchTargetContext<Variable, Type>();
+        new _BranchTargetContext<Variable, Type>(_current.reachable.parent);
     _stack.add(context);
     _statementToContext[node] = context;
   }
@@ -3140,7 +3209,8 @@
   @override
   void switchStatement_expressionEnd(Statement switchStatement) {
     _SimpleStatementContext<Variable, Type> context =
-        new _SimpleStatementContext<Variable, Type>(_current);
+        new _SimpleStatementContext<Variable, Type>(
+            _current.reachable.parent, _current);
     _stack.add(context);
     _statementToContext[switchStatement] = context;
   }
@@ -3232,8 +3302,8 @@
   void whileStatement_bodyBegin(
       Statement whileStatement, Expression condition) {
     ExpressionInfo<Variable, Type> conditionInfo = _expressionEnd(condition);
-    _WhileContext<Variable, Type> context =
-        new _WhileContext<Variable, Type>(conditionInfo);
+    _WhileContext<Variable, Type> context = new _WhileContext<Variable, Type>(
+        _current.reachable.parent, conditionInfo);
     _stack.add(context);
     _statementToContext[whileStatement] = context;
     _current = conditionInfo.ifTrue;
@@ -3302,6 +3372,11 @@
           FlowModel<Variable, Type> first, FlowModel<Variable, Type> second) =>
       FlowModel.join(typeOperations, first, second, _current._emptyVariableMap);
 
+  FlowModel<Variable, Type> _merge(
+          FlowModel<Variable, Type> first, FlowModel<Variable, Type> second) =>
+      FlowModel.merge(
+          typeOperations, first, second, _current._emptyVariableMap);
+
   /// Associates [expression], which should be the most recently visited
   /// expression, with the given [expressionInfo] object, and updates the
   /// current flow model state to correspond to it.
@@ -3373,11 +3448,13 @@
   /// after evaluation of the switch expression.
   final FlowModel<Variable, Type> _previous;
 
-  _SimpleStatementContext(this._previous);
+  _SimpleStatementContext(Reachability checkpoint, this._previous)
+      : super(checkpoint);
 
   @override
   String toString() => '_SimpleStatementContext(breakModel: $_breakModel, '
-      'continueModel: $_continueModel, previous: $_previous)';
+      'continueModel: $_continueModel, previous: $_previous, '
+      'checkpoint: $_checkpoint)';
 }
 
 /// [_FlowContext] representing a try statement.
@@ -3430,9 +3507,11 @@
   /// Flow models associated with the loop condition.
   final ExpressionInfo<Variable, Type> _conditionInfo;
 
-  _WhileContext(this._conditionInfo);
+  _WhileContext(Reachability checkpoint, this._conditionInfo)
+      : super(checkpoint);
 
   @override
   String toString() => '_WhileContext(breakModel: $_breakModel, '
-      'continueModel: $_continueModel, conditionInfo: $_conditionInfo)';
+      'continueModel: $_continueModel, conditionInfo: $_conditionInfo, '
+      'checkpoint: $_checkpoint)';
 }
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 16ae7ff..0af6a2f 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
@@ -179,6 +179,7 @@
       var x = h.addVar('x', 'int?');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         var varExpr = _Expression();
         flow.variableRead(varExpr, x);
         flow.equalityOp_rightBegin(varExpr, _Type('int?'));
@@ -203,6 +204,7 @@
       var x = h.addVar('x', 'int');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         var varExpr = _Expression();
         flow.variableRead(varExpr, x);
         flow.equalityOp_rightBegin(varExpr, _Type('int'));
@@ -227,6 +229,7 @@
     test('equalityOp(<expr> == <expr>) has no special effect', () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         flow.equalityOp_rightBegin(_Expression(), _Type('int?'));
         var expr = _Expression();
         var successIsReachable = flow.equalityOp_end(
@@ -244,6 +247,7 @@
     test('equalityOp(<expr> != <expr>) has no special effect', () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         flow.equalityOp_rightBegin(_Expression(), _Type('int?'));
         var expr = _Expression();
         var successIsReachable = flow
@@ -262,6 +266,7 @@
       var x = h.addVar('x', 'int?');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         var varExpr = _Expression();
         flow.variableRead(varExpr, x);
         flow.equalityOp_rightBegin(varExpr, _Type('int?'));
@@ -281,6 +286,7 @@
       var x = h.addVar('x', 'int?');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         var varExpr = _Expression();
         flow.variableRead(varExpr, x);
         flow.equalityOp_rightBegin(varExpr, _Type('int?'));
@@ -305,6 +311,7 @@
       var x = h.addVar('x', 'int');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         var varExpr = _Expression();
         flow.variableRead(varExpr, x);
         flow.equalityOp_rightBegin(varExpr, _Type('int'));
@@ -331,6 +338,7 @@
       var x = h.addVar('x', 'int?');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         var nullExpr = _Expression();
         flow.nullLiteral(nullExpr);
         flow.equalityOp_rightBegin(nullExpr, _Type('Null'));
@@ -351,6 +359,7 @@
       var x = h.addVar('x', 'int?');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         var nullExpr = _Expression();
         flow.equalityOp_rightBegin(nullExpr, _Type('Null'));
         var varExpr = _Expression();
@@ -370,6 +379,7 @@
       var x = h.addVar('x', 'int?');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         var nullExpr = _Expression();
         flow.nullLiteral(nullExpr);
         flow.equalityOp_rightBegin(nullExpr, _Type('Null'));
@@ -388,6 +398,7 @@
     test('equalityOp(null == null) equivalent to true', () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         var null1 = _Expression();
         flow.equalityOp_rightBegin(null1, _Type('Null'));
         var null2 = _Expression();
@@ -406,6 +417,7 @@
     test('equalityOp(null != null) equivalent to false', () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         var null1 = _Expression();
         flow.equalityOp_rightBegin(null1, _Type('Null'));
         var null2 = _Expression();
@@ -424,6 +436,7 @@
     test('equalityOp(null == non-null) is not equivalent to false', () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         var null1 = _Expression();
         flow.equalityOp_rightBegin(null1, _Type('Null'));
         var null2 = _Expression();
@@ -443,6 +456,7 @@
     test('equalityOp(null != non-null) is not equivalent to true', () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         var null1 = _Expression();
         flow.equalityOp_rightBegin(null1, _Type('Null'));
         var null2 = _Expression();
@@ -463,6 +477,7 @@
     test('equalityOp(non-null == null) is not equivalent to false', () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         var null1 = _Expression();
         flow.equalityOp_rightBegin(null1, _Type('int'));
         var null2 = _Expression();
@@ -483,6 +498,7 @@
     test('equalityOp(non-null != null) is not equivalent to true', () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         var null1 = _Expression();
         flow.equalityOp_rightBegin(null1, _Type('int'));
         var null2 = _Expression();
@@ -599,6 +615,7 @@
       var h = _Harness();
       var expr = _Expression();
       var flow = h.createFlow();
+      flow.ifStatement_conditionBegin();
       flow.ifStatement_thenBegin(expr);
       expect(() => flow.finish(), _asserts);
     });
@@ -1040,6 +1057,94 @@
       });
     });
 
+    test('handleBreak handles deep nesting', () {
+      var h = _Harness();
+      var whileStatement = _Statement();
+      h.assignedVariables((vars) {
+        vars.nest(whileStatement, () {});
+      });
+      h.run((flow) {
+        flow.whileStatement_conditionBegin(whileStatement);
+        flow.whileStatement_bodyBegin(whileStatement, h.booleanLiteral(true)());
+        h.if_(h.expr, () {
+          h.if_(h.expr, () {
+            flow.handleBreak(whileStatement);
+          });
+        });
+        flow.handleExit();
+        expect(flow.isReachable, false);
+        flow.whileStatement_end();
+        expect(flow.isReachable, true);
+      });
+    });
+
+    test('handleBreak handles mixed nesting', () {
+      var h = _Harness();
+      var whileStatement = _Statement();
+      h.assignedVariables((vars) {
+        vars.nest(whileStatement, () {});
+      });
+      h.run((flow) {
+        flow.whileStatement_conditionBegin(whileStatement);
+        flow.whileStatement_bodyBegin(whileStatement, h.booleanLiteral(true)());
+        h.if_(h.expr, () {
+          h.if_(h.expr, () {
+            flow.handleBreak(whileStatement);
+          });
+          flow.handleBreak(whileStatement);
+        });
+        flow.handleBreak(whileStatement);
+        expect(flow.isReachable, false);
+        flow.whileStatement_end();
+        expect(flow.isReachable, true);
+      });
+    });
+
+    test('handleContinue handles deep nesting', () {
+      var h = _Harness();
+      var doStatement = _Statement();
+      h.assignedVariables((vars) {
+        vars.nest(doStatement, () {});
+      });
+      h.run((flow) {
+        flow.doStatement_bodyBegin(doStatement);
+        h.if_(h.expr, () {
+          h.if_(h.expr, () {
+            flow.handleContinue(doStatement);
+          });
+        });
+        flow.handleExit();
+        expect(flow.isReachable, false);
+        flow.doStatement_conditionBegin();
+        expect(flow.isReachable, true);
+        flow.doStatement_end(h.booleanLiteral(true)());
+        expect(flow.isReachable, false);
+      });
+    });
+
+    test('handleContinue handles mixed nesting', () {
+      var h = _Harness();
+      var doStatement = _Statement();
+      h.assignedVariables((vars) {
+        vars.nest(doStatement, () {});
+      });
+      h.run((flow) {
+        flow.doStatement_bodyBegin(doStatement);
+        h.if_(h.expr, () {
+          h.if_(h.expr, () {
+            flow.handleContinue(doStatement);
+          });
+          flow.handleContinue(doStatement);
+        });
+        flow.handleContinue(doStatement);
+        expect(flow.isReachable, false);
+        flow.doStatement_conditionBegin();
+        expect(flow.isReachable, true);
+        flow.doStatement_end(h.booleanLiteral(true)());
+        expect(flow.isReachable, false);
+      });
+    });
+
     test('ifNullExpression allows ensure guarding', () {
       var h = _Harness();
       var x = h.addVar('x', 'int?');
@@ -1119,11 +1224,28 @@
       });
     });
 
+    test('ifStatement with early exit promotes in unreachable code', () {
+      var h = _Harness();
+      var x = h.addVar('x', 'int?');
+      h.run((flow) {
+        h.declare(x, initialized: true);
+        flow.handleExit();
+        expect(flow.isReachable, false);
+        flow.ifStatement_conditionBegin();
+        flow.ifStatement_thenBegin(h.eqNull(x, _Type('int?'))());
+        flow.handleExit();
+        flow.ifStatement_end(false);
+        expect(flow.isReachable, false);
+        expect(flow.promotedType(x).type, 'int');
+      });
+    });
+
     test('ifStatement_end(false) keeps else branch if then branch exits', () {
       var h = _Harness();
       var x = h.addVar('x', 'int?');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         flow.ifStatement_thenBegin(h.eqNull(x, _Type('int?'))());
         flow.handleExit();
         flow.ifStatement_end(false);
@@ -1138,6 +1260,7 @@
       var x = h.addVar('x', declaredType);
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         var read = _Expression();
         flow.variableRead(read, x);
         var expr = _Expression();
@@ -1190,6 +1313,7 @@
     test('isExpression_end does nothing if applied to a non-variable', () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         var subExpr = _Expression();
         var expr = _Expression();
         var failureReachable =
@@ -1207,6 +1331,7 @@
         () {
       var h = _Harness();
       h.run((flow) {
+        flow.ifStatement_conditionBegin();
         var subExpr = _Expression();
         var expr = _Expression();
         var failureReachable =
@@ -1305,6 +1430,7 @@
       var x = h.addVar('x', 'int?');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         flow.logicalBinaryOp_rightBegin(_Expression(), isAnd: true);
         var wholeExpr = _Expression();
         flow.logicalBinaryOp_end(wholeExpr, h.notNull(x, _Type('int?'))(),
@@ -1321,6 +1447,7 @@
       var x = h.addVar('x', 'int?');
       h.run((flow) {
         h.declare(x, initialized: true);
+        flow.ifStatement_conditionBegin();
         flow.logicalBinaryOp_rightBegin(_Expression(), isAnd: false);
         var wholeExpr = _Expression();
         flow.logicalBinaryOp_end(wholeExpr, h.eqNull(x, _Type('int?'))(),
@@ -2163,6 +2290,7 @@
         h.promote(x, 'int');
         expect(flow.promotedType(x).type, 'int');
         // if (false) {
+        flow.ifStatement_conditionBegin();
         var falseExpression = _Expression();
         flow.booleanLiteral(falseExpression, false);
         flow.ifStatement_thenBegin(falseExpression);
@@ -2328,6 +2456,55 @@
       expect(s2.reachable, same(Reachability.initial));
     });
 
+    group('unsplitTo', () {
+      test('no change', () {
+        var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+        var result = s1.unsplitTo(s1.reachable.parent);
+        expect(result, same(s1));
+      });
+
+      test('unsplit once, reachable', () {
+        var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+        var s2 = s1.split();
+        var result = s2.unsplitTo(s1.reachable.parent);
+        expect(result.reachable, same(s1.reachable));
+      });
+
+      test('unsplit once, unreachable', () {
+        var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+        var s2 = s1.split().setUnreachable();
+        var result = s2.unsplitTo(s1.reachable.parent);
+        expect(result.reachable.locallyReachable, false);
+        expect(result.reachable.parent, same(s1.reachable.parent));
+      });
+
+      test('unsplit twice, reachable', () {
+        var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+        var s2 = s1.split();
+        var s3 = s2.split();
+        var result = s3.unsplitTo(s1.reachable.parent);
+        expect(result.reachable, same(s1.reachable));
+      });
+
+      test('unsplit twice, top unreachable', () {
+        var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+        var s2 = s1.split();
+        var s3 = s2.split().setUnreachable();
+        var result = s3.unsplitTo(s1.reachable.parent);
+        expect(result.reachable.locallyReachable, false);
+        expect(result.reachable.parent, same(s1.reachable.parent));
+      });
+
+      test('unsplit twice, previous unreachable', () {
+        var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+        var s2 = s1.split().setUnreachable();
+        var s3 = s2.split();
+        var result = s3.unsplitTo(s1.reachable.parent);
+        expect(result.reachable.locallyReachable, false);
+        expect(result.reachable.parent, same(s1.reachable.parent));
+      });
+    });
+
     group('tryPromoteForTypeCheck', () {
       test('unpromoted -> unchanged (same)', () {
         var h = _Harness();
@@ -3581,6 +3758,101 @@
       });
     });
   });
+
+  group('merge', () {
+    var x = _Var('x', _Type('Object?'));
+    var intType = _Type('int');
+    var stringType = _Type('String');
+    const emptyMap = const <_Var, VariableModel<_Var, _Type>>{};
+
+    VariableModel<_Var, _Type> varModel(List<_Type> promotionChain,
+            {bool assigned = false}) =>
+        VariableModel<_Var, _Type>(
+          promotionChain,
+          promotionChain ?? [],
+          assigned,
+          !assigned,
+          false,
+        );
+
+    test('first is null', () {
+      var h = _Harness();
+      var s1 = FlowModel.withInfo(Reachability.initial.split(), {});
+      var result = FlowModel.merge(h, null, s1, emptyMap);
+      expect(result.reachable, same(Reachability.initial));
+    });
+
+    test('second is null', () {
+      var h = _Harness();
+      var splitPoint = Reachability.initial.split();
+      var afterSplit = splitPoint.split();
+      var s1 = FlowModel.withInfo(afterSplit, {});
+      var result = FlowModel.merge(h, s1, null, emptyMap);
+      expect(result.reachable, same(splitPoint));
+    });
+
+    test('both are reachable', () {
+      var h = _Harness();
+      var splitPoint = Reachability.initial.split();
+      var afterSplit = splitPoint.split();
+      var s1 = FlowModel.withInfo(afterSplit, {
+        x: varModel([intType])
+      });
+      var s2 = FlowModel.withInfo(afterSplit, {
+        x: varModel([stringType])
+      });
+      var result = FlowModel.merge(h, s1, s2, emptyMap);
+      expect(result.reachable, same(splitPoint));
+      expect(result.variableInfo[x].promotedTypes, isNull);
+    });
+
+    test('first is unreachable', () {
+      var h = _Harness();
+      var splitPoint = Reachability.initial.split();
+      var afterSplit = splitPoint.split();
+      var s1 = FlowModel.withInfo(afterSplit.setUnreachable(), {
+        x: varModel([intType])
+      });
+      var s2 = FlowModel.withInfo(afterSplit, {
+        x: varModel([stringType])
+      });
+      var result = FlowModel.merge(h, s1, s2, emptyMap);
+      expect(result.reachable, same(splitPoint));
+      expect(result.variableInfo, same(s2.variableInfo));
+    });
+
+    test('second is unreachable', () {
+      var h = _Harness();
+      var splitPoint = Reachability.initial.split();
+      var afterSplit = splitPoint.split();
+      var s1 = FlowModel.withInfo(afterSplit, {
+        x: varModel([intType])
+      });
+      var s2 = FlowModel.withInfo(afterSplit.setUnreachable(), {
+        x: varModel([stringType])
+      });
+      var result = FlowModel.merge(h, s1, s2, emptyMap);
+      expect(result.reachable, same(splitPoint));
+      expect(result.variableInfo, same(s1.variableInfo));
+    });
+
+    test('both are unreachable', () {
+      var h = _Harness();
+      var splitPoint = Reachability.initial.split();
+      var afterSplit = splitPoint.split();
+      var s1 = FlowModel.withInfo(afterSplit.setUnreachable(), {
+        x: varModel([intType])
+      });
+      var s2 = FlowModel.withInfo(afterSplit.setUnreachable(), {
+        x: varModel([stringType])
+      });
+      var result = FlowModel.merge(h, s1, s2, emptyMap);
+      expect(result.reachable.locallyReachable, false);
+      expect(result.reachable.parent, same(splitPoint.parent));
+      expect(result.variableInfo[x].promotedTypes, isNull);
+    });
+  });
+
   group('inheritTested', () {
     var x = _Var('x', _Type('Object?'));
     var intType = _Type('int');
@@ -3881,6 +4153,12 @@
     callback(_AssignedVariablesHarness(_assignedVariables));
   }
 
+  LazyExpression booleanLiteral(bool value) => () {
+        var expr = _Expression();
+        _flow.booleanLiteral(expr, value);
+        return expr;
+      };
+
   @override
   TypeClassification classifyType(_Type type) {
     if (isSubtypeOf(type, _Type('Object'))) {
@@ -3943,6 +4221,7 @@
 
   /// Invokes flow analysis of an `if` statement with no `else` part.
   void if_(LazyExpression cond, void ifTrue()) {
+    _flow.ifStatement_conditionBegin();
     _flow.ifStatement_thenBegin(cond());
     ifTrue();
     _flow.ifStatement_end(false);
@@ -3950,6 +4229,7 @@
 
   /// Invokes flow analysis of an `if` statement with an `else` part.
   void ifElse(LazyExpression cond, void ifTrue(), void ifFalse()) {
+    _flow.ifStatement_conditionBegin();
     _flow.ifStatement_thenBegin(cond());
     ifTrue();
     _flow.ifStatement_elseBegin();
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/promotion_in_dead_code.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/promotion_in_dead_code.dart
new file mode 100644
index 0000000..4a3c9a5
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/promotion_in_dead_code.dart
@@ -0,0 +1,31 @@
+// 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.
+
+// These tests verify that the kinds of constructs we expect to cause type
+// promotion continue to function properly even when used inside unreachable
+// code.
+
+ifIsNot(Object o) {
+  return;
+  if (o is! int) return;
+  /*int*/ o;
+}
+
+ifIsNot_listElement(Object o) {
+  return;
+  [if (o is! int) throw 'x'];
+  /*int*/ o;
+}
+
+ifIsNot_setElement(Object o) {
+  return;
+  ({if (o is! int) throw 'x'});
+  /*int*/ o;
+}
+
+ifIsNot_mapElement(Object o) {
+  return;
+  ({if (o is! int) 0: throw 'x'});
+  /*int*/ o;
+}
diff --git a/pkg/analysis_server/analysis_options.yaml b/pkg/analysis_server/analysis_options.yaml
index 5995e86..31a937b 100644
--- a/pkg/analysis_server/analysis_options.yaml
+++ b/pkg/analysis_server/analysis_options.yaml
@@ -1,4 +1,4 @@
-include: package:pedantic/analysis_options.1.8.0.yaml
+include: package:pedantic/analysis_options.1.9.0.yaml
 
 analyzer:
   # This currently finds ~1,200 implicit-casts issues when enabled.
@@ -16,25 +16,11 @@
 linter:
   rules:
     - await_only_futures
+    - avoid_single_cascade_in_expression_statements
     - empty_statements
+    - iterable_contains_unrelated_type
+    - list_remove_unrelated_type
+    - prefer_initializing_formals
     - unnecessary_brace_in_string_interps
-    #
-    # Delta from pedantic 1.8.0 to 1.9.0
-    #
-    - always_declare_return_types
-    - always_require_non_null_named_parameters
-    - annotate_overrides
-    - avoid_null_checks_in_equality_operators
-    - camel_case_extensions
-    - omit_local_variable_types
-    - prefer_adjacent_string_concatenation
-    - prefer_collection_literals
-    - prefer_conditional_assignment
-    - prefer_final_fields
-    - prefer_for_elements_to_map_fromIterable
-    - prefer_generic_function_type_aliases
-    - prefer_if_null_operators
-    - prefer_single_quotes
-    - prefer_spread_collections
-    - unnecessary_this
-    - use_function_type_syntax_for_parameters
+    - unnecessary_overrides
+    - void_checks
diff --git a/pkg/analysis_server/lib/protocol/protocol.dart b/pkg/analysis_server/lib/protocol/protocol.dart
index 53b008f..f995858 100644
--- a/pkg/analysis_server/lib/protocol/protocol.dart
+++ b/pkg/analysis_server/lib/protocol/protocol.dart
@@ -305,7 +305,7 @@
   /// with the given [id].  If [_result] is provided, it will be used as the
   /// result; otherwise an empty result will be used.  If an [error] is provided
   /// then the response will represent an error condition.
-  Response(this.id, {Map<String, Object> result, this.error}) : result = result;
+  Response(this.id, {this.result, this.error});
 
   /// Create and return the `DEBUG_PORT_COULD_NOT_BE_OPENED` error response.
   Response.debugPortCouldNotBeOpened(Request request, dynamic error)
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index 0a4dbf9..c16084d 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -108,10 +108,9 @@
   /// added to the context.
   Map<String, Source> sources = HashMap<String, Source>();
 
-  ContextInfo(ContextManagerImpl contextManager, this.parent, Folder folder,
+  ContextInfo(ContextManagerImpl contextManager, this.parent, this.folder,
       File packagespecFile, this.disposition)
-      : folder = folder,
-        pathFilter = PathFilter(
+      : pathFilter = PathFilter(
             folder.path, null, contextManager.resourceProvider.pathContext) {
     packageDescriptionPath = packagespecFile.path;
     parent.children.add(this);
diff --git a/pkg/analysis_server/lib/src/search/search_domain.dart b/pkg/analysis_server/lib/src/search/search_domain.dart
index 93b2db4..9dd70bf 100644
--- a/pkg/analysis_server/lib/src/search/search_domain.dart
+++ b/pkg/analysis_server/lib/src/search/search_domain.dart
@@ -25,9 +25,7 @@
 
   /// Initialize a newly created handler to handle requests for the given
   /// [server].
-  SearchDomainHandler(AnalysisServer server)
-      : server = server,
-        searchEngine = server.searchEngine;
+  SearchDomainHandler(this.server) : searchEngine = server.searchEngine;
 
   Future findElementReferences(protocol.Request request) async {
     var params =
diff --git a/pkg/analysis_server/lib/src/services/completion/completion_core.dart b/pkg/analysis_server/lib/src/services/completion/completion_core.dart
index 5339b4d..a84b968 100644
--- a/pkg/analysis_server/lib/src/services/completion/completion_core.dart
+++ b/pkg/analysis_server/lib/src/services/completion/completion_core.dart
@@ -40,9 +40,8 @@
   /// Initialize a newly created completion request based on the given
   /// arguments.
   CompletionRequestImpl(
-      this.result, int offset, this.useNewRelevance, this.performance)
-      : offset = offset,
-        replacementOffset = offset,
+      this.result, this.offset, this.useNewRelevance, this.performance)
+      : replacementOffset = offset,
         replacementLength = 0;
 
   @override
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
index 9bbc308..df843b8 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
@@ -451,7 +451,7 @@
   @override
   void visitExtendsClause(ExtendsClause node) {
     inExtendsClause = true;
-    return super.visitExtendsClause(node);
+    super.visitExtendsClause(node);
   }
 
   /// Return `true` if the [identifier] is composed of one or more underscore
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index e506f30..d04d443 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -253,7 +253,7 @@
         List.from(fixList[0].change.linkedEditGroups);
     for (var i = 1; i < fixList.length; i++) {
       edits.addAll(fixList[i].change.edits[0].edits);
-      sourceChange.linkedEditGroups..addAll(fixList[i].change.linkedEditGroups);
+      sourceChange.linkedEditGroups.addAll(fixList[i].change.linkedEditGroups);
     }
     // Sort the list of SourceEdits so that when the edits are applied, they
     // are applied from the end of the file to the top of the file.
diff --git a/pkg/analysis_server/lib/src/services/correction/sort_members.dart b/pkg/analysis_server/lib/src/services/correction/sort_members.dart
index 34e0ba4..cf3db41 100644
--- a/pkg/analysis_server/lib/src/services/correction/sort_members.dart
+++ b/pkg/analysis_server/lib/src/services/correction/sort_members.dart
@@ -262,10 +262,8 @@
   final int end;
   final String text;
 
-  _MemberInfo(this.item, this.name, int offset, int length, this.text)
-      : offset = offset,
-        length = length,
-        end = offset + length;
+  _MemberInfo(this.item, this.name, this.offset, this.length, this.text)
+      : end = offset + length;
 
   @override
   String toString() {
diff --git a/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart b/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart
index 398620c..99e8dd2 100644
--- a/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart
+++ b/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart
@@ -1083,7 +1083,7 @@
         _enclosingVName =
             _vNameFromElement(_enclosingElement, schema.FUNCTION_KIND);
       }
-      return f();
+      f();
     } finally {
       _enclosingElement = outerEnclosingElement;
       _enclosingClassElement = outerEnclosingClassElement;
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
index 7cfe3fe..7f96d3a 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
@@ -148,10 +148,9 @@
         elementKind = ElementKind.METHOD;
 
   _ClassMemberValidator.forRename(
-      this.searchEngine, this.sessionHelper, Element element, this.name)
+      this.searchEngine, this.sessionHelper, this.element, this.name)
       : isRename = true,
         library = element.library,
-        element = element,
         elementClass = element.enclosingElement,
         elementKind = element.kind;
 
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart
index 3a4a3a5..6187abe 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart
@@ -104,10 +104,9 @@
   final List<SearchMatch> references = <SearchMatch>[];
 
   _ExtensionMemberValidator.forRename(
-      this.searchEngine, this.sessionHelper, Element element, this.name)
+      this.searchEngine, this.sessionHelper, this.element, this.name)
       : isRename = true,
         library = element.library,
-        element = element,
         elementExtension = element.enclosingElement,
         elementKind = element.kind;
 
diff --git a/pkg/analysis_server/pubspec.yaml b/pkg/analysis_server/pubspec.yaml
index 842ac85..da3e6a6 100644
--- a/pkg/analysis_server/pubspec.yaml
+++ b/pkg/analysis_server/pubspec.yaml
@@ -35,5 +35,5 @@
   logging: any
   matcher: any
   mockito: any
-  pedantic: ^1.8.0
+  pedantic: ^1.9.0
   test_reflective_loader: any
diff --git a/pkg/analysis_server/test/integration/support/integration_tests.dart b/pkg/analysis_server/test/integration/support/integration_tests.dart
index e51eace..f58ca4d 100644
--- a/pkg/analysis_server/test/integration/support/integration_tests.dart
+++ b/pkg/analysis_server/test/integration/support/integration_tests.dart
@@ -690,9 +690,7 @@
   /// Iterable matcher which we use to test the contents of the list.
   final Matcher iterableMatcher;
 
-  _ListOf(elementMatcher)
-      : elementMatcher = elementMatcher,
-        iterableMatcher = everyElement(elementMatcher);
+  _ListOf(this.elementMatcher) : iterableMatcher = everyElement(elementMatcher);
 
   @override
   Description describe(Description description) =>
diff --git a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
index b911615..a9f9ce5 100644
--- a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
+++ b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
@@ -365,7 +365,7 @@
       ..writeIndentedln('switch (obj) {')
       ..indent();
     consts.forEach((cons) {
-      buffer..writeIndentedln('case ${cons.valueAsLiteral}:');
+      buffer.writeIndentedln('case ${cons.valueAsLiteral}:');
     });
     buffer
       ..indent()
@@ -380,9 +380,8 @@
     ..writeIndentedln('}');
   namespace.members.whereType<Const>().forEach((cons) {
     _writeDocCommentsAndAnnotations(buffer, cons);
-    buffer
-      ..writeIndentedln(
-          'static const ${_makeValidIdentifier(cons.name)} = ${namespace.name}$constructorName(${cons.valueAsLiteral});');
+    buffer.writeIndentedln(
+        'static const ${_makeValidIdentifier(cons.name)} = ${namespace.name}$constructorName(${cons.valueAsLiteral});');
   });
   buffer
     ..writeln()
@@ -662,7 +661,7 @@
   final nullOp = shouldBeOmittedIfNoValue ? '' : '?';
   final valueCode =
       _isSpecType(field.type) ? '${field.name}$nullOp.toJson()' : field.name;
-  buffer..writeIndented('''$mapName['${field.name}'] = $valueCode''');
+  buffer.writeIndented('''$mapName['${field.name}'] = $valueCode''');
   if (!field.allowsUndefined && !field.allowsNull) {
     buffer.write(''' ?? (throw '${field.name} is required but was not set')''');
   }
@@ -792,7 +791,7 @@
       buffer..write(' && (')..write('$valueCode.keys.every((item) => ');
       _writeTypeCheckCondition(
           buffer, interface, 'item', type.indexType, reporter);
-      buffer..write('&& $valueCode.values.every((item) => ');
+      buffer.write('&& $valueCode.values.every((item) => ');
       _writeTypeCheckCondition(
           buffer, interface, 'item', type.valueType, reporter);
       buffer.write(')))');
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index bc2bbad..0d46f4a 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -1498,6 +1498,7 @@
 
   @override
   void visitIfElement(IfElement node) {
+    _flowAnalysis?.flow?.ifStatement_conditionBegin();
     Expression condition = node.condition;
     InferenceContext.setType(condition, typeProvider.boolType);
     // TODO(scheglov) Do we need these checks for null?
@@ -1535,6 +1536,7 @@
   @override
   void visitIfStatement(IfStatement node) {
     checkUnreachableNode(node);
+    _flowAnalysis?.flow?.ifStatement_conditionBegin();
 
     Expression condition = node.condition;
 
diff --git a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
index 3f0e79b..7288dd8 100644
--- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -1184,6 +1184,7 @@
 
   @override
   StatementInferenceResult visitIfStatement(IfStatement node) {
+    inferrer.flowAnalysis.ifStatement_conditionBegin();
     InterfaceType expectedType =
         inferrer.coreTypes.boolRawType(inferrer.library.nonNullable);
     ExpressionInferenceResult conditionResult = inferrer.inferExpression(
@@ -1411,6 +1412,7 @@
       element.elementType = spreadElementType ?? const DynamicType();
       return new ExpressionInferenceResult(element.elementType, replacement);
     } else if (element is IfElement) {
+      inferrer.flowAnalysis.ifStatement_conditionBegin();
       DartType boolType =
           inferrer.coreTypes.boolRawType(inferrer.library.nonNullable);
       ExpressionInferenceResult conditionResult = inferrer.inferExpression(
@@ -1914,6 +1916,7 @@
 
       return replacement;
     } else if (entry is IfMapEntry) {
+      inferrer.flowAnalysis.ifStatement_conditionBegin();
       DartType boolType =
           inferrer.coreTypes.boolRawType(inferrer.library.nonNullable);
       ExpressionInferenceResult conditionResult = inferrer.inferExpression(
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart b/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart
index af42f14..2ae32bf 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart
@@ -377,9 +377,7 @@
           factMap[variable],
           isCheck.functionNestingLevel,
           isCheck.checkedType,
-          []
-            ..addAll(isCheck._blockingScopes)
-            ..add(_currentScope));
+          [...isCheck._blockingScopes, _currentScope]);
       factMap[variable] = facts;
       _factCacheState = facts;
     }
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt
index b370368..47cfd38 100644
--- a/pkg/front_end/test/spell_checking_list_common.txt
+++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -2783,6 +2783,7 @@
 speed
 speedup
 split
+splits
 splitter
 spread
 spreadable
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index 7363e60..7d4a32e 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -947,6 +947,7 @@
 
   @override
   DecoratedType visitIfStatement(IfStatement node) {
+    _flowAnalysis.ifStatement_conditionBegin();
     _checkExpressionNotNull(node.condition);
     NullabilityNode trueGuard;
     NullabilityNode falseGuard;
diff --git a/runtime/tests/vm/dart/regress_43682_test.dart b/runtime/tests/vm/dart/regress_43682_test.dart
new file mode 100644
index 0000000..63ef705
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_43682_test.dart
@@ -0,0 +1,32 @@
+// 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.
+
+// VMOptions=--deterministic --optimization_counter_threshold=20
+
+// Verifies that SSA construction doesn't crash when handling a Phi
+// corresponding to an expression temp in case of OSR with non-empty
+// expression stack.
+// Regression test for https://github.com/dart-lang/sdk/issues/43682.
+
+import 'package:expect/expect.dart';
+
+class Foo {
+  List<Object> data;
+  Foo(this.data);
+}
+
+Map<String, Foo> foo(List<Object> objects) {
+  Map<String, Foo> map = {};
+  // OSR happens during '...objects' spread, and Foo instance is already
+  // allocated and remains on the stack during OSR.
+  // OSR Phi corresponding to that value is stored into 'foo' local and
+  // then loaded from it, but it doesn't correspond to 'foo' environment slot.
+  final foo = new Foo([...objects]);
+  map['hi'] = foo;
+  return map;
+}
+
+main() {
+  Expect.equals(30, foo(List.filled(30, Object()))['hi']!.data.length);
+}
diff --git a/runtime/tests/vm/dart_2/regress_43682_test.dart b/runtime/tests/vm/dart_2/regress_43682_test.dart
new file mode 100644
index 0000000..15924b1
--- /dev/null
+++ b/runtime/tests/vm/dart_2/regress_43682_test.dart
@@ -0,0 +1,32 @@
+// 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.
+
+// VMOptions=--deterministic --optimization_counter_threshold=20
+
+// Verifies that SSA construction doesn't crash when handling a Phi
+// corresponding to an expression temp in case of OSR with non-empty
+// expression stack.
+// Regression test for https://github.com/dart-lang/sdk/issues/43682.
+
+import 'package:expect/expect.dart';
+
+class Foo {
+  List<Object> data;
+  Foo(this.data);
+}
+
+Map<String, Foo> foo(List<Object> objects) {
+  Map<String, Foo> map = {};
+  // OSR happens during '...objects' spread, and Foo instance is already
+  // allocated and remains on the stack during OSR.
+  // OSR Phi corresponding to that value is stored into 'foo' local and
+  // then loaded from it, but it doesn't correspond to 'foo' environment slot.
+  final foo = new Foo([...objects]);
+  map['hi'] = foo;
+  return map;
+}
+
+main() {
+  Expect.equals(30, foo(List.filled(30, Object()))['hi'].data.length);
+}
diff --git a/runtime/vm/compiler/backend/flow_graph.cc b/runtime/vm/compiler/backend/flow_graph.cc
index 9196b2d..82430b4 100644
--- a/runtime/vm/compiler/backend/flow_graph.cc
+++ b/runtime/vm/compiler/backend/flow_graph.cc
@@ -1453,13 +1453,23 @@
           // there as incoming value by renaming or it was stored there by
           // StoreLocal which took this Phi from another local via LoadLocal,
           // to which this reasoning applies recursively.
+          //
           // This means that we are guaranteed to process LoadLocal for a
-          // matching variable first.
+          // matching variable first, unless there was an OSR with a non-empty
+          // expression stack. In the latter case, Phi inserted by
+          // FlowGraph::AddSyntheticPhis for expression temp will not have an
+          // assigned type and may be accessed by StoreLocal and subsequent
+          // LoadLocal.
+          //
           if (!phi->HasType()) {
-            ASSERT((index < phi->block()->phis()->length()) &&
-                   ((*phi->block()->phis())[index] == phi));
-            phi->UpdateType(
-                CompileType::FromAbstractType(load->local().type()));
+            // Check if phi corresponds to the same slot.
+            auto* phis = phi->block()->phis();
+            if ((index < phis->length()) && (*phis)[index] == phi) {
+              phi->UpdateType(
+                  CompileType::FromAbstractType(load->local().type()));
+            } else {
+              ASSERT(IsCompiledForOsr() && (phi->block()->stack_depth() > 0));
+            }
           }
         }
         break;
diff --git a/runtime/vm/flags.cc b/runtime/vm/flags.cc
index ee2708f..901856c 100644
--- a/runtime/vm/flags.cc
+++ b/runtime/vm/flags.cc
@@ -95,11 +95,13 @@
   Flag(const char* name, const char* comment, FlagHandler handler)
       : name_(name),
         comment_(comment),
+        string_value_("false"),
         flag_handler_(handler),
         type_(kFlagHandler) {}
   Flag(const char* name, const char* comment, OptionHandler handler)
       : name_(name),
         comment_(comment),
+        string_value_(nullptr),
         option_handler_(handler),
         type_(kOptionHandler) {}
 
@@ -148,6 +150,7 @@
 
   const char* name_;
   const char* comment_;
+  const char* string_value_;
   union {
     void* addr_;
     bool* bool_ptr_;
@@ -332,9 +335,11 @@
       } else {
         return false;
       }
+      flag->string_value_ = argument;
       break;
     }
     case Flag::kOptionHandler: {
+      flag->string_value_ = argument;
       (flag->option_handler_)(argument);
       break;
     }
@@ -487,8 +492,7 @@
 
 #ifndef PRODUCT
 void Flags::PrintFlagToJSONArray(JSONArray* jsarr, const Flag* flag) {
-  if (flag->IsUnrecognized() || flag->type_ == Flag::kFlagHandler ||
-      flag->type_ == Flag::kOptionHandler) {
+  if (flag->IsUnrecognized()) {
     return;
   }
   JSONObject jsflag(jsarr);
@@ -521,6 +525,20 @@
       }
       break;
     }
+    case Flag::kFlagHandler: {
+      jsflag.AddProperty("_flagType", "Bool");
+      jsflag.AddProperty("valueAsString", flag->string_value_);
+      break;
+    }
+    case Flag::kOptionHandler: {
+      jsflag.AddProperty("_flagType", "String");
+      if (flag->string_value_ != nullptr) {
+        jsflag.AddProperty("valueAsString", flag->string_value_);
+      } else {
+        // valueAsString missing means NULL.
+      }
+      break;
+    }
     default:
       UNREACHABLE();
       break;
diff --git a/sdk/lib/async/stream.dart b/sdk/lib/async/stream.dart
index 8d2a625aa..150a759 100644
--- a/sdk/lib/async/stream.dart
+++ b/sdk/lib/async/stream.dart
@@ -483,11 +483,13 @@
    * On errors from this stream, the [onError] handler is called with the
    * error object and possibly a stack trace.
    *
-   * The [onError] callback must be of type `void onError(Object error)` or
-   * `void onError(Object error, StackTrace stackTrace)`. If [onError] accepts
-   * two arguments it is called with the error object and the stack trace
-   * (which could be `null` if this stream itself received an error without
-   * stack trace).
+   * The [onError] callback must be of type `void Function(Object error)` or
+   * `void Function(Object error, StackTrace)`.
+   * The function type determines whether [onError] is invoked with a stack
+   * trace argument.
+   * The stack trace argument may be [StackTrace.empty] if this stream received
+   * an error without a stack trace.
+   *
    * Otherwise it is called with just the error object.
    * If [onError] is omitted, any errors on this stream are considered unhandled,
    * and will be passed to the current [Zone]'s error handler.
diff --git a/tools/VERSION b/tools/VERSION
index e82a042..7d7e434 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 11
 PATCH 0
-PRERELEASE 196
+PRERELEASE 197
 PRERELEASE_PATCH 0
\ No newline at end of file