Version 2.12.0-232.0.dev

Merge commit '5ae02ab4c3de4af4ebffe7174019a65d9895edcc' 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 ee22cc5..ade689e 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
@@ -1567,7 +1567,7 @@
     VariableModel<Variable, Type> newInfoForVar =
         new VariableModel.fresh(assigned: initialized);
 
-    return _updateVariableInfo(variable, newInfoForVar);
+    return _updateVariableInfo(new VariableReference(variable), newInfoForVar);
   }
 
   /// Gets the info for the given [variable], creating it if it doesn't exist.
@@ -1799,19 +1799,20 @@
   String toString() => '($reachable, $variableInfo)';
 
   /// Returns an [ExpressionInfo] indicating the result of checking whether the
-  /// given [variable] is non-null.
+  /// given [reference] is non-null.
   ///
   /// Note that the state is only changed if the previous type of [variable] was
   /// potentially nullable.
   ExpressionInfo<Variable, Type> tryMarkNonNullable(
-      TypeOperations<Variable, Type> typeOperations, Variable variable) {
-    VariableModel<Variable, Type> info = infoFor(variable);
+      TypeOperations<Variable, Type> typeOperations,
+      Reference<Variable, Type> reference) {
+    VariableModel<Variable, Type> info = reference.getInfo(variableInfo);
     if (info.writeCaptured) {
       return new _TrivialExpressionInfo<Variable, Type>(this);
     }
 
     Type? previousType = info.promotedTypes?.last;
-    previousType ??= typeOperations.variableType(variable);
+    previousType ??= reference.getDeclaredType(typeOperations);
 
     Type newType = typeOperations.promoteToNonNull(previousType);
     if (typeOperations.isSameType(newType, previousType)) {
@@ -1820,7 +1821,7 @@
     assert(typeOperations.isSubtypeOf(newType, previousType));
 
     FlowModel<Variable, Type> modelIfSuccessful =
-        _finishTypeTest(typeOperations, variable, info, null, newType);
+        _finishTypeTest(typeOperations, reference, info, null, newType);
 
     FlowModel<Variable, Type> modelIfFailed = this;
 
@@ -1829,7 +1830,7 @@
   }
 
   /// Returns an [ExpressionInfo] indicating the result of casting the given
-  /// [variable] to the given [type], as a consequence of an `as` expression.
+  /// [reference] to the given [type], as a consequence of an `as` expression.
   ///
   /// Note that the state is only changed if [type] is a subtype of the
   /// variable's previous (possibly promoted) type.
@@ -1838,15 +1839,15 @@
   /// variable as definitely assigned?  Does it matter?
   FlowModel<Variable, Type> tryPromoteForTypeCast(
       TypeOperations<Variable, Type> typeOperations,
-      Variable variable,
+      Reference<Variable, Type> reference,
       Type type) {
-    VariableModel<Variable, Type> info = infoFor(variable);
+    VariableModel<Variable, Type> info = reference.getInfo(variableInfo);
     if (info.writeCaptured) {
       return this;
     }
 
     Type? previousType = info.promotedTypes?.last;
-    previousType ??= typeOperations.variableType(variable);
+    previousType ??= reference.getDeclaredType(typeOperations);
 
     Type? newType = typeOperations.tryPromoteToType(type, previousType);
     if (newType == null || typeOperations.isSameType(newType, previousType)) {
@@ -1855,11 +1856,11 @@
 
     assert(typeOperations.isSubtypeOf(newType, previousType),
         "Expected $newType to be a subtype of $previousType.");
-    return _finishTypeTest(typeOperations, variable, info, type, newType);
+    return _finishTypeTest(typeOperations, reference, info, type, newType);
   }
 
   /// Returns an [ExpressionInfo] indicating the result of checking whether the
-  /// given [variable] satisfies the given [type], e.g. as a consequence of an
+  /// given [reference] satisfies the given [type], e.g. as a consequence of an
   /// `is` expression as the condition of an `if` statement.
   ///
   /// Note that the "ifTrue" state is only changed if [type] is a subtype of
@@ -1869,15 +1870,15 @@
   /// variable as definitely assigned?  Does it matter?
   ExpressionInfo<Variable, Type> tryPromoteForTypeCheck(
       TypeOperations<Variable, Type> typeOperations,
-      Variable variable,
+      Reference<Variable, Type> reference,
       Type type) {
-    VariableModel<Variable, Type> info = infoFor(variable);
+    VariableModel<Variable, Type> info = reference.getInfo(variableInfo);
     if (info.writeCaptured) {
       return new _TrivialExpressionInfo<Variable, Type>(this);
     }
 
     Type? previousType = info.promotedTypes?.last;
-    previousType ??= typeOperations.variableType(variable);
+    previousType ??= reference.getDeclaredType(typeOperations);
 
     FlowModel<Variable, Type> modelIfSuccessful = this;
     Type? typeIfSuccess = typeOperations.tryPromoteToType(type, previousType);
@@ -1886,7 +1887,7 @@
       assert(typeOperations.isSubtypeOf(typeIfSuccess, previousType),
           "Expected $typeIfSuccess to be a subtype of $previousType.");
       modelIfSuccessful =
-          _finishTypeTest(typeOperations, variable, info, type, typeIfSuccess);
+          _finishTypeTest(typeOperations, reference, info, type, typeIfSuccess);
     }
 
     Type factoredType = typeOperations.factor(previousType, type);
@@ -1902,7 +1903,7 @@
       typeIfFailed = factoredType;
     }
     FlowModel<Variable, Type> modelIfFailed =
-        _finishTypeTest(typeOperations, variable, info, type, typeIfFailed);
+        _finishTypeTest(typeOperations, reference, info, type, typeIfFailed);
 
     return new ExpressionInfo<Variable, Type>(
         this, modelIfSuccessful, modelIfFailed);
@@ -1939,12 +1940,12 @@
         infoForVar.write(variable, writtenType, typeOperations, newSsaNode);
     if (identical(newInfoForVar, infoForVar)) return this;
 
-    return _updateVariableInfo(variable, newInfoForVar);
+    return _updateVariableInfo(new VariableReference(variable), newInfoForVar);
   }
 
   /// Common algorithm for [tryMarkNonNullable], [tryPromoteForTypeCast],
   /// and [tryPromoteForTypeCheck].  Builds a [FlowModel] object describing the
-  /// effect of updating the [variable] by adding the [testedType] to the
+  /// effect of updating the [reference] by adding the [testedType] to the
   /// list of tested types (if not `null`, and not there already), adding the
   /// [promotedType] to the chain of promoted types.
   ///
@@ -1955,7 +1956,7 @@
   /// - The variable should not be write-captured.
   FlowModel<Variable, Type> _finishTypeTest(
     TypeOperations<Variable, Type> typeOperations,
-    Variable variable,
+    Reference<Variable, Type> reference,
     VariableModel<Variable, Type> info,
     Type? testedType,
     Type? promotedType,
@@ -1981,7 +1982,7 @@
             newReachable == reachable
         ? this
         : _updateVariableInfo(
-            variable,
+            reference,
             new VariableModel<Variable, Type>(
                 promotedTypes: newPromotedTypes,
                 tested: newTested,
@@ -1991,15 +1992,15 @@
             reachable: newReachable);
   }
 
-  /// Returns a new [FlowModel] where the information for [variable] is replaced
-  /// with [model].
+  /// Returns a new [FlowModel] where the information for [reference] is
+  /// replaced with [model].
   FlowModel<Variable, Type> _updateVariableInfo(
-      Variable variable, VariableModel<Variable, Type> model,
+      Reference<Variable, Type> reference, VariableModel<Variable, Type> model,
       {Reachability? reachable}) {
     reachable ??= this.reachable;
     Map<Variable, VariableModel<Variable, Type>> newVariableInfo =
         new Map<Variable, VariableModel<Variable, Type>>.from(variableInfo);
-    newVariableInfo[variable] = model;
+    reference.storeInfo(newVariableInfo, model);
     return new FlowModel<Variable, Type>.withInfo(reachable, newVariableInfo);
   }
 
@@ -2296,6 +2297,33 @@
   }
 }
 
+/// Abstract base class representing a reference to a storage location that
+/// might be of interest to flow analysis to track.  This could be a variable,
+/// `this`, or the result of a property access on some other reference.
+///
+/// Note that only variables can undergo promotion, but flow analysis may track
+/// other references in order to give useful error messages to the user about
+/// why promotion did not occur.
+@visibleForTesting
+abstract class Reference<Variable extends Object, Type extends Object> {
+  /// Retrieves the declared type of this reference.  This is used as the
+  /// starting point for promotions.
+  Type getDeclaredType(TypeOperations<Variable, Type> typeOperations);
+
+  /// Gets the info for this reference, creating it if it doesn't exist.
+  VariableModel<Variable, Type> getInfo(
+          Map<Variable, VariableModel<Variable, Type>> variableInfo) =>
+      _getInfo(variableInfo) ?? new VariableModel<Variable, Type>.fresh();
+
+  /// Stores info for this reference in [variableInfo].
+  void storeInfo(Map<Variable, VariableModel<Variable, Type>> variableInfo,
+      VariableModel<Variable, Type> variableModel);
+
+  /// Gets the info for this reference, or `null` if it doesn't exist.
+  VariableModel<Variable, Type>? _getInfo(
+      Map<Variable, VariableModel<Variable, Type>> variableInfo);
+}
+
 /// Data structure representing a unique value that a variable might take on
 /// during execution of the code being analyzed.  SSA nodes are immutable (so
 /// they can be safety shared among data structures) and have identity (so that
@@ -2977,6 +3005,32 @@
   }
 }
 
+/// Specialization of [Reference] representing a reference to a local variable
+/// (or function parameter).
+@visibleForTesting
+class VariableReference<Variable extends Object, Type extends Object>
+    extends Reference<Variable, Type> {
+  /// The variable being referred to.
+  final Variable variable;
+
+  VariableReference(this.variable);
+
+  @override
+  Type getDeclaredType(TypeOperations<Variable, Type> typeOperations) =>
+      typeOperations.variableType(variable);
+
+  @override
+  void storeInfo(Map<Variable, VariableModel<Variable, Type>> variableInfo,
+      VariableModel<Variable, Type> variableModel) {
+    variableInfo[variable] = variableModel;
+  }
+
+  @override
+  VariableModel<Variable, Type>? _getInfo(
+          Map<Variable, VariableModel<Variable, Type>> variableInfo) =>
+      variableInfo[variable];
+}
+
 /// [_FlowContext] representing an assert statement or assert initializer.
 class _AssertContext<Variable extends Object, Type extends Object>
     extends _SimpleContext<Variable, Type> {
@@ -3050,12 +3104,12 @@
   /// The type of the expression on the LHS of `==` or `!=`.
   final Type _leftOperandType;
 
-  /// If the LHS of `==` or `!=` is a variable reference, the variable.
+  /// If the LHS of `==` or `!=` is a reference, the thing being referred to.
   /// Otherwise `null`.
-  final Variable? _leftOperandVariable;
+  final Reference<Variable, Type>? _leftOperandReference;
 
   _EqualityOpContext(ExpressionInfo<Variable, Type>? conditionInfo,
-      this._leftOperandType, this._leftOperandVariable)
+      this._leftOperandType, this._leftOperandReference)
       : super(conditionInfo);
 
   @override
@@ -3091,13 +3145,13 @@
   /// corresponding to it.  Otherwise `null`.
   ExpressionInfo<Variable, Type>? _expressionInfo;
 
-  /// The most recently visited expression which was a variable reference, or
-  /// `null` if no expression has been visited that was a variable reference.
-  Expression? _expressionWithVariable;
+  /// The most recently visited expression which was a reference, or `null` if
+  /// no such expression has been visited.
+  Expression? _expressionWithReference;
 
-  /// If [_expressionVariable] is not `null`, the variable corresponding to it.
+  /// If [_expressionVariable] is not `null`, the reference corresponding to it.
   /// Otherwise `null`.
-  Variable? _expressionVariable;
+  Reference<Variable, Type>? _expressionReference;
 
   int _functionNestingLevel = 0;
 
@@ -3123,9 +3177,10 @@
 
   @override
   void asExpression_end(Expression subExpression, Type type) {
-    Variable? variable = _getExpressionVariable(subExpression);
-    if (variable == null) return;
-    _current = _current.tryPromoteForTypeCast(typeOperations, variable, type);
+    Reference<Variable, Type>? reference =
+        _getExpressionReference(subExpression);
+    if (reference == null) return;
+    _current = _current.tryPromoteForTypeCast(typeOperations, reference, type);
   }
 
   @override
@@ -3232,10 +3287,11 @@
     _EqualityOpContext<Variable, Type> context =
         _stack.removeLast() as _EqualityOpContext<Variable, Type>;
     ExpressionInfo<Variable, Type>? lhsInfo = context._conditionInfo;
-    Variable? lhsVariable = context._leftOperandVariable;
+    Reference<Variable, Type>? lhsReference = context._leftOperandReference;
     Type leftOperandType = context._leftOperandType;
     ExpressionInfo<Variable, Type>? rhsInfo = _getExpressionInfo(rightOperand);
-    Variable? rhsVariable = _getExpressionVariable(rightOperand);
+    Reference<Variable, Type>? rhsReference =
+        _getExpressionReference(rightOperand);
     TypeClassification leftOperandTypeClassification =
         typeOperations.classifyType(leftOperandType);
     TypeClassification rightOperandTypeClassification =
@@ -3253,14 +3309,14 @@
       // but weak mode it might produce an "equal" result.  We don't want flow
       // analysis behavior to depend on mode, so we conservatively assume that
       // either result is possible.
-    } else if (lhsInfo is _NullInfo<Variable, Type> && rhsVariable != null) {
+    } else if (lhsInfo is _NullInfo<Variable, Type> && rhsReference != null) {
       ExpressionInfo<Variable, Type> equalityInfo =
-          _current.tryMarkNonNullable(typeOperations, rhsVariable);
+          _current.tryMarkNonNullable(typeOperations, rhsReference);
       _storeExpressionInfo(
           wholeExpression, notEqual ? equalityInfo : equalityInfo.invert());
-    } else if (rhsInfo is _NullInfo<Variable, Type> && lhsVariable != null) {
+    } else if (rhsInfo is _NullInfo<Variable, Type> && lhsReference != null) {
       ExpressionInfo<Variable, Type> equalityInfo =
-          _current.tryMarkNonNullable(typeOperations, lhsVariable);
+          _current.tryMarkNonNullable(typeOperations, lhsReference);
       _storeExpressionInfo(
           wholeExpression, notEqual ? equalityInfo : equalityInfo.invert());
     }
@@ -3271,7 +3327,7 @@
     _stack.add(new _EqualityOpContext<Variable, Type>(
         _getExpressionInfo(leftOperand),
         leftOperandType,
-        _getExpressionVariable(leftOperand)));
+        _getExpressionReference(leftOperand)));
   }
 
   @override
@@ -3351,8 +3407,8 @@
     if (identical(_expressionWithInfo, oldExpression)) {
       _expressionWithInfo = newExpression;
     }
-    if (identical(_expressionWithVariable, oldExpression)) {
-      _expressionWithVariable = newExpression;
+    if (identical(_expressionWithReference, oldExpression)) {
+      _expressionWithReference = newExpression;
     }
   }
 
@@ -3411,12 +3467,13 @@
   @override
   void ifNullExpression_rightBegin(
       Expression leftHandSide, Type leftHandSideType) {
-    Variable? lhsVariable = _getExpressionVariable(leftHandSide);
+    Reference<Variable, Type>? lhsReference =
+        _getExpressionReference(leftHandSide);
     FlowModel<Variable, Type> promoted;
     _current = _current.split();
-    if (lhsVariable != null) {
+    if (lhsReference != null) {
       ExpressionInfo<Variable, Type> promotionInfo =
-          _current.tryMarkNonNullable(typeOperations, lhsVariable);
+          _current.tryMarkNonNullable(typeOperations, lhsReference);
       _current = promotionInfo.ifFalse;
       promoted = promotionInfo.ifTrue;
     } else {
@@ -3489,10 +3546,11 @@
   @override
   void isExpression_end(Expression isExpression, Expression subExpression,
       bool isNot, Type type) {
-    Variable? subExpressionVariable = _getExpressionVariable(subExpression);
-    if (subExpressionVariable != null) {
+    Reference<Variable, Type>? subExpressionReference =
+        _getExpressionReference(subExpression);
+    if (subExpressionReference != null) {
       ExpressionInfo<Variable, Type> expressionInfo = _current
-          .tryPromoteForTypeCheck(typeOperations, subExpressionVariable, type);
+          .tryPromoteForTypeCheck(typeOperations, subExpressionReference, type);
       _storeExpressionInfo(
           isExpression, isNot ? expressionInfo.invert() : expressionInfo);
     }
@@ -3582,10 +3640,11 @@
 
   @override
   void nonNullAssert_end(Expression operand) {
-    Variable? operandVariable = _getExpressionVariable(operand);
-    if (operandVariable != null) {
+    Reference<Variable, Type>? operandReference =
+        _getExpressionReference(operand);
+    if (operandReference != null) {
       _current =
-          _current.tryMarkNonNullable(typeOperations, operandVariable).ifTrue;
+          _current.tryMarkNonNullable(typeOperations, operandReference).ifTrue;
     }
   }
 
@@ -3602,10 +3661,11 @@
     assert(targetType != null);
     _current = _current.split();
     _stack.add(new _NullAwareAccessContext<Variable, Type>(_current));
-    Variable? targetVariable = _getExpressionVariable(target);
-    if (targetVariable != null) {
+    Reference<Variable, Type>? targetReference =
+        _getExpressionReference(target);
+    if (targetReference != null) {
       _current =
-          _current.tryMarkNonNullable(typeOperations, targetVariable).ifTrue;
+          _current.tryMarkNonNullable(typeOperations, targetReference).ifTrue;
     }
   }
 
@@ -3622,8 +3682,10 @@
 
   @override
   void promote(Variable variable, Type type) {
-    _current =
-        _current.tryPromoteForTypeCheck(typeOperations, variable, type).ifTrue;
+    _current = _current
+        .tryPromoteForTypeCheck(typeOperations,
+            new VariableReference<Variable, Type>(variable), type)
+        .ifTrue;
   }
 
   @override
@@ -3761,8 +3823,11 @@
 
   @override
   Type? variableRead(Expression expression, Variable variable) {
-    _storeExpressionVariable(expression, variable);
-    VariableModel<Variable, Type> variableModel = _current.infoFor(variable);
+    VariableReference<Variable, Type> variableReference =
+        new VariableReference<Variable, Type>(variable);
+    _storeExpressionReference(expression, variableReference);
+    VariableModel<Variable, Type> variableModel =
+        variableReference.getInfo(_current.variableInfo);
     if (allowLocalBooleanVarsToPromote) {
       ExpressionInfo<Variable, Type>? expressionInfo = variableModel
           .ssaNode?.expressionInfo
@@ -3818,8 +3883,8 @@
     print('  current: $_current');
     print('  expressionWithInfo: $_expressionWithInfo');
     print('  expressionInfo: $_expressionInfo');
-    print('  expressionWithVariable: $_expressionWithVariable');
-    print('  expressionVariable: $_expressionVariable');
+    print('  expressionWithReference: $_expressionWithReference');
+    print('  expressionReference: $_expressionReference');
     print('  stack:');
     for (_FlowContext stackEntry in _stack.reversed) {
       print('    $stackEntry');
@@ -3847,14 +3912,14 @@
     }
   }
 
-  /// Gets the [Variable] associated with the [expression] (which should be the
-  /// last expression that was traversed).  If there is no [Variable] associated
-  /// with the [expression], then `null` is returned.
-  Variable? _getExpressionVariable(Expression? expression) {
-    if (identical(expression, _expressionWithVariable)) {
-      Variable? expressionVariable = _expressionVariable;
-      _expressionVariable = null;
-      return expressionVariable;
+  /// Gets the [Reference] associated with the [expression] (which should be the
+  /// last expression that was traversed).  If there is no [Reference]
+  /// associated with the [expression], then `null` is returned.
+  Reference<Variable, Type>? _getExpressionReference(Expression? expression) {
+    if (identical(expression, _expressionWithReference)) {
+      Reference<Variable, Type>? expressionReference = _expressionReference;
+      _expressionReference = null;
+      return expressionReference;
     } else {
       return null;
     }
@@ -3880,11 +3945,11 @@
   }
 
   /// Associates [expression], which should be the most recently visited
-  /// expression, with the given [Variable] object.
-  void _storeExpressionVariable(
-      Expression expression, Variable expressionVariable) {
-    _expressionWithVariable = expression;
-    _expressionVariable = expressionVariable;
+  /// expression, with the given [Reference] object.
+  void _storeExpressionReference(
+      Expression expression, Reference<Variable, Type> expressionReference) {
+    _expressionWithReference = expression;
+    _expressionReference = expressionReference;
   }
 }
 
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 4ce5600..5d522be 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
@@ -3171,28 +3171,34 @@
       test('unpromoted -> unchanged (same)', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial);
-        var s2 = s1.tryPromoteForTypeCheck(h, intVar, Type('int')).ifTrue;
+        var s2 =
+            s1.tryPromoteForTypeCheck(h, _varRef(intVar), Type('int')).ifTrue;
         expect(s2, same(s1));
       });
 
       test('unpromoted -> unchanged (supertype)', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial);
-        var s2 = s1.tryPromoteForTypeCheck(h, intVar, Type('Object')).ifTrue;
+        var s2 = s1
+            .tryPromoteForTypeCheck(h, _varRef(intVar), Type('Object'))
+            .ifTrue;
         expect(s2, same(s1));
       });
 
       test('unpromoted -> unchanged (unrelated)', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial);
-        var s2 = s1.tryPromoteForTypeCheck(h, intVar, Type('String')).ifTrue;
+        var s2 = s1
+            .tryPromoteForTypeCheck(h, _varRef(intVar), Type('String'))
+            .ifTrue;
         expect(s2, same(s1));
       });
 
       test('unpromoted -> subtype', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial);
-        var s2 = s1.tryPromoteForTypeCheck(h, intQVar, Type('int')).ifTrue;
+        var s2 =
+            s1.tryPromoteForTypeCheck(h, _varRef(intQVar), Type('int')).ifTrue;
         expect(s2.reachable.overallReachable, true);
         expect(s2.variableInfo, {
           intQVar: _matchVariableModel(chain: ['int'], ofInterest: ['int'])
@@ -3202,38 +3208,44 @@
       test('promoted -> unchanged (same)', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue;
-        var s2 = s1.tryPromoteForTypeCheck(h, objectQVar, Type('int')).ifTrue;
+        var s2 = s1
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
+            .ifTrue;
         expect(s2, same(s1));
       });
 
       test('promoted -> unchanged (supertype)', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue;
-        var s2 =
-            s1.tryPromoteForTypeCheck(h, objectQVar, Type('Object')).ifTrue;
+        var s2 = s1
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('Object'))
+            .ifTrue;
         expect(s2, same(s1));
       });
 
       test('promoted -> unchanged (unrelated)', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue;
-        var s2 =
-            s1.tryPromoteForTypeCheck(h, objectQVar, Type('String')).ifTrue;
+        var s2 = s1
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('String'))
+            .ifTrue;
         expect(s2, same(s1));
       });
 
       test('promoted -> subtype', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int?'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int?'))
             .ifTrue;
