Version 2.13.0-228.0.dev

Merge commit '9b1583101ec9bdb39b66823eb303c6decd973e73' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index a7f5ac3..6ec266d 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -383,13 +383,9 @@
 
   final Map<String, Type> _members = {};
 
-  Node? _currentSwitch;
-
   Map<String, Map<String, String>> _promotionExceptions = {};
 
-  Statement? _currentBreakTarget;
-
-  Statement? _currentContinueTarget;
+  late final _typeAnalyzer = _MiniAstTypeAnalyzer(this);
 
   Harness({this.legacy = false, String? thisType})
       : thisType = thisType == null ? null : Type(thisType);
@@ -482,8 +478,8 @@
             this, assignedVariables)
         : FlowAnalysis<Node, Statement, Expression, Var, Type>(
             this, assignedVariables);
-    b._visit(this);
-    _flow.finish();
+    _typeAnalyzer.dispatchStatement(b);
+    _typeAnalyzer.finish();
   }
 
   @override
@@ -537,16 +533,6 @@
           'TODO(paulberry): least upper bound of $type1 and $type2');
     }
   }
-
-  void _visitLoopBody(Statement loop, Statement body) {
-    var previousBreakTarget = _currentBreakTarget;
-    var previousContinueTarget = _currentContinueTarget;
-    _currentBreakTarget = loop;
-    _currentContinueTarget = loop;
-    body._visit(this);
-    _currentBreakTarget = previousBreakTarget;
-    _currentContinueTarget = previousContinueTarget;
-  }
 }
 
 class LabeledStatement extends Statement {
@@ -564,9 +550,7 @@
 
   @override
   void _visit(Harness h) {
-    h._flow.labeledStatement_begin(this);
-    _body._visit(h);
-    h._flow.labeledStatement_end();
+    h._typeAnalyzer.analyzeLabeledStatement(this, _body);
   }
 }
 
@@ -644,11 +628,6 @@
   void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
     _body._preVisit(assignedVariables);
   }
-
-  void _visit(Harness h) {
-    h._flow.switchStatement_beginCase(_hasLabel, h._currentSwitch!);
-    _body._visit(h);
-  }
 }
 
 abstract class TryBuilder {
@@ -701,9 +680,7 @@
 
   @override
   Type _visit(Harness h) {
-    target._visit(h);
-    h._flow.asExpression_end(target, type);
-    return type;
+    return h._typeAnalyzer.analyzeTypeCast(this, target, type);
   }
 }
 
@@ -725,10 +702,7 @@
 
   @override
   void _visit(Harness h) {
-    h._flow.assert_begin();
-    h._flow.assert_afterCondition(condition.._visit(h));
-    message?._visit(h);
-    h._flow.assert_end();
+    h._typeAnalyzer.analyzeAssertStatement(condition, message);
   }
 }
 
@@ -750,9 +724,7 @@
 
   @override
   void _visit(Harness h) {
-    for (var statement in statements) {
-      statement._visit(h);
-    }
+    h._typeAnalyzer.analyzeBlock(statements);
   }
 }
 
@@ -769,8 +741,7 @@
 
   @override
   Type _visit(Harness h) {
-    h._flow.booleanLiteral(this, value);
-    return Type('bool');
+    return h._typeAnalyzer.analyzeBoolLiteral(this, value);
   }
 }
 
@@ -787,7 +758,7 @@
 
   @override
   void _visit(Harness h) {
-    h._flow.handleBreak(target ?? h._currentBreakTarget!);
+    h._typeAnalyzer.analyzeBreakStatement(target);
   }
 }
 
@@ -815,12 +786,6 @@
   void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
     _body._preVisit(assignedVariables);
   }
-
-  void _visit(Harness h) {
-    h._flow.tryCatchStatement_catchBegin(_exception, _stackTrace);
-    _body._visit(h);
-    h._flow.tryCatchStatement_catchEnd();
-  }
 }
 
 class _CheckAssigned extends Statement {
@@ -928,13 +893,8 @@
 
   @override
   Type _visit(Harness h) {
-    h._flow.conditional_conditionBegin();
-    h._flow.conditional_thenBegin(condition.._visit(h), this);
-    var ifTrueType = ifTrue._visit(h);
-    h._flow.conditional_elseBegin(ifTrue);
-    var ifFalseType = ifFalse._visit(h);
-    h._flow.conditional_end(this, ifFalse);
-    return h._lub(ifTrueType, ifFalseType);
+    return h._typeAnalyzer
+        .analyzeConditionalExpression(this, condition, ifTrue, ifFalse);
   }
 }
 
@@ -949,7 +909,7 @@
 
   @override
   void _visit(Harness h) {
-    h._flow.handleContinue(h._currentContinueTarget!);
+    h._typeAnalyzer.analyzeContinueStatement();
   }
 }
 
@@ -977,15 +937,9 @@
 
   @override
   void _visit(Harness h) {
-    var initializer = this.initializer;
-    if (initializer == null) {
-      h._flow.declare(variable, false);
-    } else {
-      var initializerType = initializer._visit(h);
-      h._flow.declare(variable, true);
-      h._flow.initialize(variable, initializerType, initializer,
-          isFinal: isFinal, isLate: isLate);
-    }
+    h._typeAnalyzer.analyzeVariableDeclaration(
+        this, variable.type, variable, initializer,
+        isFinal: isFinal, isLate: isLate);
   }
 }
 
@@ -1008,11 +962,7 @@
 
   @override
   void _visit(Harness h) {
-    h._flow.doStatement_bodyBegin(this);
-    h._visitLoopBody(this, body);
-    h._flow.doStatement_conditionBegin();
-    condition._visit(h);
-    h._flow.doStatement_end(condition);
+    h._typeAnalyzer.analyzeDoLoop(this, body, condition);
   }
 }
 
@@ -1034,11 +984,9 @@
 
   @override
   Type _visit(Harness h) {
-    var lhsType = lhs._visit(h);
-    h._flow.equalityOp_rightBegin(lhs, lhsType);
-    var rhsType = rhs._visit(h);
-    h._flow.equalityOp_end(this, rhs, rhsType, notEqual: isInverted);
-    return Type('bool');
+    var operatorName = isInverted ? '!=' : '==';
+    return h._typeAnalyzer
+        .analyzeBinaryExpression(this, lhs, operatorName, rhs);
   }
 }
 
@@ -1057,7 +1005,7 @@
 
   @override
   void _visit(Harness h) {
-    expr._visit(h);
+    h._typeAnalyzer.analyzeExpressionStatement(expr);
   }
 }
 
@@ -1104,13 +1052,25 @@
 
   @override
   void _visit(Harness h) {
-    initializer?._visit(h);
+    if (initializer != null) {
+      h._typeAnalyzer.dispatchStatement(initializer!);
+    } else {
+      h._typeAnalyzer.handleNoInitializer();
+    }
     h._flow.for_conditionBegin(this);
-    condition?._visit(h);
+    if (condition != null) {
+      h._typeAnalyzer.analyzeExpression(condition!);
+    } else {
+      h._typeAnalyzer.handleNoCondition();
+    }
     h._flow.for_bodyBegin(forCollection ? null : this, condition);
-    h._visitLoopBody(this, body);
+    h._typeAnalyzer._visitLoopBody(this, body);
     h._flow.for_updaterBegin();
-    updater?._visit(h);
+    if (updater != null) {
+      h._typeAnalyzer.analyzeExpression(updater!);
+    } else {
+      h._typeAnalyzer.handleNoStatement();
+    }
     h._flow.for_end();
   }
 }
@@ -1154,13 +1114,14 @@
 
   @override
   void _visit(Harness h) {
-    var iteratedType = h._getIteratedType(iterable._visit(h));
+    var iteratedType =
+        h._getIteratedType(h._typeAnalyzer.analyzeExpression(iterable));
     h._flow.forEach_bodyBegin(this);
     var variable = this.variable;
     if (variable != null && !declaresVariable) {
       h._flow.write(this, variable, iteratedType, null);
     }
-    h._visitLoopBody(this, body);
+    h._typeAnalyzer._visitLoopBody(this, body);
     h._flow.forEach_end();
   }
 }