-        var s2 = s1.tryPromoteForTypeCheck(h, objectQVar, Type('int')).ifTrue;
+        var s2 = s1
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
+            .ifTrue;
         expect(s2.reachable.overallReachable, true);
         expect(s2.variableInfo, {
           objectQVar: _matchVariableModel(
@@ -3290,7 +3302,7 @@
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(objectQVar, true)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue;
         expect(s1.variableInfo, contains(objectQVar));
         var s2 =
@@ -3309,9 +3321,9 @@
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(objectQVar, true)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('num?'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num?'))
             .ifTrue
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue;
         expect(s1.variableInfo, {
           objectQVar: _matchVariableModel(
@@ -3336,11 +3348,11 @@
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(objectQVar, true)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('num?'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num?'))
             .ifTrue
-            .tryPromoteForTypeCheck(h, objectQVar, Type('num'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num'))
             .ifTrue
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue;
         expect(s1.variableInfo, {
           objectQVar: _matchVariableModel(
@@ -3365,9 +3377,9 @@
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(objectQVar, true)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('num?'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num?'))
             .ifTrue
-            .tryPromoteForTypeCheck(h, objectQVar, Type('num'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num'))
             .ifTrue;
         expect(s1.variableInfo, {
           objectQVar: _matchVariableModel(
@@ -3393,9 +3405,9 @@
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(objectQVar, true)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('num?'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num?'))
             .ifTrue
-            .tryPromoteForTypeCheck(h, objectQVar, Type('num'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num'))
             .ifTrue;
         expect(s1.variableInfo, {
           objectQVar: _matchVariableModel(
@@ -3458,7 +3470,7 @@
           var h = Harness();
           var s1 = FlowModel<Var, Type>(Reachability.initial)
               .declare(objectQVar, true)
-              .tryPromoteForTypeCheck(h, objectQVar, Type('int?'))
+              .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int?'))
               .ifTrue;
           expect(s1.variableInfo, {
             objectQVar: _matchVariableModel(
@@ -3480,7 +3492,7 @@
           var h = Harness();
           var s1 = FlowModel<Var, Type>(Reachability.initial)
               .declare(objectQVar, true)
-              .tryPromoteForTypeCheck(h, objectQVar, Type('int?'))
+              .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int?'))
               .ifFalse;
           expect(s1.variableInfo, {
             objectQVar: _matchVariableModel(
@@ -3503,7 +3515,7 @@
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(objectQVar, true)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('num?'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num?'))
             .ifFalse;
         expect(s1.variableInfo, {
           objectQVar: _matchVariableModel(
@@ -3525,9 +3537,9 @@
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(objectQVar, true)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('num?'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num?'))
             .ifTrue
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int?'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int?'))
             .ifFalse;
         expect(s1.variableInfo, {
           objectQVar: _matchVariableModel(
@@ -3594,9 +3606,9 @@
 
             var s1 = FlowModel<Var, Type>(Reachability.initial)
                 .declare(x, true)
-                .tryPromoteForTypeCheck(h, x, Type('B?'))
+                .tryPromoteForTypeCheck(h, _varRef(x), Type('B?'))
                 .ifFalse
-                .tryPromoteForTypeCheck(h, x, Type('A?'))
+                .tryPromoteForTypeCheck(h, _varRef(x), Type('A?'))
                 .ifFalse;
             expect(s1.variableInfo, {
               x: _matchVariableModel(
@@ -3619,9 +3631,9 @@
 
             var s1 = FlowModel<Var, Type>(Reachability.initial)
                 .declare(x, true)
-                .tryPromoteForTypeCheck(h, x, Type('A?'))
+                .tryPromoteForTypeCheck(h, _varRef(x), Type('A?'))
                 .ifFalse
-                .tryPromoteForTypeCheck(h, x, Type('B?'))
+                .tryPromoteForTypeCheck(h, _varRef(x), Type('B?'))
                 .ifFalse;
             expect(s1.variableInfo, {
               x: _matchVariableModel(
@@ -3644,9 +3656,9 @@
 
             var s1 = FlowModel<Var, Type>(Reachability.initial)
                 .declare(x, true)
-                .tryPromoteForTypeCheck(h, x, Type('A'))
+                .tryPromoteForTypeCheck(h, _varRef(x), Type('A'))
                 .ifFalse
-                .tryPromoteForTypeCheck(h, x, Type('A?'))
+                .tryPromoteForTypeCheck(h, _varRef(x), Type('A?'))
                 .ifFalse;
             expect(s1.variableInfo, {
               x: _matchVariableModel(
@@ -3670,9 +3682,9 @@
             var h = Harness();
             var s1 = FlowModel<Var, Type>(Reachability.initial)
                 .declare(objectQVar, true)
-                .tryPromoteForTypeCheck(h, objectQVar, Type('num?'))
+                .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num?'))
                 .ifFalse
-                .tryPromoteForTypeCheck(h, objectQVar, Type('num*'))
+                .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num*'))
                 .ifFalse;
             expect(s1.variableInfo, {
               objectQVar: _matchVariableModel(
@@ -3698,9 +3710,9 @@
           var h = Harness();
           var s1 = FlowModel<Var, Type>(Reachability.initial)
               .declare(objectQVar, true)
-              .tryPromoteForTypeCheck(h, objectQVar, Type('num?'))
+              .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num?'))
               .ifFalse
-              .tryPromoteForTypeCheck(h, objectQVar, Type('num*'))
+              .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('num*'))
               .ifFalse;
           expect(s1.variableInfo, {
             objectQVar: _matchVariableModel(
@@ -3730,9 +3742,9 @@
 
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(x, true)
-            .tryPromoteForTypeCheck(h, x, Type('num?'))
+            .tryPromoteForTypeCheck(h, _varRef(x), Type('num?'))
             .ifTrue
-            .tryPromoteForTypeCheck(h, x, Type('int?'))
+            .tryPromoteForTypeCheck(h, _varRef(x), Type('int?'))
             .ifTrue;
         expect(s1.variableInfo, {
           x: _matchVariableModel(
@@ -3775,14 +3787,14 @@
       test('unpromoted -> unchanged', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial);
-        var s2 = s1.tryMarkNonNullable(h, intVar).ifTrue;
+        var s2 = s1.tryMarkNonNullable(h, _varRef(intVar)).ifTrue;
         expect(s2, same(s1));
       });
 
       test('unpromoted -> promoted', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial);
-        var s2 = s1.tryMarkNonNullable(h, intQVar).ifTrue;
+        var s2 = s1.tryMarkNonNullable(h, _varRef(intQVar)).ifTrue;
         expect(s2.reachable.overallReachable, true);
         expect(s2.infoFor(intQVar),
             _matchVariableModel(chain: ['int'], ofInterest: []));
@@ -3791,18 +3803,18 @@
       test('promoted -> unchanged', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue;
-        var s2 = s1.tryMarkNonNullable(h, objectQVar).ifTrue;
+        var s2 = s1.tryMarkNonNullable(h, _varRef(objectQVar)).ifTrue;
         expect(s2, same(s1));
       });
 
       test('promoted -> re-promoted', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int?'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int?'))
             .ifTrue;
-        var s2 = s1.tryMarkNonNullable(h, objectQVar).ifTrue;
+        var s2 = s1.tryMarkNonNullable(h, _varRef(objectQVar)).ifTrue;
         expect(s2.reachable.overallReachable, true);
         expect(s2.variableInfo, {
           objectQVar:
@@ -3813,7 +3825,7 @@
       test('promote to Never', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial);
-        var s2 = s1.tryMarkNonNullable(h, nullVar).ifTrue;
+        var s2 = s1.tryMarkNonNullable(h, _varRef(nullVar)).ifTrue;
         expect(s2.reachable.overallReachable, false);
         expect(s2.infoFor(nullVar),
             _matchVariableModel(chain: ['Never'], ofInterest: []));
@@ -3825,7 +3837,7 @@
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
             .declare(intQVar, true)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue;
         var s2 = s1.conservativeJoin([intQVar], []);
         expect(s2, isNot(same(s1)));
@@ -3839,9 +3851,9 @@
       test('written', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue
-            .tryPromoteForTypeCheck(h, intQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(intQVar), Type('int'))
             .ifTrue;
         var s2 = s1.conservativeJoin([intQVar], []);
         expect(s2.reachable.overallReachable, true);
@@ -3854,9 +3866,9 @@
       test('write captured', () {
         var h = Harness();
         var s1 = FlowModel<Var, Type>(Reachability.initial)
-            .tryPromoteForTypeCheck(h, objectQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(objectQVar), Type('int'))
             .ifTrue
-            .tryPromoteForTypeCheck(h, intQVar, Type('int'))
+            .tryPromoteForTypeCheck(h, _varRef(intQVar), Type('int'))
             .ifTrue;
         var s2 = s1.conservativeJoin([], [intQVar]);
         expect(s2.reachable.overallReachable, true);
@@ -3946,7 +3958,7 @@
         var s0 = FlowModel<Var, Type>(Reachability.initial).declare(a, false);
         // In s1, a is write captured.  In s2 it's promoted.
         var s1 = s0.conservativeJoin([a], [a]);
-        var s2 = s0.tryPromoteForTypeCheck(h, a, Type('int')).ifTrue;
+        var s2 = s0.tryPromoteForTypeCheck(h, _varRef(a), Type('int')).ifTrue;
         expect(
           s1.rebaseForward(h, s2).infoFor(a),
           _matchVariableModel(writeCaptured: true, chain: isNull),
@@ -3968,11 +3980,14 @@
             s1 = s1.write(x, Type('Object?'), new SsaNode<Var, Type>(null), h);
           }
           if (thisType != null) {
-            s1 = s1.tryPromoteForTypeCheck(h, x, Type(thisType)).ifTrue;
+            s1 =
+                s1.tryPromoteForTypeCheck(h, _varRef(x), Type(thisType)).ifTrue;
           }
           var s2 = otherType == null
               ? s0
-              : s0.tryPromoteForTypeCheck(h, x, Type(otherType)).ifTrue;
+              : s0
+                  .tryPromoteForTypeCheck(h, _varRef(x), Type(otherType))
+                  .ifTrue;
           var result = s2.rebaseForward(h, s1);
           if (expectedChain == null) {
             expect(result.variableInfo, contains(x));
@@ -4021,20 +4036,23 @@
           var initialModel =
               FlowModel<Var, Type>(Reachability.initial).declare(x, true);
           for (var t in before) {
-            initialModel =
-                initialModel.tryPromoteForTypeCheck(h, x, Type(t)).ifTrue;
+            initialModel = initialModel
+                .tryPromoteForTypeCheck(h, _varRef(x), Type(t))
+                .ifTrue;
           }
           _checkChain(initialModel.infoFor(x).promotedTypes, before);
           var tryModel = initialModel;
           for (var t in inTry) {
-            tryModel = tryModel.tryPromoteForTypeCheck(h, x, Type(t)).ifTrue;
+            tryModel =
+                tryModel.tryPromoteForTypeCheck(h, _varRef(x), Type(t)).ifTrue;
           }
           var expectedTryChain = before.toList()..addAll(inTry);
           _checkChain(tryModel.infoFor(x).promotedTypes, expectedTryChain);
           var finallyModel = initialModel;
           for (var t in inFinally) {
-            finallyModel =
-                finallyModel.tryPromoteForTypeCheck(h, x, Type(t)).ifTrue;
+            finallyModel = finallyModel
+                .tryPromoteForTypeCheck(h, _varRef(x), Type(t))
+                .ifTrue;
           }
           var expectedFinallyChain = before.toList()..addAll(inFinally);
           _checkChain(
@@ -4073,8 +4091,9 @@
         var h = Harness();
         var a = Var('a', 'Object');
         var s0 = FlowModel<Var, Type>(Reachability.initial).declare(a, false);
-        var s1 = s0.tryPromoteForTypeCheck(h, a, Type('int')).ifFalse;
-        var s2 = s0.tryPromoteForTypeCheck(h, a, Type('String')).ifFalse;
+        var s1 = s0.tryPromoteForTypeCheck(h, _varRef(a), Type('int')).ifFalse;
+        var s2 =
+            s0.tryPromoteForTypeCheck(h, _varRef(a), Type('String')).ifFalse;
         expect(
           s1.rebaseForward(h, s2).infoFor(a),
           _matchVariableModel(ofInterest: ['int', 'String']),
@@ -4651,3 +4670,6 @@
       'unassigned: ${_describeMatcher(unassignedMatcher)}, '
       'writeCaptured: ${_describeMatcher(writeCapturedMatcher)})');
 }
+
+Reference<Var, Type> _varRef(Var variable) =>
+    new VariableReference<Var, Type>(variable);
diff --git a/pkg/analysis_server/test/abstract_single_unit.dart b/pkg/analysis_server/test/abstract_single_unit.dart
index 1dca4d3..1a04502 100644
--- a/pkg/analysis_server/test/abstract_single_unit.dart
+++ b/pkg/analysis_server/test/abstract_single_unit.dart
@@ -7,9 +7,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/dart/error/hint_codes.dart';
-import 'package:analyzer/src/generated/java_engine.dart';
 import 'package:analyzer/src/test_utilities/find_element.dart';
 import 'package:analyzer/src/test_utilities/find_node.dart';
 import 'package:analyzer/src/test_utilities/platform.dart';
@@ -52,19 +50,6 @@
     return findOffset(search) + search.length;
   }
 
-  AstNode findNodeAtOffset(int offset, [Predicate<AstNode> predicate]) {
-    var result = NodeLocator(offset).searchWithin(testUnit);
-    if (result != null && predicate != null) {
-      result = result.thisOrAncestorMatching(predicate);
-    }
-    return result;
-  }
-
-  AstNode findNodeAtString(String search, [Predicate<AstNode> predicate]) {
-    var offset = findOffset(search);
-    return findNodeAtOffset(offset, predicate);
-  }
-
   int findOffset(String search) {
     var offset = testCode.indexOf(search);
     expect(offset, isNonNegative, reason: "Not found '$search' in\n$testCode");
diff --git a/pkg/analysis_server/test/services/correction/name_suggestion_test.dart b/pkg/analysis_server/test/services/correction/name_suggestion_test.dart
index a4dce49..243fd9b 100644
--- a/pkg/analysis_server/test/services/correction/name_suggestion_test.dart
+++ b/pkg/analysis_server/test/services/correction/name_suggestion_test.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/services/correction/name_suggestion.dart';
-import 'package:analyzer/dart/ast/ast.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -25,7 +24,7 @@
 }
 ''');
     var excluded = <String>{};
-    var expr = findNodeAtString('as String', (node) => node is AsExpression);
+    var expr = findNode.as_('as String');
     expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
         unorderedEquals(['sortedNodes', 'nodes']));
   }
@@ -39,8 +38,7 @@
 ''');
     var excluded = <String>{};
     var expectedType = findElement.localVar('node').type;
-    Expression assignedExpression =
-        findNodeAtString('null;', (node) => node is NullLiteral);
+    var assignedExpression = findNode.nullLiteral('null;');
     var suggestions = getVariableNameSuggestionsForExpression(
         expectedType, assignedExpression, excluded);
     expect(suggestions, unorderedEquals(['treeNode', 'node']));
@@ -53,7 +51,7 @@
 }
 ''');
     var expectedType = findElement.localVar('res').type;
-    Expression assignedExpression = findNodeAtString('0.0;');
+    var assignedExpression = findNode.doubleLiteral('0.0;');
     // first choice for "double" is "d"
     expect(
         getVariableNameSuggestionsForExpression(
@@ -73,7 +71,7 @@
 }
 ''');
     var expectedType = findElement.localVar('res').type;
-    Expression assignedExpression = findNodeAtString('0;');
+    var assignedExpression = findNode.integerLiteral('0;');
     // first choice for "int" is "i"
     expect(
         getVariableNameSuggestionsForExpression(
@@ -93,7 +91,7 @@
 }
 ''');
     var expectedType = findElement.localVar('res').type;
-    Expression assignedExpression = findNodeAtString("'abc';");
+    var assignedExpression = findNode.stringLiteral("'abc';");
     // first choice for "String" is "s"
     expect(
         getVariableNameSuggestionsForExpression(
@@ -110,7 +108,7 @@
 }
 ''');
     var excluded = <String>{};
-    var expr = findNodeAtString('new List');
+    var expr = findNode.instanceCreation('new List');
     expect(
         getVariableNameSuggestionsForExpression(null, expr, excluded,
             isMethod: false),
@@ -129,7 +127,7 @@
 }
 ''');
     var excluded = <String>{};
-    var expr = findNodeAtString('topNodes[0]').parent;
+    var expr = findNode.index('topNodes[0]');
     var names = getVariableNameSuggestionsForExpression(null, expr, excluded);
     expect(names, unorderedEquals(['topNode', 'node', 'object']));
   }
@@ -147,11 +145,11 @@
     var excluded = <String>{};
     expect(
         getVariableNameSuggestionsForExpression(
-            null, findNodeAtString('new NoSuchClass()'), excluded),
+            null, findNode.instanceCreation('new NoSuchClass()'), excluded),
         unorderedEquals(['noSuchClass', 'suchClass', 'class']));
     expect(
-        getVariableNameSuggestionsForExpression(
-            null, findNodeAtString('new NoSuchClass.named()'), excluded),
+        getVariableNameSuggestionsForExpression(null,
+            findNode.instanceCreation('new NoSuchClass.named()'), excluded),
         unorderedEquals(['noSuchClass', 'suchClass', 'class']));
     // TODO(scheglov) This test does not work.
     // In "p.NoSuchClass" the identifier "p" is not resolved to a PrefixElement.
@@ -172,17 +170,17 @@
 ''');
     var excluded = <String>{};
     {
-      var expr = findNodeAtString('111');
+      var expr = findNode.integerLiteral('111');
       expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
           unorderedEquals(['a']));
     }
     {
-      var expr = findNodeAtString('222');
+      var expr = findNode.integerLiteral('222');
       expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
           unorderedEquals(['b']));
     }
     {
-      var expr = findNodeAtString('333');
+      var expr = findNode.integerLiteral('333');
       expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
           unorderedEquals(['c']));
     }
@@ -197,17 +195,17 @@
 ''');
     var excluded = <String>{};
     {
-      var expr = findNodeAtString('111');
+      var expr = findNode.integerLiteral('111');
       expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
           unorderedEquals(['a']));
     }
     {
-      var expr = findNodeAtString('222');
+      var expr = findNode.integerLiteral('222');
       expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
           unorderedEquals(['b']));
     }
     {
-      var expr = findNodeAtString('333');
+      var expr = findNode.integerLiteral('333');
       expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
           unorderedEquals(['c']));
     }
@@ -222,12 +220,12 @@
 ''');
     var excluded = <String>{};
     {
-      var expr = findNodeAtString('111');
+      var expr = findNode.integerLiteral('111');
       expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
           unorderedEquals(['a']));
     }
     {
-      var expr = findNodeAtString('222');
+      var expr = findNode.integerLiteral('222');
       expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
           unorderedEquals(['b']));
     }
@@ -240,7 +238,7 @@
 }
 ''');
     var excluded = <String>{};
-    var expr = findNodeAtString('p.get', (node) => node is MethodInvocation);
+    var expr = findNode.methodInvocation('p.get');
     expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
         unorderedEquals(['sortedNodes', 'nodes']));
   }
@@ -252,7 +250,7 @@
 }
 ''');
     var excluded = <String>{};
-    var expr = findNodeAtString('p.sorted', (node) => node is MethodInvocation);
+    var expr = findNode.methodInvocation('p.sorted');
     expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
         unorderedEquals(['sortedNodes', 'nodes']));
   }
@@ -264,7 +262,7 @@
 }
 ''');
     var excluded = <String>{};
-    var expr = findNodeAtString('p.get', (node) => node is MethodInvocation);
+    var expr = findNode.methodInvocation('p.get');
     expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
         unorderedEquals([]));
   }
@@ -278,9 +276,7 @@
     var excluded = <String>{};
     expect(
         getVariableNameSuggestionsForExpression(
-            null,
-            findNodeAtString('p.sorted', (node) => node is PrefixedIdentifier),
-            excluded),
+            null, findNode.prefixed('p.sorted'), excluded),
         unorderedEquals(['sortedNodes', 'nodes']));
   }
 
@@ -294,15 +290,11 @@
     var excluded = <String>{};
     expect(
         getVariableNameSuggestionsForExpression(
-            null,
-            findNodeAtString('p._name', (node) => node is PrefixedIdentifier),
-            excluded),
+            null, findNode.prefixed('p._name'), excluded),
         unorderedEquals(['name']));
     expect(
         getVariableNameSuggestionsForExpression(
-            null,
-            findNodeAtString('p._compute', (node) => node is MethodInvocation),
-            excluded),
+            null, findNode.methodInvocation('p._compute'), excluded),
         unorderedEquals(['computeSuffix', 'suffix']));
   }
 
@@ -313,8 +305,7 @@
 }
 ''');
     var excluded = <String>{};
-    PropertyAccess expression =
-        findNodeAtString('p.q.sorted', (node) => node is PropertyAccess);
+    var expression = findNode.propertyAccess('.sorted');
     expect(getVariableNameSuggestionsForExpression(null, expression, excluded),
         unorderedEquals(['sortedNodes', 'nodes']));
   }
@@ -327,7 +318,7 @@
 }
 ''');
     var excluded = <String>{};
-    var expr = findNodeAtString('sortedNodes;');
+    var expr = findNode.simple('sortedNodes;');
     expect(getVariableNameSuggestionsForExpression(null, expr, excluded),
         unorderedEquals(['sortedNodes', 'nodes']));
   }
@@ -342,10 +333,7 @@
     var excluded = <String>{};
     expect(
         getVariableNameSuggestionsForExpression(
-            null,
-            findNodeAtString(
-                'getSortedNodes();', (node) => node is MethodInvocation),
-            excluded),
+            null, findNode.methodInvocation('getSortedNodes();'), excluded),
         unorderedEquals(['sortedNodes', 'nodes']));
   }
 
diff --git a/pkg/analysis_server/test/services/correction/status_test.dart b/pkg/analysis_server/test/services/correction/status_test.dart
index ac7f555..96fb856 100644
--- a/pkg/analysis_server/test/services/correction/status_test.dart
+++ b/pkg/analysis_server/test/services/correction/status_test.dart
@@ -60,7 +60,7 @@
 main() {
 }
 ''');
-    var node = findNodeAtString('main');
+    var node = findNode.simple('main');
     // check
     var location = newLocation_fromNode(node);
     expect(location.file, testFile);
diff --git a/pkg/analysis_server/test/services/correction/util_test.dart b/pkg/analysis_server/test/services/correction/util_test.dart
index ad29a506..cba88ef 100644
--- a/pkg/analysis_server/test/services/correction/util_test.dart
+++ b/pkg/analysis_server/test/services/correction/util_test.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/services/correction/util.dart';
-import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:analyzer_plugin/src/utilities/string_utilities.dart';
@@ -33,7 +32,7 @@
   }
 }
 ''');
-    IfStatement ifStatement = findNodeAtString('if (');
+    var ifStatement = findNode.ifStatement('if (');
     var condition = ifStatement.condition;
     var result = CorrectionUtils(testAnalysisResult).invertCondition(condition);
     expect(result, expected);
diff --git a/pkg/analysis_server/test/services/refactoring/rename_import_test.dart b/pkg/analysis_server/test/services/refactoring/rename_import_test.dart
index da9fa1c..b0305ee 100644
--- a/pkg/analysis_server/test/services/refactoring/rename_import_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/rename_import_test.dart
@@ -2,7 +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.
 
-import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -225,8 +224,7 @@
   }
 
   void _createRefactoring(String search) {
-    ImportDirective directive =
-        findNodeAtString(search, (node) => node is ImportDirective);
+    var directive = findNode.import(search);
     createRenameRefactoringForElement(directive.element);
   }
 }
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart
index 89ec9da..f462b36 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart
@@ -22,7 +22,7 @@
       {List<String> expectedComponents,
       List<ElementKind> expectedKinds,
       List<String> expectedUris}) {
-    var node = findNodeAtString(search);
+    var node = findNode.any(search);
     var matcher = ElementMatcher.forNode(node);
     if (expectedUris != null) {
       expect(matcher.importedUris,
diff --git a/pkg/analysis_server/test/src/utilities/flutter_test.dart b/pkg/analysis_server/test/src/utilities/flutter_test.dart
index 0d8276a..cce042e 100644
--- a/pkg/analysis_server/test/src/utilities/flutter_test.dart
+++ b/pkg/analysis_server/test/src/utilities/flutter_test.dart
@@ -165,16 +165,14 @@
 Text createText(String txt) => new Text(txt);
 ''');
     {
-      MethodInvocation invocation = findNodeAtString(
-          'createEmptyText();', (node) => node is MethodInvocation);
+      var invocation = findNode.methodInvocation('createEmptyText();');
       expect(_flutter.identifyWidgetExpression(invocation), invocation);
       var argumentList = invocation.argumentList;
       expect(_flutter.identifyWidgetExpression(argumentList), isNull);
     }
 
     {
-      MethodInvocation invocation = findNodeAtString(
-          "createText('xyz');", (node) => node is MethodInvocation);
+      var invocation = findNode.methodInvocation("createText('xyz');");
       expect(_flutter.identifyWidgetExpression(invocation), invocation);
       var argumentList = invocation.argumentList;
       expect(_flutter.identifyWidgetExpression(argumentList), isNull);
@@ -195,7 +193,7 @@
 
 Text createEmptyText() => new Text('');
 ''');
-    Expression childExpression = findNodeAtString('child: ');
+    var childExpression = findNode.namedExpression('child: ');
     expect(_flutter.identifyWidgetExpression(childExpression), isNull);
   }
 
@@ -212,7 +210,7 @@
   foo.bar; // ref
 }
 ''');
-    SimpleIdentifier bar = findNodeAtString('bar; // ref');
+    var bar = findNode.simple('bar; // ref');
     expect(_flutter.identifyWidgetExpression(bar), bar.parent);
   }
 
@@ -229,7 +227,7 @@
   foo.bar; // ref
 }
 ''');
-    SimpleIdentifier foo = findNodeAtString('foo.bar');
+    var foo = findNode.simple('foo.bar');
     expect(_flutter.identifyWidgetExpression(foo), foo.parent);
   }
 
@@ -241,7 +239,7 @@
   widget; // ref
 }
 ''');
-    Expression expression = findNodeAtString('widget; // ref');
+    var expression = findNode.simple('widget; // ref');
     expect(_flutter.identifyWidgetExpression(expression), expression);
   }
 
@@ -258,12 +256,12 @@
 ''');
     expect(_flutter.identifyWidgetExpression(null), isNull);
     {
-      Expression expression = findNodeAtString('42;');
+      var expression = findNode.integerLiteral('42;');
       expect(_flutter.identifyWidgetExpression(expression), isNull);
     }
 
     {
-      Expression expression = findNodeAtString('intVariable;');
+      var expression = findNode.simple('intVariable;');
       expect(_flutter.identifyWidgetExpression(expression), isNull);
     }
   }
@@ -279,7 +277,7 @@
 
 void useWidget(Widget w) {}
 ''');
-    Expression expression = findNodeAtString('text); // ref');
+    var expression = findNode.simple('text); // ref');
     expect(_flutter.identifyWidgetExpression(expression), expression);
   }
 
@@ -323,10 +321,10 @@
   condition ? w1 : w2;
 }
 ''');
-    Expression thenWidget = findNodeAtString('w1 :');
+    var thenWidget = findNode.simple('w1 :');
     expect(_flutter.identifyWidgetExpression(thenWidget), thenWidget);
 
-    Expression elseWidget = findNodeAtString('w2;');
+    var elseWidget = findNode.simple('w2;');
     expect(_flutter.identifyWidgetExpression(elseWidget), elseWidget);
   }
 
@@ -337,7 +335,7 @@
 
 main(Widget widget) => widget; // ref
 ''');
-    Expression expression = findNodeAtString('widget; // ref');
+    var expression = findNode.simple('widget; // ref');
     expect(_flutter.identifyWidgetExpression(expression), expression);
   }
 
@@ -350,7 +348,7 @@
   widget; // ref
 }
 ''');
-    Expression expression = findNodeAtString('widget; // ref');
+    var expression = findNode.simple('widget; // ref');
     expect(_flutter.identifyWidgetExpression(expression), expression);
   }
 
@@ -400,7 +398,7 @@
   return [widget]; // ref
 }
 ''');
-    Expression expression = findNodeAtString('widget]; // ref');
+    var expression = findNode.simple('widget]; // ref');
     expect(_flutter.identifyWidgetExpression(expression), expression);
   }
 
@@ -415,7 +413,7 @@
 
 void useWidget({Widget child}) {}
 ''');
-    Expression expression = findNodeAtString('text); // ref');
+    var expression = findNode.simple('text); // ref');
     expect(_flutter.identifyWidgetExpression(expression), expression);
   }
 
@@ -427,7 +425,7 @@
   return widget; // ref
 }
 ''');
-    Expression expression = findNodeAtString('widget; // ref');
+    var expression = findNode.simple('widget; // ref');
     expect(_flutter.identifyWidgetExpression(expression), expression);
   }
 
@@ -494,46 +492,44 @@
 Text createEmptyText() => new Text('');
 ''');
     {
-      Expression expression = findNodeAtString('named(); // use');
+      var expression = findNode.simple('named(); // use');
       expect(_flutter.isWidgetExpression(expression), isFalse);
       var creation = expression.parent.parent as InstanceCreationExpression;
       expect(_flutter.isWidgetExpression(creation), isTrue);
     }
 
     {
-      Expression expression = findNodeAtString("new Text('abc')");
+      var expression = findNode.instanceCreation("new Text('abc')");
       expect(_flutter.isWidgetExpression(expression), isTrue);
     }
 
     {
-      Expression expression = findNodeAtString('text;');
+      var expression = findNode.simple('text;');
       expect(_flutter.isWidgetExpression(expression), isTrue);
     }
 
     {
-      Expression expression = findNodeAtString(
-          'createEmptyText();', (node) => node is MethodInvocation);
+      var expression = findNode.methodInvocation('createEmptyText();');
       expect(_flutter.isWidgetExpression(expression), isTrue);
     }
 
     {
-      SimpleIdentifier expression = findNodeAtString('Container(');
+      var expression = findNode.simple('Container(');
       expect(_flutter.isWidgetExpression(expression), isFalse);
     }
 
     {
-      NamedExpression expression =
-          findNodeAtString('child: ', (n) => n is NamedExpression);
+      var expression = findNode.namedExpression('child: ');
       expect(_flutter.isWidgetExpression(expression), isFalse);
     }
 
     {
-      Expression expression = findNodeAtString('42;');
+      var expression = findNode.integerLiteral('42;');
       expect(_flutter.isWidgetExpression(expression), isFalse);
     }
 
     {
-      Expression expression = findNodeAtString('intVariable;');
+      var expression = findNode.simple('intVariable;');
       expect(_flutter.isWidgetExpression(expression), isFalse);
     }
   }
diff --git a/pkg/analysis_server/tool/code_completion/corpus.dart b/pkg/analysis_server/tool/code_completion/corpus.dart
index 71e09fb..8f711d2 100644
--- a/pkg/analysis_server/tool/code_completion/corpus.dart
+++ b/pkg/analysis_server/tool/code_completion/corpus.dart
@@ -91,8 +91,9 @@
 
 Future<String> _getBody(String url) async => (await _getResponse(url)).body;
 
-Future<http.Response> _getResponse(String url) async => _client
-    .get(url, headers: const {'User-Agent': 'dart.pkg.completion_metrics'});
+Future<http.Response> _getResponse(String url) async =>
+    _client.get(Uri.parse(url),
+        headers: const {'User-Agent': 'dart.pkg.completion_metrics'});
 
 bool _hasPubspec(FileSystemEntity f) =>
     f is Directory && File(path.join(f.path, 'pubspec.yaml')).existsSync();
diff --git a/pkg/analyzer/lib/src/test_utilities/find_node.dart b/pkg/analyzer/lib/src/test_utilities/find_node.dart
index 11bd738..91afc47 100644
--- a/pkg/analyzer/lib/src/test_utilities/find_node.dart
+++ b/pkg/analyzer/lib/src/test_utilities/find_node.dart
@@ -167,6 +167,10 @@
     return _node(search, (n) => n is IfElement);
   }
 
+  IfStatement ifStatement(String search) {
+    return _node(search, (n) => n is IfStatement);
+  }
+
   ImportDirective import(String search) {
     return _node(search, (n) => n is ImportDirective);
   }
diff --git a/pkg/analyzer_plugin/test/support/abstract_single_unit.dart b/pkg/analyzer_plugin/test/support/abstract_single_unit.dart
index 96d22e0..ca144aa 100644
--- a/pkg/analyzer_plugin/test/support/abstract_single_unit.dart
+++ b/pkg/analyzer_plugin/test/support/abstract_single_unit.dart
@@ -21,8 +21,6 @@
   String testCode;
   String testFile;
   CompilationUnit testUnit;
-  CompilationUnitElement testUnitElement;
-  LibraryElement testLibraryElement;
   FindNode findNode;
   FindElement findElement;
 
@@ -90,8 +88,12 @@
     return length;
   }
 
-  Future<void> resolveTestUnit(String code) async {
+  Future<void> resolveTestCode(String code) async {
     addTestSource(code);
+    await resolveTestFile();
+  }
+
+  Future resolveTestFile() async {
     var result = await resolveFile(testFile);
     testCode = result.content;
     testUnit = result.unit;
@@ -106,8 +108,6 @@
             error.errorCode != HintCode.UNUSED_LOCAL_VARIABLE;
       }), isEmpty);
     }
-    testUnitElement = testUnit.declaredElement;
-    testLibraryElement = testUnitElement.library;
     findNode = FindNode(testCode, testUnit);
     findElement = FindElement(testUnit);
   }
diff --git a/pkg/analyzer_plugin/test/utilities/analyzer_converter_test.dart b/pkg/analyzer_plugin/test/utilities/analyzer_converter_test.dart
index 49b70c5..db1a6fc 100644
--- a/pkg/analyzer_plugin/test/utilities/analyzer_converter_test.dart
+++ b/pkg/analyzer_plugin/test/utilities/analyzer_converter_test.dart
@@ -199,7 +199,7 @@
   }
 
   Future<void> test_convertElement_class() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 @deprecated
 abstract class _A {}
 class B<K, V> {}''');
@@ -237,7 +237,7 @@
   }
 
   Future<void> test_convertElement_constructor() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 class A {
   const A.myConstructor(int a, [String b]);
 }''');
@@ -273,7 +273,7 @@
   }
 
   Future<void> test_convertElement_enum() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 @deprecated
 enum _E1 { one, two }
 enum E2 { three, four }''');
@@ -311,7 +311,7 @@
   }
 
   Future<void> test_convertElement_enumConstant() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 @deprecated
 enum _E1 { one, two }
 enum E2 { three, four }''');
@@ -399,7 +399,7 @@
   }
 
   Future<void> test_convertElement_field() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 class A {
   static const myField = 42;
 }''');
@@ -423,7 +423,7 @@
   }
 
   Future<void> test_convertElement_functionTypeAlias() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 typedef int F<T>(String x);
 ''');
     var engineElement = findElement.typeAlias('F');
@@ -446,7 +446,7 @@
   }
 
   Future<void> test_convertElement_genericTypeAlias_function() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 typedef F<T> = int Function(String x);
 ''');
     var engineElement = findElement.typeAlias('F');
@@ -469,7 +469,7 @@
   }
 
   Future<void> test_convertElement_getter() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 class A {
   int get myGetter => 42;
 }''');
@@ -492,7 +492,7 @@
   }
 
   Future<void> test_convertElement_method() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 class A {
   static List<String> myMethod(int a, {String b, int c}) {
     return null;
@@ -517,7 +517,7 @@
   }
 
   Future<void> test_convertElement_setter() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 class A {
   set mySetter(String x) {}
 }''');
@@ -590,7 +590,7 @@
   }
 
   Future<void> test_fromElement_LABEL() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 main() {
 myLabel:
   while (true) {
diff --git a/pkg/analyzer_plugin/test/utilities/range_factory_test.dart b/pkg/analyzer_plugin/test/utilities/range_factory_test.dart
index e157ab5..0250c35 100644
--- a/pkg/analyzer_plugin/test/utilities/range_factory_test.dart
+++ b/pkg/analyzer_plugin/test/utilities/range_factory_test.dart
@@ -32,7 +32,7 @@
   }
 
   Future<void> test_argumentRange_all_mixed_noTrailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1, c: 2);
 }
@@ -42,7 +42,7 @@
   }
 
   Future<void> test_argumentRange_all_mixed_trailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1, c: 2, );
 }
@@ -52,7 +52,7 @@
   }
 
   Future<void> test_argumentRange_all_named_noTrailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(a: 0, b: 1, c: 2);
 }
@@ -62,7 +62,7 @@
   }
 
   Future<void> test_argumentRange_all_named_trailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(a: 0, b: 1, c: 2, );
 }
@@ -72,7 +72,7 @@
   }
 
   Future<void> test_argumentRange_all_positional_noTrailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1, 2);
 }
@@ -82,7 +82,7 @@
   }
 
   Future<void> test_argumentRange_all_positional_trailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1, 2, );
 }
@@ -92,7 +92,7 @@
   }
 
   Future<void> test_argumentRange_first_noTrailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1);
 }
@@ -102,7 +102,7 @@
   }
 
   Future<void> test_argumentRange_first_trailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1, );
 }
@@ -112,7 +112,7 @@
   }
 
   Future<void> test_argumentRange_last_noTrailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1);
 }
@@ -122,7 +122,7 @@
   }
 
   Future<void> test_argumentRange_last_trailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1, );
 }
@@ -132,7 +132,7 @@
   }
 
   Future<void> test_argumentRange_middle_noTrailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1, 2, 3);
 }
@@ -142,7 +142,7 @@
   }
 
   Future<void> test_argumentRange_middle_trailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0, 1, 2, 3, );
 }
@@ -152,7 +152,7 @@
   }
 
   Future<void> test_argumentRange_only_named() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(a: 0);
 }
@@ -162,7 +162,7 @@
   }
 
   Future<void> test_argumentRange_only_positional() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(0);
 }
@@ -172,13 +172,13 @@
   }
 
   Future<void> test_elementName() async {
-    await resolveTestUnit('class ABC {}');
+    await resolveTestCode('class ABC {}');
     var element = findElement.class_('ABC');
     expect(range.elementName(element), SourceRange(6, 3));
   }
 
   Future<void> test_endEnd() async {
-    await resolveTestUnit('main() {}');
+    await resolveTestCode('main() {}');
     var mainFunction = testUnit.declarations[0] as FunctionDeclaration;
     var mainName = mainFunction.name;
     var mainBody = mainFunction.functionExpression.body;
@@ -186,14 +186,14 @@
   }
 
   Future<void> test_endLength() async {
-    await resolveTestUnit('main() {}');
+    await resolveTestCode('main() {}');
     var mainFunction = testUnit.declarations[0] as FunctionDeclaration;
     var mainName = mainFunction.name;
     expect(range.endLength(mainName, 3), SourceRange(4, 3));
   }
 
   Future<void> test_endStart() async {
-    await resolveTestUnit('main() {}');
+    await resolveTestCode('main() {}');
     var mainFunction = testUnit.declarations[0] as FunctionDeclaration;
     var mainName = mainFunction.name;
     var mainBody = mainFunction.functionExpression.body;
@@ -206,14 +206,14 @@
   }
 
   Future<void> test_node() async {
-    await resolveTestUnit('main() {}');
+    await resolveTestCode('main() {}');
     var mainFunction = testUnit.declarations[0] as FunctionDeclaration;
     var mainName = mainFunction.name;
     expect(range.node(mainName), SourceRange(0, 4));
   }
 
   Future<void> test_nodeInList_argumentList_first_named() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(a: 1, b: 2);
 }