@@ -1179,7 +1140,7 @@
 
   @override
   Type _visit(Harness h) {
-    var type = target._visit(h);
+    var type = h._typeAnalyzer.analyzeExpression(target);
     h._flow.forwardExpression(this, target);
     callback(h._flow.expressionInfoForTesting(this));
     return type;
@@ -1222,16 +1183,7 @@
 
   @override
   void _visit(Harness h) {
-    h._flow.ifStatement_conditionBegin();
-    h._flow.ifStatement_thenBegin(condition.._visit(h), this);
-    ifTrue._visit(h);
-    if (ifFalse == null) {
-      h._flow.ifStatement_end(false);
-    } else {
-      h._flow.ifStatement_elseBegin();
-      ifFalse!._visit(h);
-      h._flow.ifStatement_end(true);
-    }
+    h._typeAnalyzer.analyzeIfStatement(this, condition, ifTrue, ifFalse);
   }
 }
 
@@ -1252,11 +1204,7 @@
 
   @override
   Type _visit(Harness h) {
-    var lhsType = lhs._visit(h);
-    h._flow.ifNullExpression_rightBegin(lhs, lhsType);
-    var rhsType = rhs._visit(h);
-    h._flow.ifNullExpression_end();
-    return h._lub(h.promoteToNonNull(lhsType), rhsType);
+    return h._typeAnalyzer.analyzeIfNullExpression(this, lhs, rhs);
   }
 }
 
@@ -1277,8 +1225,8 @@
 
   @override
   Type _visit(Harness h) {
-    h._flow.isExpression_end(this, target.._visit(h), isInverted, type);
-    return Type('bool');
+    return h._typeAnalyzer
+        .analyzeTypeTest(this, target, type, isInverted: isInverted);
   }
 }
 
@@ -1300,7 +1248,7 @@
   @override
   void _visit(Harness h) {
     h._flow.functionExpression_begin(this);
-    body._visit(h);
+    h._typeAnalyzer.dispatchStatement(body);
     h._flow.functionExpression_end();
   }
 }
@@ -1325,10 +1273,9 @@
 
   @override
   Type _visit(Harness h) {
-    h._flow.logicalBinaryOp_begin();
-    h._flow.logicalBinaryOp_rightBegin(lhs.._visit(h), this, isAnd: isAnd);
-    h._flow.logicalBinaryOp_end(this, rhs.._visit(h), isAnd: isAnd);
-    return Type('bool');
+    var operatorName = isAnd ? '&&' : '||';
+    return h._typeAnalyzer
+        .analyzeBinaryExpression(this, lhs, operatorName, rhs);
   }
 }
 
@@ -1348,6 +1295,344 @@
   readWrite,
 }
 