@@ -224,7 +224,7 @@
   }
 
   Future<void> test_nodeInList_argumentList_first_positional() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(1, 2);
 }
@@ -235,7 +235,7 @@
   }
 
   Future<void> test_nodeInList_argumentList_last_named() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(a: 1, b: 2);
 }
@@ -246,7 +246,7 @@
   }
 
   Future<void> test_nodeInList_argumentList_last_positional() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(1, 2);
 }
@@ -257,7 +257,7 @@
   }
 
   Future<void> test_nodeInList_argumentList_middle_named() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(a: 1, b: 2, c: 3);
 }
@@ -268,7 +268,7 @@
   }
 
   Future<void> test_nodeInList_argumentList_middle_positional() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(1, 2, 3);
 }
@@ -279,7 +279,7 @@
   }
 
   Future<void> test_nodeInList_argumentList_only_named() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(a: 1);
 }
@@ -290,7 +290,7 @@
   }
 
   Future<void> test_nodeInList_argumentList_only_named_trailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(a: 1,);
 }
@@ -301,7 +301,7 @@
   }
 
   Future<void> test_nodeInList_argumentList_only_positional() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(1);
 }
@@ -313,7 +313,7 @@
 
   Future<void>
       test_nodeInList_argumentList_only_positional_trailingComma() async {
-    await resolveTestUnit('''
+    await resolveTestCode('''
 void f() {
   g(1,);
 }
@@ -324,7 +324,7 @@
   }
 
   Future<void> test_nodes() async {
-    await resolveTestUnit(' main() {}');
+    await resolveTestCode(' main() {}');
     var mainFunction = testUnit.declarations[0] as FunctionDeclaration;
     var mainName = mainFunction.name;
     var mainBody = mainFunction.functionExpression.body;
@@ -332,7 +332,7 @@
   }
 
   Future<void> test_nodes_empty() async {
-    await resolveTestUnit('main() {}');
+    await resolveTestCode('main() {}');
     expect(range.nodes([]), SourceRange(0, 0));
   }
 
@@ -341,7 +341,7 @@
   }
 
   Future<void> test_startEnd_nodeNode() async {
-    await resolveTestUnit(' main() {}');
+    await resolveTestCode(' main() {}');
     var mainFunction = testUnit.declarations[0] as FunctionDeclaration;
     var mainName = mainFunction.name;
     var mainBody = mainFunction.functionExpression.body;
@@ -349,7 +349,7 @@
   }
 
   Future<void> test_startLength_node() async {
-    await resolveTestUnit(' main() {}');
+    await resolveTestCode(' main() {}');
     var mainFunction = testUnit.declarations[0] as FunctionDeclaration;
     var mainName = mainFunction.name;
     expect(range.startLength(mainName, 10), SourceRange(1, 10));
@@ -360,7 +360,7 @@
   }
 
   Future<void> test_startStart_nodeNode() async {
-    await resolveTestUnit('main() {}');
+    await resolveTestCode('main() {}');
     var mainFunction = testUnit.declarations[0] as FunctionDeclaration;
     var mainName = mainFunction.name;
     var mainBody = mainFunction.functionExpression.body;
@@ -368,7 +368,7 @@
   }
 
   Future<void> test_token() async {
-    await resolveTestUnit(' main() {}');
+    await resolveTestCode(' main() {}');
     var mainFunction = testUnit.declarations[0] as FunctionDeclaration;
     var mainName = mainFunction.name;
     expect(range.token(mainName.beginToken), SourceRange(1, 4));
diff --git a/pkg/compiler/test/sourcemaps/tools/translate_dart2js_stacktrace.dart b/pkg/compiler/test/sourcemaps/tools/translate_dart2js_stacktrace.dart
index c54f9a3..0451c24 100644
--- a/pkg/compiler/test/sourcemaps/tools/translate_dart2js_stacktrace.dart
+++ b/pkg/compiler/test/sourcemaps/tools/translate_dart2js_stacktrace.dart
@@ -29,7 +29,7 @@
   String url = options.rest[0];
   String data;
   if (url.startsWith("http://") || url.startsWith("https://")) {
-    data = (await http.get(url)).body;
+    data = (await http.get(Uri.parse(url))).body;
   } else {
     data = new File(url).readAsStringSync();
   }
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index d1c4f9d..c7a0a91 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -6,7 +6,8 @@
 
 import 'dart:core' hide MapEntry;
 
-import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
+import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart'
+    hide Reference; // Work around https://github.com/dart-lang/sdk/issues/44667
 
 import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
 
@@ -40,7 +41,8 @@
 
 import 'package:_fe_analyzer_shared/src/util/link.dart';
 
-import 'package:kernel/ast.dart';
+import 'package:kernel/ast.dart'
+    hide Reference; // Work around https://github.com/dart-lang/sdk/issues/44667
 import 'package:kernel/type_environment.dart';
 
 import '../builder/builder.dart';
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
index 4c804fe..37f51ab 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
@@ -4,14 +4,16 @@
 
 import 'dart:core' hide MapEntry;
 
-import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
+import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart'
+    hide Reference; // Work around https://github.com/dart-lang/sdk/issues/44667
 
 import 'package:_fe_analyzer_shared/src/util/link.dart';
 
 import 'package:front_end/src/fasta/kernel/internal_ast.dart';
 import 'package:front_end/src/fasta/type_inference/type_demotion.dart';
 
-import 'package:kernel/ast.dart';
+import 'package:kernel/ast.dart'
+    hide Reference; // Work around https://github.com/dart-lang/sdk/issues/44667
 import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
 import 'package:kernel/core_types.dart' show CoreTypes;
 import 'package:kernel/type_algebra.dart';
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index f719e36e..1324c7e 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -1100,6 +1100,7 @@
 sticky
 stmt
 stopped
+storage
 str
 strategies
 streak
@@ -1239,6 +1240,7 @@
 unconditionally
 unconstrained
 undeclare
+undergo
 undo
 undoable
 unequal
diff --git a/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart b/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart
index 23be8f3..4779532 100644
--- a/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart
+++ b/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart
@@ -396,16 +396,34 @@
       VersionConstraint currentConstraint;
       if (node.value is String) {
         currentConstraint = VersionConstraint.parse(node.value as String);
-        if (currentConstraint is VersionRange &&
-            currentConstraint.min >= minimumVersion) {
-          // The current version constraint is already up to date.  Do not edit.
+        var invalidVersionMessage =
+            'The current SDK constraint in pubspec.yaml is invalid. A '
+            'minimum version, such as ">=2.7.0", is required when launching '
+            "'dart migrate'.";
+        if (currentConstraint is Version) {
+          // In this case, the constraint is an exact version, like 2.0.0.
+          _logger.stderr(invalidVersionMessage);
           return false;
+        } else if (currentConstraint is VersionRange) {
+          if (currentConstraint.min == null) {
+            _logger.stderr(invalidVersionMessage);
+            return false;
+          } else if (currentConstraint.min >= minimumVersion) {
+            // The current version constraint is already up to date.  Do not
+            // edit.
+            return false;
+          } else {
+            // TODO(srawlins): This overwrites the current maximum version. In
+            // the uncommon situation that there is a special maximum, it should
+            // not.
+            pubspec._replaceSpan(node.span, fullVersionConstraint, listener);
+            return true;
+          }
         } else {
-          // TODO(srawlins): This overwrites the current maximum version. In
-          // the uncommon situation that there is a special maximum, it should
-          // not.
-          pubspec._replaceSpan(node.span, fullVersionConstraint, listener);
-          return true;
+          // The constraint is something different, like a union, like
+          // '>=1.0.0 <2.0.0 >=3.0.0 <4.0.0', which is not valid.
+          _logger.stderr(invalidVersionMessage);
+          return false;
         }
       } else {
         // Something is odd with the constraint we've found in pubspec.yaml;
diff --git a/pkg/nnbd_migration/lib/src/front_end/resources/index.html b/pkg/nnbd_migration/lib/src/front_end/resources/index.html
index f56877d..f24e6a8 100644
--- a/pkg/nnbd_migration/lib/src/front_end/resources/index.html
+++ b/pkg/nnbd_migration/lib/src/front_end/resources/index.html
@@ -26,7 +26,7 @@
     <h2 class="before-apply">Proposed null safety changes</h2>
     <h2 class="after-apply">&#10003; Null safety migration applied</h2>
     <a target="_blank"
-       href="https://goo.gle/dart-null-safety-migration-tool">
+       href="https://dart.dev/go/null-safety-migration">
       <button class="action-button">
         <i class="material-icons">launch</i>
         <span>Help</span>
diff --git a/pkg/nnbd_migration/lib/src/front_end/resources/resources.g.dart b/pkg/nnbd_migration/lib/src/front_end/resources/resources.g.dart
index 6b0f34f..735dc5d 100644
--- a/pkg/nnbd_migration/lib/src/front_end/resources/resources.g.dart
+++ b/pkg/nnbd_migration/lib/src/front_end/resources/resources.g.dart
@@ -7422,7 +7422,7 @@
 ''';
 
 String _index_html;
-// index_html md5 is '4dc4d54418f267d0f9189690626e0f2a'
+// index_html md5 is 'c4127573314e7a91769cf9e0bcb489b1'
 String _index_html_base64 = '''
 PGh0bWw+CjxoZWFkPgogICAgPHRpdGxlPk51bGwgU2FmZXR5IFByZXZpZXc8L3RpdGxlPgogICAgPHNj
 cmlwdCBzcmM9Int7IGhpZ2hsaWdodEpzUGF0aCB9fSI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0Pnt7IGRh
@@ -7443,53 +7443,53 @@
 YWx0PSJEYXJ0UGFkIExvZ28iIGNsYXNzPSJsb2dvIi8+CiAgICA8aDE+RGFydDwvaDE+CiAgICA8aDIg
 Y2xhc3M9ImJlZm9yZS1hcHBseSI+UHJvcG9zZWQgbnVsbCBzYWZldHkgY2hhbmdlczwvaDI+CiAgICA8
 aDIgY2xhc3M9ImFmdGVyLWFwcGx5Ij4mIzEwMDAzOyBOdWxsIHNhZmV0eSBtaWdyYXRpb24gYXBwbGll
-ZDwvaDI+CiAgICA8YSB0YXJnZXQ9Il9ibGFuayIKICAgICAgIGhyZWY9Imh0dHBzOi8vZ29vLmdsZS9k
-YXJ0LW51bGwtc2FmZXR5LW1pZ3JhdGlvbi10b29sIj4KICAgICAgPGJ1dHRvbiBjbGFzcz0iYWN0aW9u
-LWJ1dHRvbiI+CiAgICAgICAgPGkgY2xhc3M9Im1hdGVyaWFsLWljb25zIj5sYXVuY2g8L2k+CiAgICAg
-ICAgPHNwYW4+SGVscDwvc3Bhbj4KICAgICAgPC9idXR0b24+CiAgICA8L2E+CiAgICA8YnV0dG9uIGNs
-YXNzPSJhY3Rpb24tYnV0dG9uIGFwcGx5LW1pZ3JhdGlvbiI+CiAgICAgICAgPGkgY2xhc3M9Im1hdGVy
-aWFsLWljb25zIj5lZGl0PC9pPgogICAgICAgIDxzcGFuIGNsYXNzPSJsYWJlbCI+CiAgICAgICAgICBB
-cHBseSBNaWdyYXRpb24KICAgICAgICA8L3NwYW4+CiAgICA8L2J1dHRvbj4KICAgIDxidXR0b24gY2xh
-c3M9ImFjdGlvbi1idXR0b24gYXBwbHktbWlncmF0aW9uIiBkaXNhYmxlZD4KICAgICAgICA8aSBjbGFz
-cz0ibWF0ZXJpYWwtaWNvbnMiPmVkaXQ8L2k+CiAgICAgICAgPHNwYW4gY2xhc3M9ImxhYmVsIj4KICAg
-ICAgICAgIEFwcGx5IE1pZ3JhdGlvbgogICAgICAgIDwvc3Bhbj4KICAgIDwvYnV0dG9uPgogICAgPGJ1
-dHRvbiBjbGFzcz0iYWN0aW9uLWJ1dHRvbiByZXJ1bi1taWdyYXRpb24gYmVmb3JlLWFwcGx5Ij4KICAg
-ICAgPHNwYW4gY2xhc3M9Im9wdGlvbmFsIj4KICAgICAgICA8aSBjbGFzcz0ibWF0ZXJpYWwtaWNvbnMi
-PnJlcGxheTwvaT4KICAgICAgICBSZXJ1biBGcm9tIFNvdXJjZXMKICAgICAgPC9zcGFuPgogICAgICA8
-c3BhbiBjbGFzcz0icmVxdWlyZWQiPgogICAgICAgIDxpIGNsYXNzPSJtYXRlcmlhbC1pY29ucyI+d2Fy
-bmluZzwvaT4KICAgICAgICBSZXJ1biBXaXRoIENoYW5nZXMKICAgICAgPC9zcGFuPgogICAgPC9idXR0
-b24+CjwvaGVhZGVyPgo8ZGl2IGNsYXNzPSJwYW5lbHMgaG9yaXpvbnRhbCI+CiAgICA8ZGl2IGNsYXNz
-PSJuYXYtcGFuZWwiPgogICAgICAgIDxkaXYgY2xhc3M9Im5hdi1pbm5lciI+CiAgICAgICAgICAgIDxk
-aXYgY2xhc3M9InBhbmVsLWhlYWRpbmciPlByb2plY3QgRmlsZXM8L2Rpdj4KICAgICAgICAgICAgPGRp
-diBjbGFzcz0ibmF2LXRyZWUiPjwvZGl2PgogICAgICAgIDwvZGl2PjwhLS0gL25hdi1pbm5lciAtLT4K
-ICAgIDwvZGl2PjwhLS0gL25hdiAtLT4KICAgIDxkaXYgY2xhc3M9ImZpbGUiPgogICAgICAgIDxkaXYg
-Y2xhc3M9InRpdGxlLWJhciI+CiAgICAgICAgICAgIDxoMyBpZD0idW5pdC1uYW1lIj4mbmJzcDs8L2gz
-PgogICAgICAgICAgICA8c3BhbiBpZD0ibWlncmF0ZS11bml0LXN0YXR1cy1pY29uLWxhYmVsIj5NaWdy
-YXRlCiAgICAgICAgICAgICAgICA8c3BhbgogICAgICAgICAgICAgICAgICAgIGNsYXNzPSJtYXRlcmlh
-bC1pY29ucyBzdGF0dXMtaWNvbiBtaWdyYXRpbmciCiAgICAgICAgICAgICAgICAgICAgaWQ9Im1pZ3Jh
-dGUtdW5pdC1zdGF0dXMtaWNvbiI+Y2hlY2tfYm94PC9zcGFuPgogICAgICAgICAgICA8L3NwYW4+CiAg
-ICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0iY29udGVudCI+CiAgICAgICAgICAgIDxkaXYg
-Y2xhc3M9InJlZ2lvbnMiPgogICAgICAgICAgICAgICAgPCEtLSBUaGUgcmVnaW9ucyBvdmVybGF5IGNv
-ZGUgY29weSBvZiB0aGUgY29udGVudCB0byBwcm92aWRlIC0tPgogICAgICAgICAgICAgICAgPCEtLSB0
-b29sdGlwcyBmb3IgbW9kaWZpZWQgcmVnaW9ucy4gLS0+CiAgICAgICAgICAgIDwvZGl2PjwhLS0gL3Jl
-Z2lvbnMgLS0+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvZGUiPgogICAgICAgICAgICAgICAgPCEt
-LSBDb21waWxhdGlvbiB1bml0IGNvbnRlbnQgaXMgd3JpdHRlbiBoZXJlLiAtLT4KICAgICAgICAgICAg
-ICAgIDxwIGNsYXNzPSJ3ZWxjb21lIj4KICAgICAgICAgICAgICAgICAgICBTZWxlY3QgYSBzb3VyY2Ug
-ZmlsZSBvbiB0aGUgbGVmdCB0byBwcmV2aWV3IHRoZSBwcm9wb3NlZCBlZGl0cy4KICAgICAgICAgICAg
-ICAgIDwvcD4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICA8L2Rpdj48IS0tIC9j
-b250ZW50IC0tPgogICAgPGRpdiBjbGFzcz0iaW5mby1wYW5lbCI+CiAgICAgICAgPGRpdiBjbGFzcz0i
-ZWRpdC1saXN0Ij4KICAgICAgICAgICAgPGRpdiBjbGFzcz0icGFuZWwtaGVhZGluZyI+UHJvcG9zZWQg
-RWRpdHM8L2Rpdj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0icGFuZWwtY29udGVudCI+PC9kaXY+CiAg
-ICAgICAgPC9kaXY+PCEtLSAvZWRpdC1saXN0IC0tPgogICAgICAgIDxkaXYgY2xhc3M9ImVkaXQtcGFu
-ZWwiPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJwYW5lbC1oZWFkaW5nIj5FZGl0IERldGFpbHM8L2Rp
-dj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0icGFuZWwtY29udGVudCI+CiAgICAgICAgICAgICAgICA8
-cCBjbGFzcz0icGxhY2Vob2xkZXIiPlNlZSBkZXRhaWxzIGFib3V0IGEgcHJvcG9zZWQgZWRpdC48L3A+
-CiAgICAgICAgICAgIDwvZGl2PjwhLS0gL3BhbmVsLWNvbnRlbnQgLS0+CiAgICAgICAgPC9kaXY+PCEt
-LSAvZWRpdC1wYW5lbCAtLT4KICAgIDwvZGl2PjwhLS0gL2luZm8tcGFuZWwgLS0+CjwvZGl2PjwhLS0g
-L3BhbmVscyAtLT4KPGZvb3Rlcj4KICA8YnV0dG9uIGNsYXNzPSJyZXBvcnQtcHJvYmxlbSI+U2VuZCBG
-ZWVkYmFjazwvYnV0dG9uPgogICAgPHNwYW4gY2xhc3M9IndpZGUiPiA8L3NwYW4+CiAgICA8ZGl2IGNs
-YXNzPSJzZGstdmVyc2lvbiI+QmFzZWQgb24gPHNwYW4gaWQ9InNkay12ZXJzaW9uIj57eyBzZGtWZXJz
-aW9uIH19PC9zcGFuPjwvZGl2Pgo8L2Zvb3Rlcj4KPC9ib2R5Pgo8L2h0bWw+Cg==
+ZDwvaDI+CiAgICA8YSB0YXJnZXQ9Il9ibGFuayIKICAgICAgIGhyZWY9Imh0dHBzOi8vZGFydC5kZXYv
+Z28vbnVsbC1zYWZldHktbWlncmF0aW9uIj4KICAgICAgPGJ1dHRvbiBjbGFzcz0iYWN0aW9uLWJ1dHRv
+biI+CiAgICAgICAgPGkgY2xhc3M9Im1hdGVyaWFsLWljb25zIj5sYXVuY2g8L2k+CiAgICAgICAgPHNw
+YW4+SGVscDwvc3Bhbj4KICAgICAgPC9idXR0b24+CiAgICA8L2E+CiAgICA8YnV0dG9uIGNsYXNzPSJh
+Y3Rpb24tYnV0dG9uIGFwcGx5LW1pZ3JhdGlvbiI+CiAgICAgICAgPGkgY2xhc3M9Im1hdGVyaWFsLWlj
+b25zIj5lZGl0PC9pPgogICAgICAgIDxzcGFuIGNsYXNzPSJsYWJlbCI+CiAgICAgICAgICBBcHBseSBN
+aWdyYXRpb24KICAgICAgICA8L3NwYW4+CiAgICA8L2J1dHRvbj4KICAgIDxidXR0b24gY2xhc3M9ImFj
+dGlvbi1idXR0b24gYXBwbHktbWlncmF0aW9uIiBkaXNhYmxlZD4KICAgICAgICA8aSBjbGFzcz0ibWF0
+ZXJpYWwtaWNvbnMiPmVkaXQ8L2k+CiAgICAgICAgPHNwYW4gY2xhc3M9ImxhYmVsIj4KICAgICAgICAg
+IEFwcGx5IE1pZ3JhdGlvbgogICAgICAgIDwvc3Bhbj4KICAgIDwvYnV0dG9uPgogICAgPGJ1dHRvbiBj
+bGFzcz0iYWN0aW9uLWJ1dHRvbiByZXJ1bi1taWdyYXRpb24gYmVmb3JlLWFwcGx5Ij4KICAgICAgPHNw
+YW4gY2xhc3M9Im9wdGlvbmFsIj4KICAgICAgICA8aSBjbGFzcz0ibWF0ZXJpYWwtaWNvbnMiPnJlcGxh
+eTwvaT4KICAgICAgICBSZXJ1biBGcm9tIFNvdXJjZXMKICAgICAgPC9zcGFuPgogICAgICA8c3BhbiBj
+bGFzcz0icmVxdWlyZWQiPgogICAgICAgIDxpIGNsYXNzPSJtYXRlcmlhbC1pY29ucyI+d2FybmluZzwv
+aT4KICAgICAgICBSZXJ1biBXaXRoIENoYW5nZXMKICAgICAgPC9zcGFuPgogICAgPC9idXR0b24+Cjwv
+aGVhZGVyPgo8ZGl2IGNsYXNzPSJwYW5lbHMgaG9yaXpvbnRhbCI+CiAgICA8ZGl2IGNsYXNzPSJuYXYt
+cGFuZWwiPgogICAgICAgIDxkaXYgY2xhc3M9Im5hdi1pbm5lciI+CiAgICAgICAgICAgIDxkaXYgY2xh
+c3M9InBhbmVsLWhlYWRpbmciPlByb2plY3QgRmlsZXM8L2Rpdj4KICAgICAgICAgICAgPGRpdiBjbGFz
+cz0ibmF2LXRyZWUiPjwvZGl2PgogICAgICAgIDwvZGl2PjwhLS0gL25hdi1pbm5lciAtLT4KICAgIDwv
+ZGl2PjwhLS0gL25hdiAtLT4KICAgIDxkaXYgY2xhc3M9ImZpbGUiPgogICAgICAgIDxkaXYgY2xhc3M9
+InRpdGxlLWJhciI+CiAgICAgICAgICAgIDxoMyBpZD0idW5pdC1uYW1lIj4mbmJzcDs8L2gzPgogICAg
+ICAgICAgICA8c3BhbiBpZD0ibWlncmF0ZS11bml0LXN0YXR1cy1pY29uLWxhYmVsIj5NaWdyYXRlCiAg
+ICAgICAgICAgICAgICA8c3BhbgogICAgICAgICAgICAgICAgICAgIGNsYXNzPSJtYXRlcmlhbC1pY29u
+cyBzdGF0dXMtaWNvbiBtaWdyYXRpbmciCiAgICAgICAgICAgICAgICAgICAgaWQ9Im1pZ3JhdGUtdW5p
+dC1zdGF0dXMtaWNvbiI+Y2hlY2tfYm94PC9zcGFuPgogICAgICAgICAgICA8L3NwYW4+CiAgICAgICAg
+PC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0iY29udGVudCI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9
+InJlZ2lvbnMiPgogICAgICAgICAgICAgICAgPCEtLSBUaGUgcmVnaW9ucyBvdmVybGF5IGNvZGUgY29w
+eSBvZiB0aGUgY29udGVudCB0byBwcm92aWRlIC0tPgogICAgICAgICAgICAgICAgPCEtLSB0b29sdGlw
+cyBmb3IgbW9kaWZpZWQgcmVnaW9ucy4gLS0+CiAgICAgICAgICAgIDwvZGl2PjwhLS0gL3JlZ2lvbnMg
+LS0+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvZGUiPgogICAgICAgICAgICAgICAgPCEtLSBDb21w
+aWxhdGlvbiB1bml0IGNvbnRlbnQgaXMgd3JpdHRlbiBoZXJlLiAtLT4KICAgICAgICAgICAgICAgIDxw
+IGNsYXNzPSJ3ZWxjb21lIj4KICAgICAgICAgICAgICAgICAgICBTZWxlY3QgYSBzb3VyY2UgZmlsZSBv
+biB0aGUgbGVmdCB0byBwcmV2aWV3IHRoZSBwcm9wb3NlZCBlZGl0cy4KICAgICAgICAgICAgICAgIDwv
+cD4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICA8L2Rpdj48IS0tIC9jb250ZW50
+IC0tPgogICAgPGRpdiBjbGFzcz0iaW5mby1wYW5lbCI+CiAgICAgICAgPGRpdiBjbGFzcz0iZWRpdC1s
+aXN0Ij4KICAgICAgICAgICAgPGRpdiBjbGFzcz0icGFuZWwtaGVhZGluZyI+UHJvcG9zZWQgRWRpdHM8
+L2Rpdj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0icGFuZWwtY29udGVudCI+PC9kaXY+CiAgICAgICAg
+PC9kaXY+PCEtLSAvZWRpdC1saXN0IC0tPgogICAgICAgIDxkaXYgY2xhc3M9ImVkaXQtcGFuZWwiPgog
+ICAgICAgICAgICA8ZGl2IGNsYXNzPSJwYW5lbC1oZWFkaW5nIj5FZGl0IERldGFpbHM8L2Rpdj4KICAg
+ICAgICAgICAgPGRpdiBjbGFzcz0icGFuZWwtY29udGVudCI+CiAgICAgICAgICAgICAgICA8cCBjbGFz
+cz0icGxhY2Vob2xkZXIiPlNlZSBkZXRhaWxzIGFib3V0IGEgcHJvcG9zZWQgZWRpdC48L3A+CiAgICAg
+ICAgICAgIDwvZGl2PjwhLS0gL3BhbmVsLWNvbnRlbnQgLS0+CiAgICAgICAgPC9kaXY+PCEtLSAvZWRp
+dC1wYW5lbCAtLT4KICAgIDwvZGl2PjwhLS0gL2luZm8tcGFuZWwgLS0+CjwvZGl2PjwhLS0gL3BhbmVs
+cyAtLT4KPGZvb3Rlcj4KICA8YnV0dG9uIGNsYXNzPSJyZXBvcnQtcHJvYmxlbSI+U2VuZCBGZWVkYmFj
+azwvYnV0dG9uPgogICAgPHNwYW4gY2xhc3M9IndpZGUiPiA8L3NwYW4+CiAgICA8ZGl2IGNsYXNzPSJz
+ZGstdmVyc2lvbiI+QmFzZWQgb24gPHNwYW4gaWQ9InNkay12ZXJzaW9uIj57eyBzZGtWZXJzaW9uIH19
+PC9zcGFuPjwvZGl2Pgo8L2Zvb3Rlcj4KPC9ib2R5Pgo8L2h0bWw+Cg==
 ''';
 
 String _migration_css;
@@ -7724,7 +7724,7 @@
 dEZ1bmN0aW9uTmFtZXNJZk5lY2Vzc2FyeShhKXtmdW5jdGlvbiB0KCl7fTtpZih0eXBlb2YgdC5uYW1l
 PT0ic3RyaW5nIilyZXR1cm4KZm9yKHZhciBzPTA7czxhLmxlbmd0aDtzKyspe3ZhciByPWFbc10KdmFy
 IHE9T2JqZWN0LmtleXMocikKZm9yKHZhciBwPTA7cDxxLmxlbmd0aDtwKyspe3ZhciBvPXFbcF0KdmFy
-IG49cltvXQppZih0eXBlb2Ygbj09J2Z1bmN0aW9uJyluLm5hbWU9b319fWZ1bmN0aW9uIGluaGVyaXQo
+IG49cltvXQppZih0eXBlb2Ygbj09ImZ1bmN0aW9uIiluLm5hbWU9b319fWZ1bmN0aW9uIGluaGVyaXQo
 YSxiKXthLnByb3RvdHlwZS5jb25zdHJ1Y3Rvcj1hCmEucHJvdG90eXBlWyIkaSIrYS5uYW1lXT1hCmlm
 KGIhPW51bGwpe2lmKHope2EucHJvdG90eXBlLl9fcHJvdG9fXz1iLnByb3RvdHlwZQpyZXR1cm59dmFy
 IHM9T2JqZWN0LmNyZWF0ZShiLnByb3RvdHlwZSkKY29weVByb3BlcnRpZXMoYS5wcm90b3R5cGUscykK
@@ -7758,7 +7758,7 @@
 S3EodGhpcyxhLGIsYyx0cnVlLGZhbHNlLGUpLnByb3RvdHlwZQpyZXR1cm4gc306dGVhck9mZkdldHRl
 cihhLGIsYyxlLGYpfXZhciB4PTAKZnVuY3Rpb24gaW5zdGFsbFRlYXJPZmYoYSxiLGMsZCxlLGYsZyxo
 LGksail7dmFyIHM9W10KZm9yKHZhciByPTA7cjxoLmxlbmd0aDtyKyspe3ZhciBxPWhbcl0KaWYodHlw
-ZW9mIHE9PSdzdHJpbmcnKXE9YVtxXQpxLiRjYWxsTmFtZT1nW3JdCnMucHVzaChxKX12YXIgcT1zWzBd
+ZW9mIHE9PSJzdHJpbmciKXE9YVtxXQpxLiRjYWxsTmFtZT1nW3JdCnMucHVzaChxKX12YXIgcT1zWzBd
 CnEuJFI9ZQpxLiREPWYKdmFyIHA9aQppZih0eXBlb2YgcD09Im51bWJlciIpcCs9eAp2YXIgbz1oWzBd
 CnEuJHN0dWJOYW1lPW8KdmFyIG49dGVhck9mZihzLGp8fDAscCxjLG8sZCkKYVtiXT1uCmlmKGMpcS4k
 dGVhck9mZj1ufWZ1bmN0aW9uIGluc3RhbGxTdGF0aWNUZWFyT2ZmKGEsYixjLGQsZSxmLGcsaCl7cmV0
@@ -7916,18 +7916,18 @@
 cm9wZXJ0eShzLCJtZXNzYWdlIix7Z2V0OnJ9KQpzLm5hbWU9IiJ9ZWxzZSBzLnRvU3RyaW5nPXIKcmV0
 dXJuIHN9LAp4OmZ1bmN0aW9uKCl7cmV0dXJuIEouaih0aGlzLmRhcnRFeGNlcHRpb24pfSwKdjpmdW5j
 dGlvbihhKXt0aHJvdyBILmIoYSl9LApsazpmdW5jdGlvbihhKXt0aHJvdyBILmIoUC5hNChhKSl9LApj
-TTpmdW5jdGlvbihhKXt2YXIgcyxyLHEscCxvLG4KYT1ILmVBKGEucmVwbGFjZShTdHJpbmcoe30pLCck
-cmVjZWl2ZXIkJykpCnM9YS5tYXRjaCgvXFxcJFthLXpBLVpdK1xcXCQvZykKaWYocz09bnVsbClzPUgu
+TTpmdW5jdGlvbihhKXt2YXIgcyxyLHEscCxvLG4KYT1ILmVBKGEucmVwbGFjZShTdHJpbmcoe30pLCIk
+cmVjZWl2ZXIkIikpCnM9YS5tYXRjaCgvXFxcJFthLXpBLVpdK1xcXCQvZykKaWYocz09bnVsbClzPUgu
 Vk0oW10sdC5zKQpyPXMuaW5kZXhPZigiXFwkYXJndW1lbnRzXFwkIikKcT1zLmluZGV4T2YoIlxcJGFy
 Z3VtZW50c0V4cHJcXCQiKQpwPXMuaW5kZXhPZigiXFwkZXhwclxcJCIpCm89cy5pbmRleE9mKCJcXCRt
 ZXRob2RcXCQiKQpuPXMuaW5kZXhPZigiXFwkcmVjZWl2ZXJcXCQiKQpyZXR1cm4gbmV3IEguZjkoYS5y
-ZXBsYWNlKG5ldyBSZWdFeHAoJ1xcXFxcXCRhcmd1bWVudHNcXFxcXFwkJywnZycpLCcoKD86eHxbXnhd
-KSopJykucmVwbGFjZShuZXcgUmVnRXhwKCdcXFxcXFwkYXJndW1lbnRzRXhwclxcXFxcXCQnLCdnJyks
-JygoPzp4fFteeF0pKiknKS5yZXBsYWNlKG5ldyBSZWdFeHAoJ1xcXFxcXCRleHByXFxcXFxcJCcsJ2cn
-KSwnKCg/Onh8W154XSkqKScpLnJlcGxhY2UobmV3IFJlZ0V4cCgnXFxcXFxcJG1ldGhvZFxcXFxcXCQn
-LCdnJyksJygoPzp4fFteeF0pKiknKS5yZXBsYWNlKG5ldyBSZWdFeHAoJ1xcXFxcXCRyZWNlaXZlclxc
-XFxcXCQnLCdnJyksJygoPzp4fFteeF0pKiknKSxyLHEscCxvLG4pfSwKUzc6ZnVuY3Rpb24oYSl7cmV0
-dXJuIGZ1bmN0aW9uKCRleHByJCl7dmFyICRhcmd1bWVudHNFeHByJD0nJGFyZ3VtZW50cyQnCnRyeXsk
+ZXBsYWNlKG5ldyBSZWdFeHAoIlxcXFxcXCRhcmd1bWVudHNcXFxcXFwkIiwiZyIpLCIoKD86eHxbXnhd
+KSopIikucmVwbGFjZShuZXcgUmVnRXhwKCJcXFxcXFwkYXJndW1lbnRzRXhwclxcXFxcXCQiLCJnIiks
+IigoPzp4fFteeF0pKikiKS5yZXBsYWNlKG5ldyBSZWdFeHAoIlxcXFxcXCRleHByXFxcXFxcJCIsImci
+KSwiKCg/Onh8W154XSkqKSIpLnJlcGxhY2UobmV3IFJlZ0V4cCgiXFxcXFxcJG1ldGhvZFxcXFxcXCQi
+LCJnIiksIigoPzp4fFteeF0pKikiKS5yZXBsYWNlKG5ldyBSZWdFeHAoIlxcXFxcXCRyZWNlaXZlclxc
+XFxcXCQiLCJnIiksIigoPzp4fFteeF0pKikiKSxyLHEscCxvLG4pfSwKUzc6ZnVuY3Rpb24oYSl7cmV0
+dXJuIGZ1bmN0aW9uKCRleHByJCl7dmFyICRhcmd1bWVudHNFeHByJD0iJGFyZ3VtZW50cyQiCnRyeXsk
 ZXhwciQuJG1ldGhvZCQoJGFyZ3VtZW50c0V4cHIkKX1jYXRjaChzKXtyZXR1cm4gcy5tZXNzYWdlfX0o
 YSl9LApNajpmdW5jdGlvbihhKXtyZXR1cm4gZnVuY3Rpb24oJGV4cHIkKXt0cnl7JGV4cHIkLiRtZXRo
 b2QkfWNhdGNoKHMpe3JldHVybiBzLm1lc3NhZ2V9fShhKX0sCklqOmZ1bmN0aW9uKGEsYil7cmV0dXJu
@@ -11519,10 +11519,10 @@
 dXJuIEguY00oSC5TNyh7JG1ldGhvZCQ6bnVsbCwKdG9TdHJpbmc6ZnVuY3Rpb24oKXtyZXR1cm4iJHJl
 Y2VpdmVyJCJ9fSkpfSkKcygkLCJSMSIsIk45IixmdW5jdGlvbigpe3JldHVybiBILmNNKEguUzcobnVs
 bCkpfSkKcygkLCJmTiIsImlJIixmdW5jdGlvbigpe3JldHVybiBILmNNKGZ1bmN0aW9uKCl7dmFyICRh
-cmd1bWVudHNFeHByJD0nJGFyZ3VtZW50cyQnCnRyeXtudWxsLiRtZXRob2QkKCRhcmd1bWVudHNFeHBy
+cmd1bWVudHNFeHByJD0iJGFyZ3VtZW50cyQiCnRyeXtudWxsLiRtZXRob2QkKCRhcmd1bWVudHNFeHBy
 JCl9Y2F0Y2gocSl7cmV0dXJuIHEubWVzc2FnZX19KCkpfSkKcygkLCJxaSIsIlVOIixmdW5jdGlvbigp
 e3JldHVybiBILmNNKEguUzcodm9pZCAwKSl9KQpzKCQsInJaIiwiWmgiLGZ1bmN0aW9uKCl7cmV0dXJu
-IEguY00oZnVuY3Rpb24oKXt2YXIgJGFyZ3VtZW50c0V4cHIkPSckYXJndW1lbnRzJCcKdHJ5eyh2b2lk
+IEguY00oZnVuY3Rpb24oKXt2YXIgJGFyZ3VtZW50c0V4cHIkPSIkYXJndW1lbnRzJCIKdHJ5eyh2b2lk
 IDApLiRtZXRob2QkKCRhcmd1bWVudHNFeHByJCl9Y2F0Y2gocSl7cmV0dXJuIHEubWVzc2FnZX19KCkp
 fSkKcygkLCJrcSIsInJOIixmdW5jdGlvbigpe3JldHVybiBILmNNKEguTWoobnVsbCkpfSkKcygkLCJ0
 dCIsImMzIixmdW5jdGlvbigpe3JldHVybiBILmNNKGZ1bmN0aW9uKCl7dHJ5e251bGwuJG1ldGhvZCR9
@@ -11812,7 +11812,7 @@
 dXBlcmNsYXNzVGFnPSJBcnJheUJ1ZmZlclZpZXciCkguUGcuJG5hdGl2ZVN1cGVyY2xhc3NUYWc9IkFy
 cmF5QnVmZmVyVmlldyJ9KSgpCmNvbnZlcnRBbGxUb0Zhc3RPYmplY3QodykKY29udmVydFRvRmFzdE9i
 amVjdCgkKTsoZnVuY3Rpb24oYSl7aWYodHlwZW9mIGRvY3VtZW50PT09InVuZGVmaW5lZCIpe2EobnVs
-bCkKcmV0dXJufWlmKHR5cGVvZiBkb2N1bWVudC5jdXJyZW50U2NyaXB0IT0ndW5kZWZpbmVkJyl7YShk
+bCkKcmV0dXJufWlmKHR5cGVvZiBkb2N1bWVudC5jdXJyZW50U2NyaXB0IT0idW5kZWZpbmVkIil7YShk
 b2N1bWVudC5jdXJyZW50U2NyaXB0KQpyZXR1cm59dmFyIHM9ZG9jdW1lbnQuc2NyaXB0cwpmdW5jdGlv
 biBvbkxvYWQoYil7Zm9yKHZhciBxPTA7cTxzLmxlbmd0aDsrK3Epc1txXS5yZW1vdmVFdmVudExpc3Rl
 bmVyKCJsb2FkIixvbkxvYWQsZmFsc2UpCmEoYi50YXJnZXQpfWZvcih2YXIgcj0wO3I8cy5sZW5ndGg7
diff --git a/pkg/nnbd_migration/test/migration_cli_test.dart b/pkg/nnbd_migration/test/migration_cli_test.dart
index f07ef19..6121240 100644
--- a/pkg/nnbd_migration/test/migration_cli_test.dart
+++ b/pkg/nnbd_migration/test/migration_cli_test.dart
@@ -297,7 +297,7 @@
   }
 
   Future assertPreviewServerResponsive(String url) async {
-    var response = await httpGet(url);
+    var response = await httpGet(Uri.parse(url));
     assertHttpSuccess(response);
   }
 
@@ -372,13 +372,13 @@
 
   /// Performs an HTTP get, verifying that the response received (if any) is
   /// reasonable.
-  Future<http.Response> httpGet(dynamic url, {Map<String, String> headers}) {
+  Future<http.Response> httpGet(Uri url, {Map<String, String> headers}) {
     return checkHttpResponse(http.get(url, headers: headers));
   }
 
   /// Performs an HTTP post, verifying that the response received (if any) is
   /// reasonable.
-  Future<http.Response> httpPost(dynamic url,
+  Future<http.Response> httpPost(Uri url,
       {Map<String, String> headers, dynamic body, Encoding encoding}) {
     return checkHttpResponse(
         http.post(url, headers: headers, body: body, encoding: encoding));
@@ -399,7 +399,7 @@
         await callback(url);
       });
       // Server should be stopped now
-      expect(httpGet(url), throwsA(anything));
+      expect(httpGet(Uri.parse(url)), throwsA(anything));
       assertNormalExit(cliRunner);
     }
   }
@@ -1863,6 +1863,48 @@
         projectDir, simpleProject(migrated: true, pubspecText: pubspecText));
   }
 
+  test_pubspec_environment_sdk_is_exact_version() async {
+    var pubspecText = '''
+name: test
+environment:
+  sdk: '2.0.0'
+''';
+    var projectContents = simpleProject(pubspecText: pubspecText);
+    var projectDir = createProjectDir(projectContents);
+    var cliRunner = _createCli()
+        .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir]));
+    await cliRunner.run();
+    // The Dart source code should still be migrated.
+    assertProjectContents(
+        projectDir,
+        simpleProject(
+            migrated: true,
+            pubspecText: pubspecText,
+            // The package config file should not have been touched.
+            packageConfigText: _getPackageConfigText(migrated: false)));
+  }
+
+  test_pubspec_environment_sdk_is_missing_min() async {
+    var pubspecText = '''
+name: test
+environment:
+  sdk: '<3.0.0'
+''';
+    var projectContents = simpleProject(pubspecText: pubspecText);
+    var projectDir = createProjectDir(projectContents);
+    var cliRunner = _createCli()
+        .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir]));
+    await cliRunner.run();
+    // The Dart source code should still be migrated.
+    assertProjectContents(
+        projectDir,
+        simpleProject(
+            migrated: true,
+            pubspecText: pubspecText,
+            // The package config file should not have been touched.
+            packageConfigText: _getPackageConfigText(migrated: false)));
+  }
+
   test_pubspec_environment_sdk_is_not_string() async {
     var pubspecText = '''
 name: test
@@ -1884,6 +1926,27 @@
             packageConfigText: _getPackageConfigText(migrated: false)));
   }
 