+class _MiniAstTypeAnalyzer {
+  final Harness _harness;
+
+  Statement? _currentBreakTarget;
+
+  Statement? _currentContinueTarget;
+
+  late final Type boolType = Type('bool');
+
+  late final Type neverType = Type('Never');
+
+  late final Type nullType = Type('Null');
+
+  _MiniAstTypeAnalyzer(this._harness);
+
+  FlowAnalysis<Node, Statement, Expression, Var, Type> get flow =>
+      _harness._flow;
+
+  Type get thisType => _harness.thisType!;
+
+  void analyzeAssertStatement(Expression condition, Expression? message) {
+    flow.assert_begin();
+    analyzeExpression(condition);
+    flow.assert_afterCondition(condition);
+    if (message != null) {
+      analyzeExpression(message);
+    } else {
+      handleNoMessage();
+    }
+    flow.assert_end();
+  }
+
+  Type analyzeBinaryExpression(
+      Expression node, Expression lhs, String operatorName, Expression rhs) {
+    bool isEquals = false;
+    bool isNot = false;
+    bool isLogical = false;
+    bool isAnd = false;
+    switch (operatorName) {
+      case '==':
+        isEquals = true;
+        break;
+      case '!=':
+        isEquals = true;
+        isNot = true;
+        operatorName = '==';
+        break;
+      case '&&':
+        isLogical = true;
+        isAnd = true;
+        break;
+      case '||':
+        isLogical = true;
+        break;
+    }
+    if (operatorName == '==') {
+      isEquals = true;
+    } else if (operatorName == '!=') {
+      isEquals = true;
+      isNot = true;
+      operatorName = '==';
+    }
+    if (isLogical) {
+      flow.logicalBinaryOp_begin();
+    }
+    var leftType = analyzeExpression(lhs);
+    if (isEquals) {
+      flow.equalityOp_rightBegin(lhs, leftType);
+    } else if (isLogical) {
+      flow.logicalBinaryOp_rightBegin(lhs, node, isAnd: isAnd);
+    }
+    var rightType = analyzeExpression(rhs);
+    if (isEquals) {
+      flow.equalityOp_end(node, rhs, rightType, notEqual: isNot);
+    } else if (isLogical) {
+      flow.logicalBinaryOp_end(node, rhs, isAnd: isAnd);
+    }
+    return boolType;
+  }
+
+  void analyzeBlock(Iterable<Statement> statements) {
+    for (var statement in statements) {
+      dispatchStatement(statement);
+    }
+  }
+
+  Type analyzeBoolLiteral(Expression node, bool value) {
+    flow.booleanLiteral(node, value);
+    return boolType;
+  }
+
+  void analyzeBreakStatement(Statement? target) {
+    flow.handleBreak(target ?? _currentBreakTarget!);
+  }
+
+  Type analyzeConditionalExpression(Expression node, Expression condition,
+      Expression ifTrue, Expression ifFalse) {
+    flow.conditional_conditionBegin();
+    analyzeExpression(condition);
+    flow.conditional_thenBegin(condition, node);
+    var ifTrueType = analyzeExpression(ifTrue);
+    flow.conditional_elseBegin(ifTrue);
+    var ifFalseType = analyzeExpression(ifFalse);
+    flow.conditional_end(node, ifFalse);
+    return leastUpperBound(ifTrueType, ifFalseType);
+  }
+
+  void analyzeContinueStatement() {
+    flow.handleContinue(_currentContinueTarget!);
+  }
+
+  void analyzeDoLoop(Statement node, Statement body, Expression condition) {
+    flow.doStatement_bodyBegin(node);
+    _visitLoopBody(node, body);
+    flow.doStatement_conditionBegin();
+    analyzeExpression(condition);
+    flow.doStatement_end(condition);
+  }
+
+  Type analyzeExpression(Expression expression) {
+    return dispatchExpression(expression);
+  }
+
+  void analyzeExpressionStatement(Expression expression) {
+    analyzeExpression(expression);
+  }
+
+  Type analyzeIfNullExpression(
+      Expression node, Expression lhs, Expression rhs) {
+    var leftType = analyzeExpression(lhs);
+    flow.ifNullExpression_rightBegin(lhs, leftType);
+    var rightType = analyzeExpression(rhs);
+    flow.ifNullExpression_end();
+    return leastUpperBound(
+        flow.typeOperations.promoteToNonNull(leftType), rightType);
+  }
+
+  void analyzeIfStatement(Statement node, Expression condition,
+      Statement ifTrue, Statement? ifFalse) {
+    flow.ifStatement_conditionBegin();
+    analyzeExpression(condition);
+    flow.ifStatement_thenBegin(condition, node);
+    dispatchStatement(ifTrue);
+    if (ifFalse == null) {
+      handleNoStatement();
+      flow.ifStatement_end(false);
+    } else {
+      flow.ifStatement_elseBegin();
+      dispatchStatement(ifFalse);
+      flow.ifStatement_end(true);
+    }
+  }
+
+  void analyzeLabeledStatement(Statement node, Statement body) {
+    flow.labeledStatement_begin(node);
+    dispatchStatement(body);
+    flow.labeledStatement_end();
+  }
+
+  Type analyzeLogicalNot(Expression node, Expression expression) {
+    analyzeExpression(expression);
+    flow.logicalNot_end(node, expression);
+    return boolType;
+  }
+
+  Type analyzeNonNullAssert(Expression node, Expression expression) {
+    var type = analyzeExpression(expression);
+    flow.nonNullAssert_end(expression);
+    return flow.typeOperations.promoteToNonNull(type);
+  }
+
+  Type analyzeNullLiteral(Expression node) {
+    flow.nullLiteral(node);
+    return nullType;
+  }
+
+  Type analyzeParenthesizedExpression(Expression node, Expression expression) {
+    var type = analyzeExpression(expression);
+    flow.parenthesizedExpression(node, expression);
+    return type;
+  }
+
+  Type analyzePropertyGet(
+      Expression node, Expression receiver, String propertyName) {
+    var receiverType = analyzeExpression(receiver);
+    var type = _lookupMember(node, receiverType, propertyName);
+    flow.propertyGet(node, receiver, propertyName, propertyName, type);
+    return type;
+  }
+
+  void analyzeReturnStatement() {
+    flow.handleExit();
+  }
+
+  void analyzeSwitchStatement(
+      _Switch node, Expression expression, List<SwitchCase> cases) {
+    analyzeExpression(expression);
+    flow.switchStatement_expressionEnd(node);
+    var previousBreakTarget = _currentBreakTarget;
+    _currentBreakTarget = node;
+    for (var case_ in cases) {
+      flow.switchStatement_beginCase(case_._hasLabel, node);
+      dispatchStatement(case_._body);
+    }
+    _currentBreakTarget = previousBreakTarget;
+    flow.switchStatement_end(isSwitchExhaustive(node));
+  }
+
+  Type analyzeThis(Expression node) {
+    var thisType = this.thisType;
+    flow.thisOrSuper(node, thisType);
+    return thisType;
+  }
+
+  Type analyzeThisPropertyGet(Expression node, String propertyName) {
+    var type = _lookupMember(node, thisType, propertyName);
+    flow.thisOrSuperPropertyGet(node, propertyName, propertyName, type);
+    return type;
+  }
+
+  Type analyzeThrow(Expression node, Expression expression) {
+    analyzeExpression(expression);
+    flow.handleExit();
+    return neverType;
+  }
+
+  void analyzeTryStatement(Statement node, Statement body,
+      Iterable<_CatchClause> catchClauses, Statement? finallyBlock) {
+    if (finallyBlock != null) {
+      flow.tryFinallyStatement_bodyBegin();
+    }
+    if (catchClauses.isNotEmpty) {
+      flow.tryCatchStatement_bodyBegin();
+    }
+    dispatchStatement(body);
+    if (catchClauses.isNotEmpty) {
+      flow.tryCatchStatement_bodyEnd(body);
+      for (var catch_ in catchClauses) {
+        flow.tryCatchStatement_catchBegin(
+            catch_._exception, catch_._stackTrace);
+        dispatchStatement(catch_._body);
+        flow.tryCatchStatement_catchEnd();
+      }
+      flow.tryCatchStatement_end();
+    }
+    if (finallyBlock != null) {
+      flow.tryFinallyStatement_finallyBegin(
+          catchClauses.isNotEmpty ? node : body);
+      dispatchStatement(finallyBlock);
+      flow.tryFinallyStatement_end();
+    } else {
+      handleNoStatement();
+    }
+  }
+
+  Type analyzeTypeCast(Expression node, Expression expression, Type type) {
+    analyzeExpression(expression);
+    flow.asExpression_end(expression, type);
+    return type;
+  }
+
+  Type analyzeTypeTest(Expression node, Expression expression, Type type,
+      {bool isInverted = false}) {
+    analyzeExpression(expression);
+    flow.isExpression_end(node, expression, isInverted, type);
+    return boolType;
+  }
+
+  void analyzeVariableDeclaration(
+      Statement node, Type type, Var variable, Expression? initializer,
+      {required bool isFinal, required bool isLate}) {
+    if (initializer == null) {
+      handleNoInitializer();
+      flow.declare(variable, false);
+    } else {
+      var initializerType = analyzeExpression(initializer);
+      flow.declare(variable, true);
+      flow.initialize(variable, initializerType, initializer,
+          isFinal: isFinal, isLate: isLate);
+    }
+  }
+
+  Type analyzeVariableGet(
+      Expression node, Var variable, void Function(Type?)? callback) {
+    var promotedType = flow.variableRead(node, variable);
+    callback?.call(promotedType);
+    return promotedType ?? variable.type;
+  }
+
+  void analyzeWhileLoop(Statement node, Expression condition, Statement body) {
+    flow.whileStatement_conditionBegin(node);
+    analyzeExpression(condition);
+    flow.whileStatement_bodyBegin(node, condition);
+    _visitLoopBody(node, body);
+    flow.whileStatement_end();
+  }
+
+  Type dispatchExpression(Expression expression) => expression._visit(_harness);
+
+  void dispatchStatement(Statement statement) => statement._visit(_harness);
+
+  void finish() {
+    flow.finish();
+  }
+
+  void handleNoCondition() {}
+
+  void handleNoInitializer() {}
+
+  void handleNoMessage() {}
+
+  void handleNoStatement() {}
+
+  bool isSwitchExhaustive(_Switch node) {
+    return node.isExhaustive;
+  }
+
+  Type leastUpperBound(Type t1, Type t2) => _harness._lub(t1, t2);
+
+  Type lookupInterfaceMember(Node node, Type receiverType, String memberName) {
+    return _harness.getMember(receiverType, memberName);
+  }
+
+  Type _lookupMember(Expression node, Type receiverType, String memberName) {
+    return lookupInterfaceMember(node, receiverType, memberName);
+  }
+
+  void _visitLoopBody(Statement loop, Statement body) {
+    var previousBreakTarget = _currentBreakTarget;
+    var previousContinueTarget = _currentContinueTarget;
+    _currentBreakTarget = loop;
+    _currentContinueTarget = loop;
+    dispatchStatement(body);
+    _currentBreakTarget = previousBreakTarget;
+    _currentContinueTarget = previousContinueTarget;
+  }
+}
+
 class _NonNullAssert extends Expression {
   final Expression operand;
 
@@ -1363,9 +1648,7 @@
 
   @override
   Type _visit(Harness h) {
-    var type = operand._visit(h);
-    h._flow.nonNullAssert_end(operand);
-    return h.promoteToNonNull(type);
+    return h._typeAnalyzer.analyzeNonNullAssert(this, operand);
   }
 }
 
@@ -1384,8 +1667,7 @@
 
   @override
   Type _visit(Harness h) {
-    h._flow.logicalNot_end(this, operand.._visit(h));
-    return Type('bool');
+    return h._typeAnalyzer.analyzeLogicalNot(this, operand);
   }
 }
 
@@ -1407,9 +1689,9 @@
 
   @override
   Type _visit(Harness h) {
-    var lhsType = lhs._visit(h);
+    var lhsType = h._typeAnalyzer.analyzeExpression(lhs);
     h._flow.nullAwareAccess_rightBegin(isCascaded ? null : lhs, lhsType);
-    var rhsType = rhs._visit(h);
+    var rhsType = h._typeAnalyzer.analyzeExpression(rhs);
     h._flow.nullAwareAccess_end();
     return h._lub(rhsType, Type('Null'));
   }
@@ -1426,8 +1708,7 @@
 
   @override
   Type _visit(Harness h) {
-    h._flow.nullLiteral(this);
-    return Type('Null');
+    return h._typeAnalyzer.analyzeNullLiteral(this);
   }
 }
 
@@ -1446,9 +1727,7 @@
 
   @override
   Type _visit(Harness h) {
-    var type = expr._visit(h);
-    h._flow.parenthesizedExpression(this, expr);
-    return type;
+    return h._typeAnalyzer.analyzeParenthesizedExpression(this, expr);
   }
 }
 
@@ -1482,10 +1761,7 @@
 
   @override
   Type _visit(Harness h) {
-    var targetType = target._visit(h);
-    var propertyType = h.getMember(targetType, propertyName);
-    h._flow.propertyGet(this, target, propertyName, propertyName, propertyType);
-    return propertyType;
+    return h._typeAnalyzer.analyzePropertyGet(this, target, propertyName);
   }
 
   @override
@@ -1506,7 +1782,7 @@
 
   @override
   void _visit(Harness h) {
-    h._flow.handleExit();
+    h._typeAnalyzer.analyzeReturnStatement();
   }
 }
 