+  test_pubspec_environment_sdk_is_union() async {
+    var pubspecText = '''
+name: test
+environment:
+  sdk: '>=2.0.0 <2.1.0 >=2.2.0 <3.0.0'
+''';
+    var projectContents = simpleProject(pubspecText: pubspecText);
+    var projectDir = createProjectDir(projectContents);
+    var cliRunner = _createCli()
+        .decodeCommandLineArgs(_parseArgs(['--apply-changes', projectDir]));
+    await cliRunner.run();
+    // The Dart source code should still be migrated.
+    assertProjectContents(
+        projectDir,
+        simpleProject(
+            migrated: true,
+            pubspecText: pubspecText,
+            // The package config file should not have been touched.
+            packageConfigText: _getPackageConfigText(migrated: false)));
+  }
+
   test_pubspec_has_unusual_max_sdk_constraint() async {
     // No one should be using a weird max SDK constraint like this.  If they are
     // doing so, we'll fix it to 3.0.0.
diff --git a/samples/ffi/resource_management/pool.dart b/samples/ffi/resource_management/pool.dart
index 3bc0e2a..14e07f4 100644
--- a/samples/ffi/resource_management/pool.dart
+++ b/samples/ffi/resource_management/pool.dart
@@ -11,30 +11,47 @@
 
 import '../calloc.dart';
 
-/// Keeps track of all allocated memory and frees all allocated memory on
-/// [releaseAll].
+/// An [Allocator] which frees all allocations at the same time.
 ///
-/// Wraps an [Allocator] to do the actual allocation and freeing.
+/// The pool allows you to allocate heap memory, but ignores calls to [free].
+/// Instead you call [releaseAll] to release all the allocations at the same
+/// time.
+///
+/// Also allows other resources to be associated with the pool, through the
+/// [using] method, to have a release function called for them when the pool is
+/// released.
+///
+/// An [Allocator] can be provided to do the actual allocation and freeing.
+/// Defaults to using [calloc].
 class Pool implements Allocator {
   /// The [Allocator] used for allocation and freeing.
   final Allocator _wrappedAllocator;
 
-  Pool(this._wrappedAllocator);
-
   /// Native memory under management by this [Pool].
   final List<Pointer<NativeType>> _managedMemoryPointers = [];
 
   /// Callbacks for releasing native resources under management by this [Pool].
   final List<Function()> _managedResourceReleaseCallbacks = [];
 
-  /// Allocates memory on the native heap by using the allocator supplied to
-  /// the constructor.
+  bool _inUse = true;
+
+  /// Creates a pool of allocations.
+  ///
+  /// The [allocator] is used to do the actual allocation and freeing of
+  /// memory. It defaults to using [calloc].
+  Pool([Allocator allocator = calloc]) : _wrappedAllocator = allocator;
+
+  /// Allocates memory and includes it in the pool.
+  ///
+  /// Uses the allocator provided to the [Pool] constructor to do the
+  /// allocation.
   ///
   /// Throws an [ArgumentError] if the number of bytes or alignment cannot be
   /// satisfied.
   @override
-  Pointer<T> allocate<T extends NativeType>(int numBytes, {int? alignment}) {
-    final p = _wrappedAllocator.allocate<T>(numBytes, alignment: alignment);
+  Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
+    _ensureInUse();
+    final p = _wrappedAllocator.allocate<T>(byteCount, alignment: alignment);
     _managedMemoryPointers.add(p);
     return p;
   }
@@ -43,6 +60,8 @@
   ///
   /// Executes [releaseCallback] on [releaseAll].
   T using<T>(T resource, Function(T) releaseCallback) {
+    _ensureInUse();
+    releaseCallback = Zone.current.bindUnaryCallback(releaseCallback);
     _managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
     return resource;
   }
@@ -53,59 +72,102 @@
   }
 
   /// Releases all resources that this [Pool] manages.
-  void releaseAll() {
-    for (final c in _managedResourceReleaseCallbacks) {
-      c();
+  ///
+  /// If [reuse] is `true`, the pool can be used again after resources
+  /// have been released. If not, the default, then the [allocate]
+  /// and [using] methods must not be called after a call to `releaseAll`.
+  void releaseAll({bool reuse = false}) {
+    if (!reuse) {
+      _inUse = false;
     }
-    _managedResourceReleaseCallbacks.clear();
+    while (_managedResourceReleaseCallbacks.isNotEmpty) {
+      _managedResourceReleaseCallbacks.removeLast()();
+    }
     for (final p in _managedMemoryPointers) {
       _wrappedAllocator.free(p);
     }
     _managedMemoryPointers.clear();
   }
 
+  /// Does nothing, invoke [releaseAll] instead.
   @override
-  void free(Pointer<NativeType> pointer) => throw UnsupportedError(
-      "Individually freeing Pool allocated memory is not allowed");
+  void free(Pointer<NativeType> pointer) {}
+
+  void _ensureInUse() {
+    if (!_inUse) {
+      throw StateError(
+          "Pool no longer in use, `releaseAll(reuse: false)` was called.");
+    }
+  }
 }
 
-/// Creates a [Pool] to manage native resources.
+/// Runs [computation] with a new [Pool], and releases all allocations at the end.
 ///
-/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
-R using<R>(R Function(Pool) f, [Allocator wrappedAllocator = calloc]) {
-  final p = Pool(wrappedAllocator);
+/// If [R] is a [Future], all allocations are released when the future completes.
+///
+/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_
+/// cleaned up.
+R using<R>(R Function(Pool) computation,
+    [Allocator wrappedAllocator = calloc]) {
+  final pool = Pool(wrappedAllocator);
+  bool isAsync = false;
   try {
-    return f(p);
+    final result = computation(pool);
+    if (result is Future) {
+      isAsync = true;
+      return (result.whenComplete(pool.releaseAll) as R);
+    }
+    return result;
   } finally {
-    p.releaseAll();
+    if (!isAsync) {
+      pool.releaseAll();
+    }
   }
 }
 
 /// Creates a zoned [Pool] to manage native resources.
 ///
-/// Pool is availabe through [currentPool].
-///
-/// Please note that all throws are caught and packaged in [RethrownError].
+/// The pool is availabe through [zonePool].
 ///
 /// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
-R usePool<R>(R Function() f, [Allocator wrappedAllocator = calloc]) {
-  final p = Pool(wrappedAllocator);
+R withZonePool<R>(R Function() computation,
+    [Allocator wrappedAllocator = calloc]) {
+  final pool = Pool(wrappedAllocator);
+  var poolHolder = [pool];
+  bool isAsync = false;
   try {
-    return runZoned(() => f(),
-        zoneValues: {#_pool: p},
-        onError: (error, st) => throw RethrownError(error, st));
+    return runZoned(() {
+      final result = computation();
+      if (result is Future) {
+        isAsync = true;
+        result.whenComplete(pool.releaseAll);
+      }
+      return result;
+    }, zoneValues: {#_pool: poolHolder});
   } finally {
-    p.releaseAll();
+    if (!isAsync) {
+      pool.releaseAll();
+      poolHolder.remove(pool);
+    }
   }
 }
 
-/// The [Pool] in the current zone.
-Pool get currentPool => Zone.current[#_pool];
-
-class RethrownError {
-  dynamic original;
-  StackTrace originalStackTrace;
-  RethrownError(this.original, this.originalStackTrace);
-  toString() => """RethrownError(${original})
-${originalStackTrace}""";
+/// A zone-specific [Pool].
+///
+/// Asynchronous computations can share a [Pool]. Use [withZonePool] to create
+/// a new zone with a fresh [Pool], and that pool will then be released
+/// automatically when the function passed to [withZonePool] completes.
+/// All code inside that zone can use `zonePool` to access the pool.
+///
+/// The current pool must not be accessed by code which is not running inside
+/// a zone created by [withZonePool].
+Pool get zonePool {
+  final List<Pool>? poolHolder = Zone.current[#_pool];
+  if (poolHolder == null) {
+    throw StateError("Not inside a zone created by `usePool`");
+  }
+  if (!poolHolder.isEmpty) {
+    return poolHolder.single;
+  }
+  throw StateError("Pool as already been cleared with releaseAll.");
 }
diff --git a/samples/ffi/resource_management/pool_sample.dart b/samples/ffi/resource_management/pool_sample.dart
index ef8b415..1f75165 100644
--- a/samples/ffi/resource_management/pool_sample.dart
+++ b/samples/ffi/resource_management/pool_sample.dart
@@ -4,6 +4,7 @@
 //
 // Sample illustrating resource management with an explicit pool.
 
+import 'dart:async';
 import 'dart:ffi';
 
 import 'package:expect/expect.dart';
@@ -12,7 +13,7 @@
 import 'utf8_helpers.dart';
 import '../dylib_utils.dart';
 
-main() {
+main() async {
   final ffiTestDynamicLibrary =
       dlopenPlatformSpecific("ffi_test_dynamic_library");
 
@@ -108,6 +109,24 @@
   } on Exception catch (e) {
     print("Caught exception: $e");
   }
+
+  /// [using] waits with releasing its resources until after [Future]s
+  /// complete.
+  List<int> freed = [];
+  freeInt(int i) {
+    freed.add(i);
+  }
+
+  Future<int> myFutureInt = using((Pool pool) {
+    return Future.microtask(() {
+      pool.using(1, freeInt);
+      return 1;
+    });
+  });
+
+  Expect.isTrue(freed.isEmpty);
+  await myFutureInt;
+  Expect.equals(1, freed.single);
 }
 
 /// Represents some opaque resource being managed by a library.
diff --git a/samples/ffi/resource_management/pool_zoned_sample.dart b/samples/ffi/resource_management/pool_zoned_sample.dart
index 4c99fdc..b5b9eed 100644
--- a/samples/ffi/resource_management/pool_zoned_sample.dart
+++ b/samples/ffi/resource_management/pool_zoned_sample.dart
@@ -12,7 +12,7 @@
 import 'utf8_helpers.dart';
 import '../dylib_utils.dart';
 
-main() {
+main() async {
   final ffiTestDynamicLibrary =
       dlopenPlatformSpecific("ffi_test_dynamic_library");
 
@@ -20,9 +20,9 @@
       Void Function(Pointer<Void>, Pointer<Void>, IntPtr),
       void Function(Pointer<Void>, Pointer<Void>, int)>("MemMove");
 
-  // To ensure resources are freed, wrap them in a [using] call.
-  usePool(() {
-    final p = currentPool<Int64>(2);
+  // To ensure resources are freed, wrap them in a [withZonePool] call.
+  withZonePool(() {
+    final p = zonePool<Int64>(2);
     p[0] = 24;
     MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
     print(p[1]);
@@ -31,24 +31,23 @@
 
   // Resources are freed also when abnormal control flow occurs.
   try {
-    usePool(() {
-      final p = currentPool<Int64>(2);
+    withZonePool(() {
+      final p = zonePool<Int64>(2);
       p[0] = 25;
       MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
       print(p[1]);
       Expect.equals(25, p[1]);
       throw Exception("Some random exception");
     });
-  } on RethrownError catch (e) {
-    // Note that exceptions are wrapped when using zones.
-    print("Caught exception: ${e.original}");
+  } catch (e) {
+    print("Caught exception: ${e}");
   }
 
   // In a pool multiple resources can be allocated, which will all be freed
   // at the end of the scope.
-  usePool(() {
-    final p = currentPool<Int64>(2);
-    final p2 = currentPool<Int64>(2);
+  withZonePool(() {
+    final p = zonePool<Int64>(2);
+    final p2 = zonePool<Int64>(2);
     p[0] = 1;
     p[1] = 2;
     MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
@@ -59,10 +58,10 @@
   // If the resource allocation happens in a different scope, it is in the
   // same zone, so it's lifetime is automatically managed by the pool.
   f1() {
-    return currentPool<Int64>(2);
+    return zonePool<Int64>(2);
   }
 
-  usePool(() {
+  withZonePool(() {
     final p = f1();
     final p2 = f1();
     p[0] = 1;
@@ -73,8 +72,8 @@
   });
 
   // Using Strings.
-  usePool(() {
-    final p = "Hello world!".toUtf8(currentPool);
+  withZonePool(() {
+    final p = "Hello world!".toUtf8(zonePool);
     print(p.contents());
   });
 
@@ -91,24 +90,42 @@
       void Function(Pointer<SomeResource>)>("ReleaseResource");
 
   // Using an FFI call to release a resource.
-  usePool(() {
-    final r = currentPool.using(allocateResource(), releaseResource);
+  withZonePool(() {
+    final r = zonePool.using(allocateResource(), releaseResource);
     useResource(r);
   });
 
   // Using an FFI call to release a resource with abnormal control flow.
   try {
-    usePool(() {
-      final r = currentPool.using(allocateResource(), releaseResource);
+    withZonePool(() {
+      final r = zonePool.using(allocateResource(), releaseResource);
       useResource(r);
 
       throw Exception("Some random exception");
     });
     // Resource has been freed.
-  } on RethrownError catch (e) {
-    // Note that exceptions are wrapped when using zones.
-    print("Caught exception: ${e.original}");
+  } catch (e) {
+    print("Caught exception: ${e}");
   }
+
+  /// [using] waits with releasing its resources until after [Future]s
+  /// complete.
+
+  List<int> freed = [];
+  freeInt(int i) {
+    freed.add(i);
+  }
+
+  Future<int> myFutureInt = withZonePool(() {
+    return Future.microtask(() {
+      zonePool.using(1, freeInt);
+      return 1;
+    });
+  });
+
+  Expect.isTrue(freed.isEmpty);
+  await myFutureInt;
+  Expect.equals(1, freed.single);
 }
 
 /// Represents some opaque resource being managed by a library.
diff --git a/samples_2/ffi/resource_management/pool.dart b/samples_2/ffi/resource_management/pool.dart
index 477a78e..d4610b0 100644
--- a/samples_2/ffi/resource_management/pool.dart
+++ b/samples_2/ffi/resource_management/pool.dart
@@ -13,30 +13,47 @@
 
 import '../calloc.dart';
 
-/// Keeps track of all allocated memory and frees all allocated memory on
-/// [releaseAll].
+/// An [Allocator] which frees all allocations at the same time.
 ///
-/// Wraps an [Allocator] to do the actual allocation and freeing.
+/// The pool allows you to allocate heap memory, but ignores calls to [free].
+/// Instead you call [releaseAll] to release all the allocations at the same
+/// time.
+///
+/// Also allows other resources to be associated with the pool, through the
+/// [using] method, to have a release function called for them when the pool is
+/// released.
+///
+/// An [Allocator] can be provided to do the actual allocation and freeing.
+/// Defaults to using [calloc].
 class Pool implements Allocator {
   /// The [Allocator] used for allocation and freeing.
   final Allocator _wrappedAllocator;
 
-  Pool(this._wrappedAllocator);
-
   /// Native memory under management by this [Pool].
   final List<Pointer<NativeType>> _managedMemoryPointers = [];
 
   /// Callbacks for releasing native resources under management by this [Pool].
   final List<Function()> _managedResourceReleaseCallbacks = [];
 
-  /// Allocates memory on the native heap by using the allocator supplied to
-  /// the constructor.
+  bool _inUse = true;
+
+  /// Creates a pool of allocations.
+  ///
+  /// The [allocator] is used to do the actual allocation and freeing of
+  /// memory. It defaults to using [calloc].
+  Pool([Allocator allocator = calloc]) : _wrappedAllocator = allocator;
+
+  /// Allocates memory and includes it in the pool.
+  ///
+  /// Uses the allocator provided to the [Pool] constructor to do the
+  /// allocation.
   ///
   /// Throws an [ArgumentError] if the number of bytes or alignment cannot be
   /// satisfied.
   @override
-  Pointer<T> allocate<T extends NativeType>(int numBytes, {int alignment}) {
-    final p = _wrappedAllocator.allocate<T>(numBytes, alignment: alignment);
+  Pointer<T> allocate<T extends NativeType>(int byteCount, {int alignment}) {
+    _ensureInUse();
+    final p = _wrappedAllocator.allocate<T>(byteCount, alignment: alignment);
     _managedMemoryPointers.add(p);
     return p;
   }
@@ -45,6 +62,8 @@
   ///
   /// Executes [releaseCallback] on [releaseAll].
   T using<T>(T resource, Function(T) releaseCallback) {
+    _ensureInUse();
+    releaseCallback = Zone.current.bindUnaryCallback(releaseCallback);
     _managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
     return resource;
   }
@@ -55,59 +74,102 @@
   }
 
   /// Releases all resources that this [Pool] manages.
-  void releaseAll() {
-    for (final c in _managedResourceReleaseCallbacks) {
-      c();
+  ///
+  /// If [reuse] is `true`, the pool can be used again after resources
+  /// have been released. If not, the default, then the [allocate]
+  /// and [using] methods must not be called after a call to `releaseAll`.
+  void releaseAll({bool reuse = false}) {
+    if (!reuse) {
+      _inUse = false;
     }
-    _managedResourceReleaseCallbacks.clear();
+    while (_managedResourceReleaseCallbacks.isNotEmpty) {
+      _managedResourceReleaseCallbacks.removeLast()();
+    }
     for (final p in _managedMemoryPointers) {
       _wrappedAllocator.free(p);
     }
     _managedMemoryPointers.clear();
   }
 
+  /// Does nothing, invoke [releaseAll] instead.
   @override
-  void free(Pointer<NativeType> pointer) => throw UnsupportedError(
-      "Individually freeing Pool allocated memory is not allowed");
+  void free(Pointer<NativeType> pointer) {}
+
+  void _ensureInUse() {
+    if (!_inUse) {
+      throw StateError(
+          "Pool no longer in use, `releaseAll(reuse: false)` was called.");
+    }
+  }
 }
 
-/// Creates a [Pool] to manage native resources.
+/// Runs [computation] with a new [Pool], and releases all allocations at the end.
 ///
-/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
-R using<R>(R Function(Pool) f, [Allocator wrappedAllocator = calloc]) {
-  final p = Pool(wrappedAllocator);
+/// If [R] is a [Future], all allocations are released when the future completes.
+///
+/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_
+/// cleaned up.
+R using<R>(R Function(Pool) computation,
+    [Allocator wrappedAllocator = calloc]) {
+  final pool = Pool(wrappedAllocator);
+  bool isAsync = false;
   try {
-    return f(p);
+    final result = computation(pool);
+    if (result is Future) {
+      isAsync = true;
+      return (result.whenComplete(pool.releaseAll) as R);
+    }
+    return result;
   } finally {
-    p.releaseAll();
+    if (!isAsync) {
+      pool.releaseAll();
+    }
   }
 }
 
 /// Creates a zoned [Pool] to manage native resources.
 ///
-/// Pool is availabe through [currentPool].
-///
-/// Please note that all throws are caught and packaged in [RethrownError].
+/// The pool is availabe through [zonePool].
 ///
 /// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
-R usePool<R>(R Function() f, [Allocator wrappedAllocator = calloc]) {
-  final p = Pool(wrappedAllocator);
+R withZonePool<R>(R Function() computation,
+    [Allocator wrappedAllocator = calloc]) {
+  final pool = Pool(wrappedAllocator);
+  var poolHolder = [pool];
+  bool isAsync = false;
   try {
-    return runZoned(() => f(),
-        zoneValues: {#_pool: p},
-        onError: (error, st) => throw RethrownError(error, st));
+    return runZoned(() {
+      final result = computation();
+      if (result is Future) {
+        isAsync = true;
+        result.whenComplete(pool.releaseAll);
+      }
+      return result;
+    }, zoneValues: {#_pool: poolHolder});
   } finally {
-    p.releaseAll();
+    if (!isAsync) {
+      pool.releaseAll();
+      poolHolder.remove(pool);
+    }
   }
 }
 
-/// The [Pool] in the current zone.
-Pool get currentPool => Zone.current[#_pool];
-
-class RethrownError {
-  dynamic original;
-  StackTrace originalStackTrace;
-  RethrownError(this.original, this.originalStackTrace);
-  toString() => """RethrownError(${original})
-${originalStackTrace}""";
+/// A zone-specific [Pool].
+///
+/// Asynchronous computations can share a [Pool]. Use [withZonePool] to create
+/// a new zone with a fresh [Pool], and that pool will then be released
+/// automatically when the function passed to [withZonePool] completes.
+/// All code inside that zone can use `zonePool` to access the pool.
+///
+/// The current pool must not be accessed by code which is not running inside
+/// a zone created by [withZonePool].
+Pool get zonePool {
+  final List<Pool> poolHolder = Zone.current[#_pool];
+  if (poolHolder == null) {
+    throw StateError("Not inside a zone created by `usePool`");
+  }
+  if (!poolHolder.isEmpty) {
+    return poolHolder.single;
+  }
+  throw StateError("Pool as already been cleared with releaseAll.");
 }
diff --git a/samples_2/ffi/resource_management/pool_sample.dart b/samples_2/ffi/resource_management/pool_sample.dart
index c794a40..e3441c1 100644
--- a/samples_2/ffi/resource_management/pool_sample.dart
+++ b/samples_2/ffi/resource_management/pool_sample.dart
@@ -14,7 +14,7 @@
 import 'utf8_helpers.dart';
 import '../dylib_utils.dart';
 
-main() {
+main() async {
   final ffiTestDynamicLibrary =
       dlopenPlatformSpecific("ffi_test_dynamic_library");
 
@@ -110,6 +110,24 @@
   } on Exception catch (e) {
     print("Caught exception: $e");
   }
+
+  /// [using] waits with releasing its resources until after [Future]s
+  /// complete.
+  List<int> freed = [];
+  freeInt(int i) {
+    freed.add(i);
+  }
+
+  Future<int> myFutureInt = using((Pool pool) {
+    return Future.microtask(() {
+      pool.using(1, freeInt);
+      return 1;
+    });
+  });
+
+  Expect.isTrue(freed.isEmpty);
+  await myFutureInt;
+  Expect.equals(1, freed.single);
 }
 
 /// Represents some opaque resource being managed by a library.
diff --git a/samples_2/ffi/resource_management/pool_zoned_sample.dart b/samples_2/ffi/resource_management/pool_zoned_sample.dart
index a1dc9c1..b9142c2 100644
--- a/samples_2/ffi/resource_management/pool_zoned_sample.dart
+++ b/samples_2/ffi/resource_management/pool_zoned_sample.dart
@@ -14,7 +14,7 @@
 import 'utf8_helpers.dart';
 import '../dylib_utils.dart';
 
-main() {
+main() async {
   final ffiTestDynamicLibrary =
       dlopenPlatformSpecific("ffi_test_dynamic_library");
 
@@ -22,9 +22,9 @@
       Void Function(Pointer<Void>, Pointer<Void>, IntPtr),
       void Function(Pointer<Void>, Pointer<Void>, int)>("MemMove");
 
-  // To ensure resources are freed, wrap them in a [using] call.
-  usePool(() {
-    final p = currentPool<Int64>(2);
+  // To ensure resources are freed, wrap them in a [withZonePool] call.
+  withZonePool(() {
+    final p = zonePool<Int64>(2);
     p[0] = 24;
     MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
     print(p[1]);
@@ -33,24 +33,23 @@
 
   // Resources are freed also when abnormal control flow occurs.
   try {
-    usePool(() {
-      final p = currentPool<Int64>(2);
+    withZonePool(() {
+      final p = zonePool<Int64>(2);
       p[0] = 25;
       MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
       print(p[1]);
       Expect.equals(25, p[1]);
       throw Exception("Some random exception");
     });
-  } on RethrownError catch (e) {
-    // Note that exceptions are wrapped when using zones.
-    print("Caught exception: ${e.original}");
+  } catch (e) {
+    print("Caught exception: ${e}");
   }
 
   // In a pool multiple resources can be allocated, which will all be freed
   // at the end of the scope.
-  usePool(() {
-    final p = currentPool<Int64>(2);
-    final p2 = currentPool<Int64>(2);
+  withZonePool(() {
+    final p = zonePool<Int64>(2);
+    final p2 = zonePool<Int64>(2);
     p[0] = 1;
     p[1] = 2;
     MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
@@ -61,10 +60,10 @@
   // If the resource allocation happens in a different scope, it is in the
   // same zone, so it's lifetime is automatically managed by the pool.
   f1() {
-    return currentPool<Int64>(2);
+    return zonePool<Int64>(2);
   }
 
-  usePool(() {
+  withZonePool(() {
     final p = f1();
     final p2 = f1();
     p[0] = 1;
@@ -75,8 +74,8 @@
   });
 
   // Using Strings.
-  usePool(() {
-    final p = "Hello world!".toUtf8(currentPool);
+  withZonePool(() {
+    final p = "Hello world!".toUtf8(zonePool);
     print(p.contents());
   });
 
@@ -93,24 +92,42 @@
       void Function(Pointer<SomeResource>)>("ReleaseResource");
 
   // Using an FFI call to release a resource.
-  usePool(() {
-    final r = currentPool.using(allocateResource(), releaseResource);
+  withZonePool(() {
+    final r = zonePool.using(allocateResource(), releaseResource);
     useResource(r);
   });
 
   // Using an FFI call to release a resource with abnormal control flow.
   try {
-    usePool(() {
-      final r = currentPool.using(allocateResource(), releaseResource);
+    withZonePool(() {
+      final r = zonePool.using(allocateResource(), releaseResource);
       useResource(r);
 
       throw Exception("Some random exception");
     });
     // Resource has been freed.
-  } on RethrownError catch (e) {
-    // Note that exceptions are wrapped when using zones.
-    print("Caught exception: ${e.original}");
+  } catch (e) {
+    print("Caught exception: ${e}");
   }
+
+  /// [using] waits with releasing its resources until after [Future]s
+  /// complete.
+
+  List<int> freed = [];
+  freeInt(int i) {
+    freed.add(i);
+  }
+
+  Future<int> myFutureInt = withZonePool(() {
+    return Future.microtask(() {
+      zonePool.using(1, freeInt);
+      return 1;
+    });
+  });
+
+  Expect.isTrue(freed.isEmpty);
+  await myFutureInt;
+  Expect.equals(1, freed.single);
 }
 
 /// Represents some opaque resource being managed by a library.
diff --git a/tests/language/nnbd/flow_analysis/expr_property_not_promoted_test.dart b/tests/language/nnbd/flow_analysis/expr_property_not_promoted_test.dart
new file mode 100644
index 0000000..bfbfc95
--- /dev/null
+++ b/tests/language/nnbd/flow_analysis/expr_property_not_promoted_test.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../../static_type_helper.dart';
+
+// This test verifies that neither an `== null` nor an `is` test can promote the
+// type of a property access on an arbitrary expression.  (Such accesses cannot
+// be promoted soundly).
+
+class _C {
+  final int? _f;
+
+  _C(this._f);
+}
+
+void equality(_C Function() c) {
+  if (c()._f == null) {
+    c()._f.expectStaticType<Exactly<int?>>();
+  } else {
+    c()._f.expectStaticType<Exactly<int?>>();
+  }
+}
+
+void is_(_C Function() c) {
+  if (c()._f is int) {
+    c()._f.expectStaticType<Exactly<int?>>();
+  } else {
+    c()._f.expectStaticType<Exactly<int?>>();
+  }
+}
+
+main() {
+  equality(() => _C(1));
+  is_(() => _C(1));
+  equality(() => _C(null));
+  is_(() => _C(null));
+}
diff --git a/tests/language/nnbd/flow_analysis/this_not_promoted_test.dart b/tests/language/nnbd/flow_analysis/this_not_promoted_test.dart
new file mode 100644
index 0000000..ec329b4
--- /dev/null
+++ b/tests/language/nnbd/flow_analysis/this_not_promoted_test.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../../static_type_helper.dart';
+
+// This test verifies that neither an `== null` nor an `is` test can promote the
+// type of `this`.  (In principle, we could soundly do so, but we have decided
+// not to do so at this time).
+
+class C {
+  void equality() {
+    if (this == null) {
+      this.expectStaticType<Exactly<C>>();
+    } else {
+      this.expectStaticType<Exactly<C>>();
+    }
+  }
+
+  void isSameType() {
+    if (this is C) {
+      this.expectStaticType<Exactly<C>>();
+    } else {
+      this.expectStaticType<Exactly<C>>();
+    }
+  }
+
+  void isSubtype() {
+    if (this is D) {
+      this.expectStaticType<Exactly<C>>();
+    } else {
+      this.expectStaticType<Exactly<C>>();
+    }
+  }
+}
+
+class D extends C {}
+
+class E {}
+
+class F extends E {}
+
+extension on E {
+  void equality() {
+    if (this == null) {
+      this.expectStaticType<Exactly<E>>();
+    } else {
+      this.expectStaticType<Exactly<E>>();
+    }
+  }
+
+  void isSameType() {
+    if (this is E) {
+      this.expectStaticType<Exactly<E>>();
+    } else {
+      this.expectStaticType<Exactly<E>>();
+    }
+  }
+
+  void isSubtype() {
+    if (this is F) {
+      this.expectStaticType<Exactly<E>>();
+    } else {
+      this.expectStaticType<Exactly<E>>();
+    }
+  }
+}
+
+class G {}
+
+extension on G? {
+  void equality() {
+    if (this == null) {
+      this.expectStaticType<Exactly<G?>>();
+    } else {
+      this.expectStaticType<Exactly<G?>>();
+    }
+  }
+
+  void isSameType() {
+    if (this is G?) {
+      this.expectStaticType<Exactly<G?>>();
+    } else {
+      this.expectStaticType<Exactly<G?>>();
+    }
+  }
+
+  void isSubtype() {
+    if (this is G) {
+      this.expectStaticType<Exactly<G?>>();
+    } else {
+      this.expectStaticType<Exactly<G?>>();
+    }
+  }
+}
+
+main() {
+  C().equality();
+  C().isSameType();
+  C().isSubtype();
+  E().equality();
+  E().isSameType();
+  E().isSubtype();
+  G().equality();
+  G().isSameType();
+  G().isSubtype();
+  (null as G?).equality();
+  (null as G?).isSameType();
+  (null as G?).isSubtype();
+}
diff --git a/tests/language/nnbd/flow_analysis/this_property_not_promoted_test.dart b/tests/language/nnbd/flow_analysis/this_property_not_promoted_test.dart
new file mode 100644
index 0000000..375221b
--- /dev/null
+++ b/tests/language/nnbd/flow_analysis/this_property_not_promoted_test.dart
@@ -0,0 +1,106 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../../static_type_helper.dart';
+
+// This test verifies that neither an `== null` nor an `is` test can promote the
+// type of a property access on `this`.  (In principle, we could soundly promote
+// some such accesses, but we have decided not to do so at this time).
+
+class _C {
+  final int? _f;
+
+  _C(this._f);
+
+  void equality_implicitThis() {
+    if (_f == null) {
+      _f.expectStaticType<Exactly<int?>>();
+    } else {
+      _f.expectStaticType<Exactly<int?>>();
+    }
+  }
+
+  void equality_explicitThis() {
+    if (this._f == null) {
+      this._f.expectStaticType<Exactly<int?>>();
+    } else {
+      this._f.expectStaticType<Exactly<int?>>();
+    }
+  }
+
+  void is_implicitThis() {
+    if (_f is int) {
+      _f.expectStaticType<Exactly<int?>>();
+    } else {
+      _f.expectStaticType<Exactly<int?>>();
+    }
+  }
+
+  void is_explicitThis() {
+    if (this._f is int) {
+      this._f.expectStaticType<Exactly<int?>>();
+    } else {
+      this._f.expectStaticType<Exactly<int?>>();
+    }
+  }
+}
+
+class _D {
+  final int? _f;
+
+  _D(this._f);
+}
+
+extension on _D {
+  void equality_implicitThis() {
+    if (_f == null) {
+      _f.expectStaticType<Exactly<int?>>();
+    } else {
+      _f.expectStaticType<Exactly<int?>>();
+    }
+  }
+
+  void equality_explicitThis() {
+    if (this._f == null) {
+      this._f.expectStaticType<Exactly<int?>>();
+    } else {
+      this._f.expectStaticType<Exactly<int?>>();
+    }
+  }
+
+  void is_implicitThis() {
+    if (_f is int) {
+      _f.expectStaticType<Exactly<int?>>();
+    } else {
+      _f.expectStaticType<Exactly<int?>>();
+    }
+  }
+
+  void is_explicitThis() {
+    if (this._f is int) {
+      this._f.expectStaticType<Exactly<int?>>();
+    } else {
+      this._f.expectStaticType<Exactly<int?>>();
+    }
+  }
+}
+
+main() {
+  _C(1).equality_implicitThis();
+  _C(1).equality_explicitThis();
+  _C(1).is_implicitThis();
+  _C(1).is_explicitThis();
+  _C(null).equality_implicitThis();
+  _C(null).equality_explicitThis();
+  _C(null).is_implicitThis();
+  _C(null).is_explicitThis();
+  _D(1).equality_implicitThis();
+  _D(1).equality_explicitThis();
+  _D(1).is_implicitThis();
+  _D(1).is_explicitThis();
+  _D(null).equality_implicitThis();
+  _D(null).equality_explicitThis();
+  _D(null).is_implicitThis();
+  _D(null).is_explicitThis();
+}
diff --git a/tests/language/nnbd/flow_analysis/unreachable_via_expr_property_test.dart b/tests/language/nnbd/flow_analysis/unreachable_via_expr_property_test.dart
new file mode 100644
index 0000000..a6e9676
--- /dev/null
+++ b/tests/language/nnbd/flow_analysis/unreachable_via_expr_property_test.dart
@@ -0,0 +1,163 @@
+// 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.
+
+/// This test verifies that `is` and `==` tests performed on a property get of
+/// an arbitrary expression do not lead to code being considered unreachable.
+/// (In principle, we could soundly mark some such code as unreachable, but we
+/// have decided not to do so at this time).
+///
+/// Exception: when the static type of the property access is guaranteed to be
+/// Null, and we are performing an `== null` test, then we do mark the non-null
+/// branch as unreachable.
+
+import '../../static_type_helper.dart';
+
+class C {
+  Null get nullProperty => null;
+  Object? get objectQProperty => null;
+}
+
+void equalitySimple(int? x, int? y, C Function() f) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (f().nullProperty == null) {
+    x = null;
+  } else {
+    y = null;
+  }
+  // Since the assignment to x was reachable, it should have static type
+  // `int?` now.  But y should still have static type `int`.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int>>();
+}
+
+void equalityWithBogusPromotion(int? x, int? y, C Function() f) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (f().objectQProperty is Null) {
+    if (f().objectQProperty == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+void isSimple(int? x, int? y, C Function() f) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (f().nullProperty is Never) {
+    x = null;
+  } else {
+    y = null;
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+void isWithBogusPromotion(int? x, int? y, C Function() f) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (f().objectQProperty is Null) {
+    if (f().objectQProperty is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+class _C {
+  final Null _nullField = null;
+  final Object? _objectQField = null;
+}
+
+void equalitySimplePrivate(int? x, int? y, _C Function() f) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (f()._nullField == null) {
+    x = null;
+  } else {
+    y = null;
+  }
+  // Since the assignment to x was reachable, it should have static type
+  // `int?` now.  But y should still have static type `int`.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int>>();
+}
+
+void equalityWithBogusPromotionPrivate(int? x, int? y, _C Function() f) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (f()._objectQField is Null) {
+    if (f()._objectQField == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+void isSimplePrivate(int? x, int? y, _C Function() f) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (f()._nullField is Never) {
+    x = null;
+  } else {
+    y = null;
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+void isWithBogusPromotionPrivate(int? x, int? y, _C Function() f) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (f()._objectQField is Null) {
+    if (f()._objectQField is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+main() {
+  equalitySimple(1, 1, () => C());
+  equalityWithBogusPromotion(1, 1, () => C());
+  isSimple(1, 1, () => C());
+  isWithBogusPromotion(1, 1, () => C());
+  equalitySimplePrivate(1, 1, () => _C());
+  equalityWithBogusPromotionPrivate(1, 1, () => _C());
+  isSimplePrivate(1, 1, () => _C());
+  isWithBogusPromotionPrivate(1, 1, () => _C());
+}
diff --git a/tests/language/nnbd/flow_analysis/unreachable_via_this_property_test.dart b/tests/language/nnbd/flow_analysis/unreachable_via_this_property_test.dart
new file mode 100644
index 0000000..ced63a5
--- /dev/null
+++ b/tests/language/nnbd/flow_analysis/unreachable_via_this_property_test.dart
@@ -0,0 +1,583 @@
+// 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.
+
+/// This test verifies that `is` and `==` tests performed on a property get of
+/// `this` do not lead to code being considered unreachable.  (In principle, we
+/// could soundly mark some such code as unreachable, but we have decided not to
+/// do so at this time).
+///
+/// Exception: when the static type of the property access is guaranteed to be
+/// Null, and we are performing an `== null` test, then we do mark the non-null
+/// branch as unreachable.
+
+import '../../static_type_helper.dart';
+
+class B {
+  Null get nullProperty => null;
+  Object? get objectQProperty => null;
+}
+
+class C extends B {
+  void equalitySimple_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (nullProperty == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignment to x was reachable, it should have static type
+    // `int?` now.  But y should still have static type `int`.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int>>();
+  }
+
+  void equalitySimple_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this.nullProperty == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignment to x was reachable, it should have static type
+    // `int?` now.  But y should still have static type `int`.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int>>();
+  }
+
+  void equalityWithBogusPromotion_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (objectQProperty is Null) {
+      if (objectQProperty == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void equalityWithBogusPromotion_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this.objectQProperty is Null) {
+      if (this.objectQProperty == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (nullProperty is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this.nullProperty is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (objectQProperty is Null) {
+      if (objectQProperty is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this.objectQProperty is Null) {
+      if (this.objectQProperty is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+}
+
+class D extends B {}
+
+extension on D {
+  void equalitySimple_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (nullProperty == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignment to x was reachable, it should have static type
+    // `int?` now.  But y should still have static type `int`.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int>>();
+  }
+
+  void equalitySimple_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this.nullProperty == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignment to x was reachable, it should have static type
+    // `int?` now.  But y should still have static type `int`.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int>>();
+  }
+
+  void equalityWithBogusPromotion_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (objectQProperty is Null) {
+      if (objectQProperty == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void equalityWithBogusPromotion_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this.objectQProperty is Null) {
+      if (this.objectQProperty == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (nullProperty is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this.nullProperty is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (objectQProperty is Null) {
+      if (objectQProperty is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this.objectQProperty is Null) {
+      if (this.objectQProperty is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+}
+
+class _B {
+  final Null _nullField = null;
+  final Object? _objectQField = null;
+}
+
+class _C extends _B {
+  void equalitySimple_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (_nullField == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignment to x was reachable, it should have static type
+    // `int?` now.  But y should still have static type `int`.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int>>();
+  }
+
+  void equalitySimple_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this._nullField == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignment to x was reachable, it should have static type
+    // `int?` now.  But y should still have static type `int`.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int>>();
+  }
+
+  void equalityWithBogusPromotion_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (_objectQField is Null) {
+      if (_objectQField == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void equalityWithBogusPromotion_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this._objectQField is Null) {
+      if (this._objectQField == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (_nullField is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this._nullField is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (_objectQField is Null) {
+      if (_objectQField is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this._objectQField is Null) {
+      if (this._objectQField is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+}
+
+class _D extends _B {}
+
+extension on _D {
+  void equalitySimple_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (_nullField == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignment to x was reachable, it should have static type
+    // `int?` now.  But y should still have static type `int`.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int>>();
+  }
+
+  void equalitySimple_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this._nullField == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignment to x was reachable, it should have static type
+    // `int?` now.  But y should still have static type `int`.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int>>();
+  }
+
+  void equalityWithBogusPromotion_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (_objectQField is Null) {
+      if (_objectQField == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void equalityWithBogusPromotion_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this._objectQField is Null) {
+      if (this._objectQField == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (_nullField is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this._nullField is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion_implicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (_objectQField is Null) {
+      if (_objectQField is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion_explicitThis(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this._objectQField is Null) {
+      if (this._objectQField is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+}
+
+main() {
+  C().equalitySimple_implicitThis(1, 1);
+  C().equalitySimple_explicitThis(1, 1);
+  C().equalityWithBogusPromotion_implicitThis(1, 1);
+  C().equalityWithBogusPromotion_explicitThis(1, 1);
+  C().isSimple_implicitThis(1, 1);
+  C().isSimple_explicitThis(1, 1);
+  C().isWithBogusPromotion_implicitThis(1, 1);
+  C().isWithBogusPromotion_explicitThis(1, 1);
+  D().equalitySimple_implicitThis(1, 1);
+  D().equalitySimple_explicitThis(1, 1);
+  D().equalityWithBogusPromotion_implicitThis(1, 1);
+  D().equalityWithBogusPromotion_explicitThis(1, 1);
+  D().isSimple_implicitThis(1, 1);
+  D().isSimple_explicitThis(1, 1);
+  D().isWithBogusPromotion_implicitThis(1, 1);
+  D().isWithBogusPromotion_explicitThis(1, 1);
+  _C().equalitySimple_implicitThis(1, 1);
+  _C().equalitySimple_explicitThis(1, 1);
+  _C().equalityWithBogusPromotion_implicitThis(1, 1);
+  _C().equalityWithBogusPromotion_explicitThis(1, 1);
+  _C().isSimple_implicitThis(1, 1);
+  _C().isSimple_explicitThis(1, 1);
+  _C().isWithBogusPromotion_implicitThis(1, 1);
+  _C().isWithBogusPromotion_explicitThis(1, 1);
+  _D().equalitySimple_implicitThis(1, 1);
+  _D().equalitySimple_explicitThis(1, 1);
+  _D().equalityWithBogusPromotion_implicitThis(1, 1);
+  _D().equalityWithBogusPromotion_explicitThis(1, 1);
+  _D().isSimple_implicitThis(1, 1);
+  _D().isSimple_explicitThis(1, 1);
+  _D().isWithBogusPromotion_implicitThis(1, 1);
+  _D().isWithBogusPromotion_explicitThis(1, 1);
+}
diff --git a/tests/language/nnbd/flow_analysis/unreachable_via_this_test.dart b/tests/language/nnbd/flow_analysis/unreachable_via_this_test.dart
new file mode 100644
index 0000000..cc478bf
--- /dev/null
+++ b/tests/language/nnbd/flow_analysis/unreachable_via_this_test.dart
@@ -0,0 +1,231 @@
+// 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.
+
+/// This test verifies that `is` and `==` tests performed on `this` do not lead
+/// to code being considered unreachable.  (In principle, we could soundly mark
+/// some such code as unreachable, but we have decided not to do so at this
+/// time).
+
+import '../../static_type_helper.dart';
+
+class C {
+  void equalitySimple(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void equalityWithBogusPromotion(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this is Null) {
+      if (this == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this is Null) {
+      if (this is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+}
+
+class D {}
+
+extension on D {
+  void equalitySimple(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void equalityWithBogusPromotion(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this is Null) {
+      if (this == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this is Null) {
+      if (this is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+}
+
+class E {}
+
+extension on E? {
+  void equalitySimple(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void equalityWithBogusPromotion(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this is Null) {
+      if (this == null) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isSimple(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+
+  void isWithBogusPromotion(int? x, int? y) {
+    if (x == null || y == null) return;
+    x.expectStaticType<Exactly<int>>();
+    y.expectStaticType<Exactly<int>>();
+    if (this is Null) {
+      if (this is Never) {
+        x = null;
+      } else {
+        y = null;
+      }
+    }
+    // Since the assignments to x and y were both reachable, they should have
+    // static type `int?` now.
+    x.expectStaticType<Exactly<int?>>();
+    y.expectStaticType<Exactly<int?>>();
+  }
+}
+
+main() {
+  C().equalitySimple(1, 1);
+  C().equalityWithBogusPromotion(1, 1);
+  C().isSimple(1, 1);
+  C().isWithBogusPromotion(1, 1);
+  D().equalitySimple(1, 1);
+  D().equalityWithBogusPromotion(1, 1);
+  D().isSimple(1, 1);
+  D().isWithBogusPromotion(1, 1);
+  E().equalitySimple(1, 1);
+  E().equalityWithBogusPromotion(1, 1);
+  E().isSimple(1, 1);
+  E().isWithBogusPromotion(1, 1);
+  (null as E?).equalitySimple(1, 1);
+  (null as E?).equalityWithBogusPromotion(1, 1);
+  (null as E?).isSimple(1, 1);
+  (null as E?).isWithBogusPromotion(1, 1);
+}
diff --git a/tests/language/nnbd/flow_analysis/unreachable_via_variable_property_test.dart b/tests/language/nnbd/flow_analysis/unreachable_via_variable_property_test.dart
new file mode 100644
index 0000000..2268dc5
--- /dev/null
+++ b/tests/language/nnbd/flow_analysis/unreachable_via_variable_property_test.dart
@@ -0,0 +1,163 @@
+// 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.
+
+/// This test verifies that `is` and `==` tests performed on a property get of a
+/// variable do not lead to code being considered unreachable.  (In principle,
+/// we could soundly mark some such code as unreachable, but we have decided not
+/// to do so at this time).
+///
+/// Exception: when the static type of the property access is guaranteed to be
+/// Null, and we are performing an `== null` test, then we do mark the non-null
+/// branch as unreachable.
+
+import '../../static_type_helper.dart';
+
+class C {
+  Null get nullProperty => null;
+  Object? get objectQProperty => null;
+}
+
+void equalitySimple(int? x, int? y, C c) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (c.nullProperty == null) {
+    x = null;
+  } else {
+    y = null;
+  }
+  // Since the assignment to x was reachable, it should have static type
+  // `int?` now.  But y should still have static type `int`.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int>>();
+}
+
+void equalityWithBogusPromotion(int? x, int? y, C c) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (c.objectQProperty is Null) {
+    if (c.objectQProperty == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+void isSimple(int? x, int? y, C c) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (c.nullProperty is Never) {
+    x = null;
+  } else {
+    y = null;
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+void isWithBogusPromotion(int? x, int? y, C c) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (c.objectQProperty is Null) {
+    if (c.objectQProperty is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+class _C {
+  final Null _nullField = null;
+  final Object? _objectQField = null;
+}
+
+void equalitySimplePrivate(int? x, int? y, _C c) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (c._nullField == null) {
+    x = null;
+  } else {
+    y = null;
+  }
+  // Since the assignment to x was reachable, it should have static type
+  // `int?` now.  But y should still have static type `int`.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int>>();
+}
+
+void equalityWithBogusPromotionPrivate(int? x, int? y, _C c) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (c._objectQField is Null) {
+    if (c._objectQField == null) {
+      x = null;
+    } else {
+      y = null;
+    }
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+void isSimplePrivate(int? x, int? y, _C c) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (c._nullField is Never) {
+    x = null;
+  } else {
+    y = null;
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+void isWithBogusPromotionPrivate(int? x, int? y, _C c) {
+  if (x == null || y == null) return;
+  x.expectStaticType<Exactly<int>>();
+  y.expectStaticType<Exactly<int>>();
+  if (c._objectQField is Null) {
+    if (c._objectQField is Never) {
+      x = null;
+    } else {
+      y = null;
+    }
+  }
+  // Since the assignments to x and y were both reachable, they should have
+  // static type `int?` now.
+  x.expectStaticType<Exactly<int?>>();
+  y.expectStaticType<Exactly<int?>>();
+}
+
+main() {
+  equalitySimple(1, 1, C());
+  equalityWithBogusPromotion(1, 1, C());
+  isSimple(1, 1, C());
+  isWithBogusPromotion(1, 1, C());
+  equalitySimplePrivate(1, 1, _C());
+  equalityWithBogusPromotionPrivate(1, 1, _C());
+  isSimplePrivate(1, 1, _C());
+  isWithBogusPromotionPrivate(1, 1, _C());
+}
diff --git a/tests/language/nnbd/flow_analysis/variable_property_not_promoted_test.dart b/tests/language/nnbd/flow_analysis/variable_property_not_promoted_test.dart
new file mode 100644
index 0000000..6aa9d0c
--- /dev/null
+++ b/tests/language/nnbd/flow_analysis/variable_property_not_promoted_test.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../../static_type_helper.dart';
+
+// This test verifies that neither an `== null` nor an `is` test can promote the
+// type of a property access on a variable.  (In principle, we could soundly
+// promote some such accesses, but we have decided not to do so at this time).
+
+class _C {
+  final int? _f;
+
+  _C(this._f);
+}
+
+void equality(_C c) {
+  if (c._f == null) {
+    c._f.expectStaticType<Exactly<int?>>();
+  } else {
+    c._f.expectStaticType<Exactly<int?>>();
+  }
+}
+
+void is_(_C c) {
+  if (c._f is int) {
+    c._f.expectStaticType<Exactly<int?>>();
+  } else {
+    c._f.expectStaticType<Exactly<int?>>();
+  }
+}
+
+main() {
+  equality(_C(1));
+  is_(_C(1));
+  equality(_C(null));
+  is_(_C(null));
+}
diff --git a/tests/standalone/io/shared_socket_test.dart b/tests/standalone/io/shared_socket_test.dart
index 2fb755f..3195b63 100644
--- a/tests/standalone/io/shared_socket_test.dart
+++ b/tests/standalone/io/shared_socket_test.dart
@@ -82,7 +82,7 @@
   }
 }
 
-Future<String> get(String url) async {
+Future<String> get(Uri url) async {
   while (true) {
     try {
       await http.get(url);
@@ -96,7 +96,7 @@
     final futures = <Future>[];
     final numAtOnce = 16; // enough to keep the server busy
     for (int i = 0; i < numAtOnce; ++i) {
-      futures.add(get('http://localhost:$port').then((_) {}));
+      futures.add(get(Uri.http('localhost:$port', '')).then((_) {}));
     }
     await Future.wait(futures);
   }
diff --git a/tests/standalone_2/io/shared_socket_test.dart b/tests/standalone_2/io/shared_socket_test.dart
index bd1a041..2324884a1 100644
--- a/tests/standalone_2/io/shared_socket_test.dart
+++ b/tests/standalone_2/io/shared_socket_test.dart
@@ -84,7 +84,7 @@
   }
 }
 
-Future<String> get(String url) async {
+Future<String> get(Uri url) async {
   while (true) {
     try {
       await http.get(url);
@@ -98,7 +98,7 @@
     final futures = <Future>[];
     final numAtOnce = 16; // enough to keep the server busy
     for (int i = 0; i < numAtOnce; ++i) {
-      futures.add(get('http://localhost:$port').then((_) {}));
+      futures.add(get(Uri.http('localhost:$port', '')).then((_) {}));
     }
     await Future.wait(futures);
   }
diff --git a/tools/VERSION b/tools/VERSION
index 61c91e3..cdb060a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 231
+PRERELEASE 232
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/bots/get_builder_status.dart b/tools/bots/get_builder_status.dart
index e058e42..d66167f 100755
--- a/tools/bots/get_builder_status.dart
+++ b/tools/bots/get_builder_status.dart
@@ -22,10 +22,10 @@
 
 /*late*/ bool useStagingDatabase;
 
-String get queryUrl {
-  var project = useStagingDatabase ? "dart-ci-staging" : "dart-ci";
-  return 'https://firestore.googleapis.com/v1/'
-      'projects/$project/databases/(default)/documents:runQuery';
+Uri get _queryUrl {
+  var project = useStagingDatabase ? 'dart-ci-staging' : 'dart-ci';
+  return Uri.https('firestore.googleapis.com',
+      '/v1/projects/$project/databases/(default)/documents:runQuery');
 }
 
 /*late*/ String builder;
@@ -226,7 +226,7 @@
     'Accept': 'application/json',
     'Content-Type': 'application/json'
   };
-  return client.post(queryUrl, headers: headers, body: query);
+  return client.post(_queryUrl, headers: headers, body: query);
 }
 
 String buildQuery() => jsonEncode({
diff --git a/tools/bots/post_results_to_pubsub.dart b/tools/bots/post_results_to_pubsub.dart
index 0ad3df2..1134188 100644
--- a/tools/bots/post_results_to_pubsub.dart
+++ b/tools/bots/post_results_to_pubsub.dart
@@ -30,10 +30,8 @@
 
 const resultsPerMessage = 100;
 
-String getPostUrl(String project) {
-  return 'https://pubsub.googleapis.com/v1/projects/$project'
-      '/topics/results:publish';
-}
+Uri _postUrl(String project) => Uri.https(
+    'pubsub.googleapis.com', 'v1/projects/$project/topics/results:publish');
 
 main(List<String> args) async {
   final parser = new ArgParser();
@@ -114,7 +112,7 @@
       ]
     });
     final headers = {'Authorization': 'Bearer $token'};
-    final postUrl = getPostUrl(project);
+    final postUrl = _postUrl(project);
     final response =
         await client.post(postUrl, headers: headers, body: jsonMessage);