@@ -1542,18 +1818,7 @@
 
   @override
   void _visit(Harness h) {
-    expression._visit(h);
-    h._flow.switchStatement_expressionEnd(this);
-    var oldSwitch = h._currentSwitch;
-    var previousBreakTarget = h._currentBreakTarget;
-    h._currentSwitch = this;
-    h._currentBreakTarget = this;
-    for (var case_ in cases) {
-      case_._visit(h);
-    }
-    h._currentSwitch = oldSwitch;
-    h._currentBreakTarget = previousBreakTarget;
-    h._flow.switchStatement_end(isExhaustive);
+    h._typeAnalyzer.analyzeSwitchStatement(this, expression, cases);
   }
 }
 
@@ -1566,9 +1831,7 @@
 
   @override
   Type _visit(Harness h) {
-    var thisType = h.thisType!;
-    h._flow.thisOrSuper(this, thisType);
-    return thisType;
+    return h._typeAnalyzer.analyzeThis(this);
   }
 }
 
@@ -1582,9 +1845,7 @@
 
   @override
   Type _visit(Harness h) {
-    var type = h.getMember(h.thisType!, propertyName);
-    h._flow.thisOrSuperPropertyGet(this, propertyName, propertyName, type);
-    return type;
+    return h._typeAnalyzer.analyzeThisPropertyGet(this, propertyName);
   }
 }
 
@@ -1603,9 +1864,7 @@
 
   @override
   Type _visit(Harness h) {
-    operand._visit(h);
-    h._flow.handleExit();
-    return Type('Never');
+    return h._typeAnalyzer.analyzeThrow(this, operand);
   }
 }
 
@@ -1653,26 +1912,7 @@
 
   @override
   void _visit(Harness h) {
-    if (_finally != null) {
-      h._flow.tryFinallyStatement_bodyBegin();
-    }
-    if (_catches.isNotEmpty) {
-      h._flow.tryCatchStatement_bodyBegin();
-    }
-    _body._visit(h);
-    if (_catches.isNotEmpty) {
-      h._flow.tryCatchStatement_bodyEnd(_body);
-      for (var catch_ in _catches) {
-        catch_._visit(h);
-      }
-      h._flow.tryCatchStatement_end();
-    }
-    if (_finally != null) {
-      h._flow
-          .tryFinallyStatement_finallyBegin(_catches.isNotEmpty ? this : _body);
-      _finally!._visit(h);
-      h._flow.tryFinallyStatement_end();
-    }
+    h._typeAnalyzer.analyzeTryStatement(this, _body, _catches, _finally);
   }
 }
 
@@ -1699,9 +1939,7 @@
 
   @override
   Type _visit(Harness h) {
-    var readResult = h._flow.variableRead(this, variable);
-    callback?.call(readResult);
-    return readResult ?? variable.type;
+    return h._typeAnalyzer.analyzeVariableGet(this, variable, callback);
   }
 
   @override
@@ -1730,11 +1968,7 @@
 
   @override
   void _visit(Harness h) {
-    h._flow.whileStatement_conditionBegin(this);
-    condition._visit(h);
-    h._flow.whileStatement_bodyBegin(this, condition);
-    h._visitLoopBody(this, body);
-    h._flow.whileStatement_end();
+    h._typeAnalyzer.analyzeWhileLoop(this, condition, body);
   }
 }
 
@@ -1755,7 +1989,7 @@
 
   @override
   Type _visit(Harness h) {
-    var type = target._visit(h);
+    var type = h._typeAnalyzer.analyzeExpression(target);
     h._flow.forwardExpression(this, target);
     Type.withComparisonsAllowed(() {
       callback(h._flow.whyNotPromoted(this)());
@@ -1815,9 +2049,13 @@
 
   @override
   Type _visit(Harness h) {
-    before?._visit(h);
-    var type = expr._visit(h);
-    after?._visit(h);
+    if (before != null) {
+      h._typeAnalyzer.dispatchStatement(before!);
+    }
+    var type = h._typeAnalyzer.analyzeExpression(expr);
+    if (after != null) {
+      h._typeAnalyzer.dispatchStatement(after!);
+    }
     h._flow.forwardExpression(this, expr);
     return type;
   }
@@ -1848,9 +2086,9 @@
     if (rhs == null) {
       // We are simulating an increment/decrement operation.
       // TODO(paulberry): Make a separate node type for this.
-      type = lhs._visit(h);
+      type = h._typeAnalyzer.analyzeExpression(lhs);
     } else {
-      type = rhs._visit(h);
+      type = h._typeAnalyzer.analyzeExpression(rhs);
     }
     lhs._visitWrite(h, this, type, rhs);
     return type;
diff --git a/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart b/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
index 679b457..9bd0039 100644
--- a/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
+++ b/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart = 2.9
-
 import 'dart:math';
 
 import 'package:analysis_server/src/protocol_server.dart' hide Element;
@@ -21,6 +19,7 @@
 import 'package:analyzer/src/generated/java_core.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer_plugin/utilities/range_factory.dart';
+import 'package:collection/collection.dart';
 
 /// An enumeration of possible statement completion kinds.
 class DartStatementCompletion {
@@ -105,20 +104,23 @@
 
   final StatementCompletionContext statementContext;
   final CorrectionUtils utils;
-  AstNode node;
-  StatementCompletion completion;
+
+  /// TODO(brianwilkerson) Refactor the code so that the completion is returned
+  ///  from the methods in which it's computed rather than being a field that we
+  ///  have to test.
+  StatementCompletion? completion;
   SourceChange change = SourceChange('statement-completion');
   List<engine.AnalysisError> errors = [];
   final Map<String, LinkedEditGroup> linkedPositionGroups =
       <String, LinkedEditGroup>{};
-  Position exitPosition;
+  Position? exitPosition;
 
   StatementCompletionProcessor(this.statementContext)
       : utils = CorrectionUtils(statementContext.resolveResult);
 
   String get eol => utils.endOfLine;
 
-  String get file => statementContext.resolveResult.path;
+  String get file => statementContext.resolveResult.path!;
 
   LineInfo get lineInfo => statementContext.resolveResult.lineInfo;
 
@@ -126,29 +128,27 @@
 
   Source get source => unitElement.source;
 
-  CompilationUnit get unit => statementContext.resolveResult.unit;
+  CompilationUnit get unit => statementContext.resolveResult.unit!;
 
-  CompilationUnitElement get unitElement =>
-      statementContext.resolveResult.unit.declaredElement;
+  CompilationUnitElement get unitElement => unit.declaredElement!;
 
   Future<StatementCompletion> compute() async {
-    node = _selectedNode();
+    var node = _selectedNode();
     if (node == null) {
       return NO_COMPLETION;
     }
     node = node.thisOrAncestorMatching(
         (n) => n is Statement || _isNonStatementDeclaration(n));
     if (node == null) {
-      return _complete_simpleEnter() ? completion : NO_COMPLETION;
+      return _complete_simpleEnter() ? completion! : NO_COMPLETION;
     }
     if (node is Block) {
-      Block blockNode = node;
-      if (blockNode.statements.isNotEmpty) {
-        node = blockNode.statements.last;
+      if (node.statements.isNotEmpty) {
+        node = node.statements.last;
       }
     }
     if (_isEmptyStatementOrEmptyBlock(node)) {
-      node = node.parent;
+      node = node.parent!;
     }
     for (var error in statementContext.resolveResult.errors) {
       if (error.offset >= node.offset && error.offset <= node.end) {
@@ -158,41 +158,41 @@
       }
     }
 
-    _checkExpressions();
+    _checkExpressions(node);
     if (node is Statement) {
       if (errors.isEmpty) {
-        if (_complete_ifStatement() ||
-            _complete_forStatement2() ||
-            _complete_whileStatement() ||
-            _complete_controlFlowBlock()) {
-          return completion;
+        if (_complete_ifStatement(node) ||
+            _complete_forStatement2(node) ||
+            _complete_whileStatement(node) ||
+            _complete_controlFlowBlock(node)) {
+          return completion!;
         }
       } else {
-        if (_complete_ifStatement() ||
-            _complete_doStatement() ||
-            _complete_forStatement2() ||
-            _complete_functionDeclarationStatement() ||
-            _complete_switchStatement() ||
-            _complete_tryStatement() ||
-            _complete_whileStatement() ||
-            _complete_controlFlowBlock() ||
-            _complete_simpleSemicolon() ||
-            _complete_methodCall()) {
-          return completion;
+        if (_complete_ifStatement(node) ||
+            _complete_doStatement(node) ||
+            _complete_forStatement2(node) ||
+            _complete_functionDeclarationStatement(node) ||
+            _complete_switchStatement(node) ||
+            _complete_tryStatement(node) ||
+            _complete_whileStatement(node) ||
+            _complete_controlFlowBlock(node) ||
+            _complete_simpleSemicolon(node) ||
+            _complete_methodCall(node)) {
+          return completion!;
         }
       }
     } else if (node is Declaration) {
       if (errors.isNotEmpty) {
-        if (_complete_classDeclaration() ||
-            _complete_variableDeclaration() ||
-            _complete_simpleSemicolon() ||
-            _complete_functionDeclaration()) {
-          return completion;
+        if (_complete_classDeclaration(node) ||
+            _complete_variableDeclaration(node) ||
+            _complete_simpleSemicolon(node) ||
+            _complete_functionDeclaration(node)) {
+          return completion!;
         }
       }
     }
     if (_complete_simpleEnter()) {
-      return completion;
+      return completion!;
     }
     return NO_COMPLETION;
   }
@@ -259,16 +259,16 @@
     return text;
   }
 
-  void _checkExpressions() {
+  void _checkExpressions(AstNode node) {
     // Note: This may queue edits that have to be accounted for later.
     // See _lengthOfInsertions().
-    AstNode errorMatching(errorCode, {partialMatch}) {
+    AstNode? errorMatching(errorCode, {partialMatch}) {
       var error = _findError(errorCode, partialMatch: partialMatch);
       if (error == null) {
         return null;
       }
       var expr = _selectedNode();
-      return (expr.thisOrAncestorOfType<StringInterpolation>() == null)
+      return (expr?.thisOrAncestorOfType<StringInterpolation>() == null)
           ? expr
           : null;
     }
@@ -306,9 +306,8 @@
         errorMatching(ScannerErrorCode.EXPECTED_TOKEN, partialMatch: "']'");
     if (expr != null) {
       expr = expr.thisOrAncestorOfType<ListLiteral>();
-      if (expr != null) {
-        ListLiteral lit = expr;
-        if (lit.rightBracket.isSynthetic) {
+      if (expr is ListLiteral) {
+        if (expr.rightBracket.isSynthetic) {
           var src = utils.getNodeText(expr).trim();
           var loc = expr.offset + src.length;
           if (src.contains(eol)) {
@@ -351,14 +350,13 @@
     */
   }
 
-  bool _complete_classDeclaration() {
+  bool _complete_classDeclaration(AstNode node) {
     if (node is! ClassDeclaration) {
       return false;
     }
-    ClassDeclaration decl = node;
-    if (decl.leftBracket.isSynthetic && errors.length == 1) {
+    if (node.leftBracket.isSynthetic && errors.length == 1) {
       // The space before the left brace is assumed to exist, even if it does not.
-      var sb = SourceBuilder(file, decl.end - 1);
+      var sb = SourceBuilder(file, node.end - 1);
       sb.append(' ');
       _appendEmptyBraces(sb, true);
       _insertBuilder(sb);
@@ -368,19 +366,18 @@
     return false;
   }
 
-  bool _complete_controlFlowBlock() {
+  bool _complete_controlFlowBlock(AstNode node) {
     var expr = (node is ExpressionStatement)
-        ? (node as ExpressionStatement).expression
-        : (node is ReturnStatement
-            ? (node as ReturnStatement).expression
-            : null);
+        ? node.expression
+        : (node is ReturnStatement ? node.expression : null);
     if (!(node is ReturnStatement || expr is ThrowExpression)) {
       return false;
     }
-    if (node.parent is! Block) {
+    var parent = node.parent;
+    if (parent is! Block) {
       return false;
     }
-    var outer = node.parent.parent;
+    var outer = parent.parent;
     if (!(outer is DoStatement ||
         outer is ForStatement ||
         outer is IfStatement ||
@@ -399,12 +396,10 @@
         // Because of this, check for length == 0 rather than isSynthetic.
         if (expr == null || expr.length == 0) {
           if (node is ReturnStatement) {
-            insertOffset = (node as ReturnStatement).returnKeyword.end;
+            insertOffset = node.returnKeyword.end;
           } else if (node is ExpressionStatement) {
             insertOffset =
-                ((node as ExpressionStatement).expression as ThrowExpression)
-                    .throwKeyword
-                    .end;
+                (node.expression as ThrowExpression).throwKeyword.end;
           } else {
             insertOffset = node.end; // Not reached.
           }
@@ -417,71 +412,65 @@
         delta = 1;
       }
     }
-    var offset = _appendNewlinePlusIndentAt(node.parent.end);
+    var offset = _appendNewlinePlusIndentAt(parent.end);
     exitPosition = Position(file, offset + delta + previousInsertions);
     _setCompletion(DartStatementCompletion.COMPLETE_CONTROL_FLOW_BLOCK);
     return true;
   }
 
-  bool _complete_doStatement() {
+  bool _complete_doStatement(AstNode node) {
     if (node is! DoStatement) {
       return false;
     }
-    DoStatement statement = node;
-    var sb = _sourceBuilderAfterKeyword(statement.doKeyword);
-    var hasWhileKeyword =
-        statement.whileKeyword != null && !statement.whileKeyword.isSynthetic;
+    var sb = _sourceBuilderAfterKeyword(node, node.doKeyword);
+    var hasWhileKeyword = !node.whileKeyword.isSynthetic;
     var exitDelta = 0;
-    if (!_statementHasValidBody(statement.doKeyword, statement.body)) {
-      var text = utils.getNodeText(statement.body);
+    if (!_statementHasValidBody(node.doKeyword, node.body)) {
+      var text = utils.getNodeText(node.body);
       var delta = 0;
       if (text.startsWith(';')) {
         delta = 1;
-        _addReplaceEdit(range.startLength(statement.body, delta), '');
+        _addReplaceEdit(range.startLength(node.body, delta), '');
         if (hasWhileKeyword) {
-          text = utils.getNodeText(statement);
+          text = utils.getNodeText(node);
           if (text.indexOf(RegExp(r'do\s*;\s*while')) == 0) {
             var end = text.indexOf('while');
             var start = text.indexOf(';') + 1;
             delta += end - start - 1;
-            _addReplaceEdit(
-                SourceRange(start + statement.offset, end - start), ' ');
+            _addReplaceEdit(SourceRange(start + node.offset, end - start), ' ');
           }
         }
         sb = SourceBuilder(file, sb.offset + delta);
         sb.append(' ');
       }
-      _appendEmptyBraces(sb,
-          !(hasWhileKeyword && _isSyntheticExpression(statement.condition)));
+      _appendEmptyBraces(
+          sb, !(hasWhileKeyword && _isSyntheticExpression(node.condition)));
       if (delta != 0) {
         exitDelta = sb.length - delta;
       }
-    } else if (_isEmptyBlock(statement.body)) {
-      sb = SourceBuilder(sb.file, statement.body.end);
+    } else if (_isEmptyBlock(node.body)) {
+      sb = SourceBuilder(sb.file, node.body.end);
     }
-    SourceBuilder sb2;
+    SourceBuilder? sb2;
     if (hasWhileKeyword) {
-      var stmt = _KeywordConditionBlockStructure(
-          statement.whileKeyword,
-          statement.leftParenthesis,
-          statement.condition,
-          statement.rightParenthesis,
-          null);
-      sb2 = _complete_keywordCondition(stmt);
+      var stmt = _KeywordConditionBlockStructure(node.whileKeyword,
+          node.leftParenthesis, node.condition, node.rightParenthesis, null);
+      sb2 = _complete_keywordCondition(node, stmt);
       if (sb2 == null) {
         return false;
       }
       if (sb2.length == 0) {
         // true if condition is '()'
+        final exitPosition = this.exitPosition;
         if (exitPosition != null) {
-          if (statement.semicolon.isSynthetic) {
+          if (node.semicolon.isSynthetic) {
             _insertBuilder(sb);
             sb = SourceBuilder(file, exitPosition.offset + 1);
             sb.append(';');
           }
         }
       } else {
-        if (sb.exitOffset == null && sb2?.exitOffset != null) {
+        if (sb.exitOffset == null && sb2.exitOffset != null) {
           _insertBuilder(sb);
           sb = sb2;
           sb.append(';');
@@ -497,7 +486,7 @@
     _insertBuilder(sb);
     if (exitDelta != 0) {
       exitPosition =
-          Position(exitPosition.file, exitPosition.offset + exitDelta);
+          Position(exitPosition!.file, exitPosition!.offset + exitDelta);
     }
     _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT);
     return true;
@@ -514,6 +503,7 @@
       throw StateError('Unrecognized for loop parts');
     }
     return _complete_forEachStatementRest(
+        forNode,
         forNode.forKeyword,
         forNode.leftParenthesis,
         name,
@@ -524,11 +514,12 @@
   }
 
   bool _complete_forEachStatementRest(
+      AstNode node,
       Token forKeyword,
       Token leftParenthesis,
-      AstNode name,
+      AstNode? name,
       Token inKeyword,
-      Expression iterable,
+      Expression? iterable,
       Token rightParenthesis,
       Statement body) {
     if (inKeyword.isSynthetic) {
@@ -577,7 +568,7 @@
         return false;
       }
       // keywordOnly (unit test name suffix that exercises this branch)
-      sb = _sourceBuilderAfterKeyword(forNode.forKeyword);
+      sb = _sourceBuilderAfterKeyword(forNode, forNode.forKeyword);
       sb.append('(');
       sb.setExitOffset();
       sb.append(')');
@@ -625,18 +616,19 @@
         sb = SourceBuilder(file, forNode.rightParenthesis.offset);
       } else if (forParts is ForPartsWithExpression &&
           forParts.initialization is SimpleIdentifier &&
-          forParts.initialization.beginToken.lexeme == 'in') {
+          forParts.initialization!.beginToken.lexeme == 'in') {
         // looks like a for/each statement missing the loop variable
         return _complete_forEachStatementRest(
+            forNode,
             forNode.forKeyword,
             forNode.leftParenthesis,
             null,
-            forParts.initialization.beginToken,
+            forParts.initialization!.beginToken,
             null,
             forNode.rightParenthesis,
             forNode.body);
       } else {
-        var start = forParts.condition.offset + forParts.condition.length;
+        var start = forParts.condition!.offset + forParts.condition!.length;
         var text = utils.getNodeText(forNode).substring(start - forNode.offset);
         if (text.startsWith(RegExp(r'\s*\)'))) {
           // missingLeftSeparator
@@ -651,12 +643,12 @@
         }
       }
     }
-    if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) {
+    var body = forNode.body;
+    if (!_statementHasValidBody(forNode.forKeyword, body)) {
       // keywordOnly, noError
       sb.append(' ');
       _appendEmptyBraces(sb, true /*exitPosition == null*/);
-    } else if (forNode.body is Block) {
-      Block body = forNode.body;
+    } else if (body is Block) {
       if (body.rightBracket.end <= selectionOffset) {
         // emptyInitializersAfterBody
         errors = []; // Ignore errors; they are for previous statement.
@@ -668,8 +660,7 @@
     return true;
   }
 
-  bool _complete_forStatement2() {
-    var node = this.node;
+  bool _complete_forStatement2(AstNode node) {
     if (node is ForStatement) {
       var forLoopParts = node.forLoopParts;
       if (forLoopParts is ForParts) {
@@ -681,7 +672,7 @@
     return false;
   }
 
-  bool _complete_functionDeclaration() {
+  bool _complete_functionDeclaration(AstNode node) {
     if (node is! MethodDeclaration && node is! FunctionDeclaration) {
       return false;
     }
@@ -698,11 +689,19 @@
 
     int paramListEnd;
     if (node is FunctionDeclaration) {
-      FunctionDeclaration func = node;
-      paramListEnd = computeExitPos(func.functionExpression.parameters);
+      var parameters = node.functionExpression.parameters;
+      if (parameters == null) {
+        return false;
+      }
+      paramListEnd = computeExitPos(parameters);
+    } else if (node is MethodDeclaration) {
+      var parameters = node.parameters;
+      if (parameters == null) {
+        return false;
+      }
+      paramListEnd = computeExitPos(parameters);
     } else {
-      MethodDeclaration meth = node;
-      paramListEnd = computeExitPos(meth.parameters);
+      return false;
     }
     var sb = SourceBuilder(file, paramListEnd);
     if (needsParen) {
@@ -715,26 +714,23 @@
     return true;
   }
 
-  bool _complete_functionDeclarationStatement() {
+  bool _complete_functionDeclarationStatement(AstNode node) {
     if (node is! FunctionDeclarationStatement) {
       return false;
     }
     var error = _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'");
     if (error != null) {
-      FunctionDeclarationStatement stmt = node;
-      var src = utils.getNodeText(stmt);
-      var insertOffset = stmt.functionDeclaration.end - 1;
-      if (stmt.functionDeclaration.functionExpression.body
-          is ExpressionFunctionBody) {
-        ExpressionFunctionBody fnb =
-            stmt.functionDeclaration.functionExpression.body;
-        var fnbOffset = fnb.functionDefinition.offset;
-        var fnSrc = src.substring(fnbOffset - stmt.offset);
+      var src = utils.getNodeText(node);
+      var insertOffset = node.functionDeclaration.end - 1;
+      var body = node.functionDeclaration.functionExpression.body;
+      if (body is ExpressionFunctionBody) {
+        var fnbOffset = body.functionDefinition.offset;
+        var fnSrc = src.substring(fnbOffset - node.offset);
         if (!fnSrc.startsWith('=>')) {
           return false;
         }
         var delta = 0;
-        if (fnb.expression.isSynthetic) {
+        if (body.expression.isSynthetic) {
           if (!fnSrc.startsWith('=> ')) {
             _addInsertEdit(insertOffset, ' ');
             delta = 1;
@@ -754,12 +750,12 @@
     return false;
   }
 
-  bool _complete_ifOrWhileStatement(
+  bool _complete_ifOrWhileStatement(AstNode node,
       _KeywordConditionBlockStructure statement, StatementCompletionKind kind) {
-    if (_statementHasValidBody(statement.keyword, statement.block)) {
+    if (_statementHasValidBody(statement.keyword, statement.block!)) {
       return false;
     }
-    var sb = _complete_keywordCondition(statement);
+    var sb = _complete_keywordCondition(node, statement);
     if (sb == null) {
       return false;
     }
@@ -768,24 +764,24 @@
     _appendEmptyBraces(sb, exitPosition == null);
     _insertBuilder(sb);
     if (overshoot != 0) {
-      exitPosition = _newPosition(exitPosition.offset - overshoot);
+      exitPosition = _newPosition(exitPosition!.offset - overshoot);
     }
     _setCompletion(kind);
     return true;
   }
 
-  bool _complete_ifStatement() {
+  bool _complete_ifStatement(AstNode node) {
     if (node is! IfStatement) {
       return false;
     }
-    IfStatement ifNode = node;
-    if (ifNode.elseKeyword != null) {
-      if (selectionOffset >= ifNode.elseKeyword.end &&
-          _isEmptyStatement(ifNode.elseStatement)) {
+    var elseKeyword = node.elseKeyword;
+    if (elseKeyword != null) {
+      if (selectionOffset >= elseKeyword.end &&
+          _isEmptyStatement(node.elseStatement)) {
         var sb = SourceBuilder(file, selectionOffset);
-        var src = utils.getNodeText(ifNode);
+        var src = utils.getNodeText(node);
         if (!src
-            .substring(ifNode.elseKeyword.end - node.offset)
+            .substring(elseKeyword.end - node.offset)
             .startsWith(RegExp(r'[ \t]'))) {
           sb.append(' ');
         }
@@ -797,24 +793,24 @@
       return false;
     }
     var stmt = _KeywordConditionBlockStructure(
-        ifNode.ifKeyword,
-        ifNode.leftParenthesis,
-        ifNode.condition,
-        ifNode.rightParenthesis,
-        ifNode.thenStatement);
+        node.ifKeyword,
+        node.leftParenthesis,
+        node.condition,
+        node.rightParenthesis,
+        node.thenStatement);
     return _complete_ifOrWhileStatement(
-        stmt, DartStatementCompletion.COMPLETE_IF_STMT);
+        node, stmt, DartStatementCompletion.COMPLETE_IF_STMT);
   }
 
-  SourceBuilder _complete_keywordCondition(
-      _KeywordConditionBlockStructure statement) {
+  SourceBuilder? _complete_keywordCondition(
+      AstNode node, _KeywordConditionBlockStructure statement) {
     SourceBuilder sb;
     if (statement.leftParenthesis.isSynthetic) {
       if (!statement.rightParenthesis.isSynthetic) {
         // Quite unlikely to see this so don't try to fix it.
         return null;
       }
-      sb = _sourceBuilderAfterKeyword(statement.keyword);
+      sb = _sourceBuilderAfterKeyword(node, statement.keyword);
       sb.append('(');
       sb.setExitOffset();
       sb.append(')');
@@ -841,18 +837,19 @@
     return sb;
   }
 
-  bool _complete_methodCall() {
+  bool _complete_methodCall(AstNode node) {
     var parenError =
         _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "')'") ??
             _findError(ScannerErrorCode.EXPECTED_TOKEN, partialMatch: "')'");
     if (parenError == null) {
       return false;
     }
-    AstNode argList =
-        _selectedNode(at: selectionOffset).thisOrAncestorOfType<ArgumentList>();
+    var argList = _selectedNode(at: selectionOffset)
+        ?.thisOrAncestorOfType<ArgumentList>();
     argList ??= _selectedNode(at: parenError.offset)
-        .thisOrAncestorOfType<ArgumentList>();
-    if (argList?.thisOrAncestorMatching((n) => n == node) == null) {
+        ?.thisOrAncestorOfType<ArgumentList>();
+    if (argList == null ||
+        argList.thisOrAncestorMatching((n) => n == node) == null) {
       return false;
     }
     var previousInsertions = _lengthOfInsertions();
@@ -891,7 +888,7 @@
     return true;
   }
 
-  bool _complete_simpleSemicolon() {
+  bool _complete_simpleSemicolon(AstNode node) {
     if (errors.length != 1) {
       return false;
     }
@@ -908,41 +905,39 @@
     return false;
   }
 
-  bool _complete_switchStatement() {
+  bool _complete_switchStatement(AstNode node) {
     if (node is! SwitchStatement) {
       return false;
     }
     SourceBuilder sb;
-    SwitchStatement switchNode = node;
-    if (switchNode.leftParenthesis.isSynthetic &&
-        switchNode.rightParenthesis.isSynthetic) {
-      exitPosition = Position(file, switchNode.switchKeyword.end + 2);
-      var src = utils.getNodeText(switchNode);
+    if (node.leftParenthesis.isSynthetic && node.rightParenthesis.isSynthetic) {
+      exitPosition = Position(file, node.switchKeyword.end + 2);
+      var src = utils.getNodeText(node);
       if (src
-          .substring(switchNode.switchKeyword.end - switchNode.offset)
+          .substring(node.switchKeyword.end - node.offset)
           .startsWith(RegExp(r'[ \t]+'))) {
-        sb = SourceBuilder(file, switchNode.switchKeyword.end + 1);
+        sb = SourceBuilder(file, node.switchKeyword.end + 1);
       } else {
-        sb = SourceBuilder(file, switchNode.switchKeyword.end);
+        sb = SourceBuilder(file, node.switchKeyword.end);
         sb.append(' ');
       }
       sb.append('()');
-    } else if (switchNode.leftParenthesis.isSynthetic ||
-        switchNode.rightParenthesis.isSynthetic) {
+    } else if (node.leftParenthesis.isSynthetic ||
+        node.rightParenthesis.isSynthetic) {
       return false;
     } else {
-      sb = SourceBuilder(file, switchNode.rightParenthesis.offset + 1);
-      if (_isSyntheticExpression(switchNode.expression)) {
-        exitPosition = Position(file, switchNode.leftParenthesis.offset + 1);
+      sb = SourceBuilder(file, node.rightParenthesis.offset + 1);
+      if (_isSyntheticExpression(node.expression)) {
+        exitPosition = Position(file, node.leftParenthesis.offset + 1);
       }
     }
-    if (switchNode
+    if (node
         .leftBracket.isSynthetic /*&& switchNode.rightBracket.isSynthetic*/) {
       // See https://github.com/dart-lang/sdk/issues/29391
       sb.append(' ');
       _appendEmptyBraces(sb, exitPosition == null);
     } else {
-      var member = _findInvalidElement(switchNode.members);
+      var member = _findInvalidElement(node.members);
       if (member != null) {
         if (member.colon.isSynthetic) {
           var loc =
@@ -958,98 +953,103 @@
     return true;
   }
 
-  bool _complete_tryStatement() {
+  bool _complete_tryStatement(AstNode node) {
     if (node is! TryStatement) {
       return false;
     }
-    TryStatement tryNode = node;
-    SourceBuilder sb;
-    CatchClause catchNode;
     var addSpace = true;
-    if (tryNode.body.leftBracket.isSynthetic) {
-      var src = utils.getNodeText(tryNode);
+    if (node.body.leftBracket.isSynthetic) {
+      var src = utils.getNodeText(node);
+      SourceBuilder sb;
       if (src
-          .substring(tryNode.tryKeyword.end - tryNode.offset)
+          .substring(node.tryKeyword.end - node.offset)
           .startsWith(RegExp(r'[ \t]+'))) {
         // keywordSpace
-        sb = SourceBuilder(file, tryNode.tryKeyword.end + 1);
+        sb = SourceBuilder(file, node.tryKeyword.end + 1);
       } else {
         // keywordOnly
-        sb = SourceBuilder(file, tryNode.tryKeyword.end);
+        sb = SourceBuilder(file, node.tryKeyword.end);
         sb.append(' ');
       }
       _appendEmptyBraces(sb, true);
       _insertBuilder(sb);
-      sb = null;
-    } else if ((catchNode = _findInvalidElement(tryNode.catchClauses)) !=
-        null) {
-      if (catchNode.onKeyword != null) {
-        if (catchNode.exceptionType.length == 0 ||
-            null !=
-                _findError(CompileTimeErrorCode.NON_TYPE_IN_CATCH_CLAUSE,
-                    partialMatch: "name 'catch")) {
-          var src = utils.getNodeText(catchNode);
-          if (src.startsWith(RegExp(r'on[ \t]+'))) {
-            if (src.startsWith(RegExp(r'on[ \t][ \t]+'))) {
-              // onSpaces
-              exitPosition = Position(file, catchNode.onKeyword.end + 1);
-              sb = SourceBuilder(file, catchNode.onKeyword.end + 2);
-              addSpace = false;
+    } else {
+      SourceBuilder? sb;
+      var catchNode = _findInvalidElement(node.catchClauses);
+      if (catchNode != null) {
+        var onKeyword = catchNode.onKeyword;
+        var exceptionType = catchNode.exceptionType;
+        if (onKeyword != null && exceptionType != null) {
+          if (exceptionType.length == 0 ||
+              _findError(CompileTimeErrorCode.NON_TYPE_IN_CATCH_CLAUSE,
+                      partialMatch: "name 'catch") !=
+                  null) {
+            var src = utils.getNodeText(catchNode);
+            if (src.startsWith(RegExp(r'on[ \t]+'))) {
+              if (src.startsWith(RegExp(r'on[ \t][ \t]+'))) {
+                // onSpaces
+                exitPosition = Position(file, onKeyword.end + 1);
+                sb = SourceBuilder(file, onKeyword.end + 2);
+                addSpace = false;
+              } else {
+                // onSpace
+                sb = SourceBuilder(file, onKeyword.end + 1);
+                sb.setExitOffset();
+              }
             } else {
-              // onSpace
-              sb = SourceBuilder(file, catchNode.onKeyword.end + 1);
+              // onOnly
+              sb = SourceBuilder(file, onKeyword.end);
+              sb.append(' ');
               sb.setExitOffset();
             }
           } else {
-            // onOnly
-            sb = SourceBuilder(file, catchNode.onKeyword.end);
-            sb.append(' ');
-            sb.setExitOffset();
+            // onType
+            sb = SourceBuilder(file, exceptionType.end);
           }
-        } else {
-          // onType
-          sb = SourceBuilder(file, catchNode.exceptionType.end);
         }
-      }
-      if (catchNode.catchKeyword != null) {
-        // catchOnly
-        var struct = _KeywordConditionBlockStructure(
-            catchNode.catchKeyword,
-            catchNode.leftParenthesis,
-            catchNode.exceptionParameter,
-            catchNode.rightParenthesis,
-            catchNode.body);
+        var catchKeyword = catchNode.catchKeyword;
+        if (catchKeyword != null) {
+          // catchOnly
+          var struct = _KeywordConditionBlockStructure(
+              catchKeyword,
+              catchNode.leftParenthesis!,
+              catchNode.exceptionParameter!,
+              catchNode.rightParenthesis!,
+              catchNode.body);
+          if (sb != null) {
+            // onCatch
+            _insertBuilder(sb);
+          }
+          sb = _complete_keywordCondition(node, struct);
+          if (sb == null) {
+            return false;
+          }
+        }
         if (sb != null) {
-          // onCatch
+          if (catchNode.body.leftBracket.isSynthetic) {
+            // onOnly and others
+            if (addSpace) {
+              sb.append(' ');
+            }
+            _appendEmptyBraces(sb, exitPosition == null);
+          }
           _insertBuilder(sb);
         }
-        sb = _complete_keywordCondition(struct);
-        if (sb == null) {
-          return false;
-        }
-      }
-      if (catchNode.body.leftBracket.isSynthetic) {
-        // onOnly and others
-        if (addSpace) {
+      } else if (node.finallyKeyword != null) {
+        if (node.finallyBlock!.leftBracket.isSynthetic) {
+          // finallyOnly
+          sb = SourceBuilder(file, node.finallyKeyword!.end);
           sb.append(' ');
+          _appendEmptyBraces(sb, true);
+          _insertBuilder(sb);
         }
-        _appendEmptyBraces(sb, exitPosition == null);
-      }
-      _insertBuilder(sb);
-    } else if (tryNode.finallyKeyword != null) {
-      if (tryNode.finallyBlock.leftBracket.isSynthetic) {
-        // finallyOnly
-        sb = SourceBuilder(file, tryNode.finallyKeyword.end);
-        sb.append(' ');
-        _appendEmptyBraces(sb, true);
-        _insertBuilder(sb);
       }
     }
     _setCompletion(DartStatementCompletion.COMPLETE_TRY_STMT);
     return true;
   }
 
-  bool _complete_variableDeclaration() {
+  bool _complete_variableDeclaration(AstNode node) {
     if (node is! VariableDeclaration) {
       return false;
     }
@@ -1059,36 +1059,25 @@
     return true;
   }
 
-  bool _complete_whileStatement() {
+  bool _complete_whileStatement(AstNode node) {
     if (node is! WhileStatement) {
       return false;
     }
-    WhileStatement whileNode = node;
-    if (whileNode != null) {
-      var stmt = _KeywordConditionBlockStructure(
-          whileNode.whileKeyword,
-          whileNode.leftParenthesis,
-          whileNode.condition,
-          whileNode.rightParenthesis,
-          whileNode.body);
-      return _complete_ifOrWhileStatement(
-          stmt, DartStatementCompletion.COMPLETE_WHILE_STMT);
-    }
-    return false;
+    var stmt = _KeywordConditionBlockStructure(node.whileKeyword,
+        node.leftParenthesis, node.condition, node.rightParenthesis, node.body);
+    return _complete_ifOrWhileStatement(
+        node, stmt, DartStatementCompletion.COMPLETE_WHILE_STMT);
   }
 
-  engine.AnalysisError _findError(ErrorCode code, {partialMatch}) {
-    return errors.firstWhere(
-        (err) =>
-            err.errorCode == code &&
-            (partialMatch == null ? true : err.message.contains(partialMatch)),
-        orElse: () => null);
+  engine.AnalysisError? _findError(ErrorCode code, {partialMatch}) {
+    return errors.firstWhereOrNull((err) =>
+        err.errorCode == code &&
+        (partialMatch == null ? true : err.message.contains(partialMatch)));
   }
 
-  T _findInvalidElement<T extends AstNode>(NodeList<T> list) {
-    return list.firstWhere(
-        (item) => selectionOffset >= item.offset && selectionOffset <= item.end,
-        orElse: () => null);
+  T? _findInvalidElement<T extends AstNode>(NodeList<T> list) {
+    return list.firstWhereOrNull((item) =>
+        selectionOffset >= item.offset && selectionOffset <= item.end);
   }
 
   void _insertBuilder(SourceBuilder builder, [int length = 0]) {
@@ -1110,7 +1099,7 @@
     return stmt is Block && stmt.statements.isEmpty;
   }
 
-  bool _isEmptyStatement(AstNode stmt) {
+  bool _isEmptyStatement(AstNode? stmt) {
     if (stmt is ExpressionStatement) {
       var expression = stmt.expression;
       if (expression is SimpleIdentifier) {
@@ -1137,7 +1126,7 @@
         p?.parent?.parent is! Statement;
   }
 
-  bool _isSyntheticExpression(Expression expr) {
+  bool _isSyntheticExpression(Expression? expr) {
     return expr is SimpleIdentifier && expr.isSynthetic;
   }
 
@@ -1187,10 +1176,10 @@
     }
   }
 
-  AstNode _selectedNode({int at}) =>
+  AstNode? _selectedNode({int? at}) =>
       NodeLocator(at ?? selectionOffset).searchWithin(unit);
 
-  void _setCompletion(StatementCompletionKind kind, [List args]) {
+  void _setCompletion(StatementCompletionKind kind, [List? args]) {
     assert(exitPosition != null);
     change.selection = exitPosition;
     change.message = formatList(kind.message, args);
@@ -1199,12 +1188,13 @@
     completion = StatementCompletion(kind, change);
   }
 
-  void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) {
+  void _setCompletionAt(StatementCompletionKind kind, int offset,
+      [List? args]) {
     exitPosition = _newPosition(offset);
     _setCompletion(kind, args);
   }
 
-  SourceBuilder _sourceBuilderAfterKeyword(Token keyword) {
+  SourceBuilder _sourceBuilderAfterKeyword(AstNode node, Token keyword) {
     SourceBuilder sb;
     var text = _baseNodeText(node);
     text = text.substring(keyword.offset - node.offset);
@@ -1239,7 +1229,7 @@
   final Token keyword;
   final Token leftParenthesis, rightParenthesis;
   final Expression condition;
-  final Statement block;
+  final Statement? block;
 
   _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis,
       this.condition, this.rightParenthesis, this.block);
diff --git a/pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart b/pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart
index 6d787d6..741715f 100644
--- a/pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart
+++ b/pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart = 2.9
-
 import 'package:analysis_server/src/protocol_server.dart';
 import 'package:analysis_server/src/services/completion/statement/statement_completion.dart';
 import 'package:test/test.dart';
@@ -28,7 +26,7 @@
 }
 
 class StatementCompletionTest extends AbstractSingleUnitTest {
-  SourceChange change;
+  late SourceChange change;
 
   int _after(String source, String match) =>
       source.indexOf(match) + match.length;
@@ -39,7 +37,7 @@
   void _assertHasChange(
     String message,
     String expectedCode, [
-    int Function(String) cmp,
+    int Function(String)? cmp,
   ]) {
     if (change.message == message) {
       if (change.edits.isNotEmpty) {
@@ -48,13 +46,13 @@
         expect(resultCode, expectedCode.replaceAll('////', ''));
         if (cmp != null) {
           var offset = cmp(resultCode);
-          expect(change.selection.offset, offset);
+          expect(change.selection!.offset, offset);
         }
       } else {
         expect(testCode, expectedCode.replaceAll('////', ''));
         if (cmp != null) {
           var offset = cmp(testCode);
-          expect(change.selection.offset, offset);
+          expect(change.selection!.offset, offset);
         }
       }
       return;
diff --git a/tools/VERSION b/tools/VERSION
index d75cb56..646d39a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 227
+PRERELEASE 228
 PRERELEASE_PATCH 0
\ No newline at end of file