Flow analysis: move tracking of definite assignment into VariableModel class.

Change-Id: I28cdc6f65aa3488da3538a7b920dc10f87ec1de0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/115003
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/front_end/lib/src/fasta/flow_analysis/flow_analysis.dart b/pkg/front_end/lib/src/fasta/flow_analysis/flow_analysis.dart
index 48999a6..f220caf 100644
--- a/pkg/front_end/lib/src/fasta/flow_analysis/flow_analysis.dart
+++ b/pkg/front_end/lib/src/fasta/flow_analysis/flow_analysis.dart
@@ -136,8 +136,6 @@
     return result;
   }
 
-  final _VariableSet<Variable> _emptySet;
-
   /// The [NodeOperations], used to manipulate expressions.
   final NodeOperations<Expression> nodeOperations;
 
@@ -188,22 +186,10 @@
     TypeOperations<Variable, Type> typeOperations,
     FunctionBodyAccess<Variable> functionBody,
   ) {
-    _VariableSet<Variable> emptySet =
-        new FlowModel<Variable, Type>(false).notAssigned;
-    return new FlowAnalysis._(
-      nodeOperations,
-      typeOperations,
-      functionBody,
-      emptySet,
-    );
+    return new FlowAnalysis._(nodeOperations, typeOperations, functionBody);
   }
 
-  FlowAnalysis._(
-    this.nodeOperations,
-    this.typeOperations,
-    this.functionBody,
-    this._emptySet,
-  ) {
+  FlowAnalysis._(this.nodeOperations, this.typeOperations, this.functionBody) {
     _current = new FlowModel<Variable, Type>(true);
   }
 
@@ -520,7 +506,16 @@
   /// Return whether the [variable] is definitely assigned in the current state.
   bool isAssigned(Variable variable) {
     _variableReferenced(variable);
-    return !_current.notAssigned.contains(variable);
+    VariableModel<Type> variableInfo = _current.variableInfo[variable];
+    if (variableInfo == null) {
+      // In error-free code, variables should always be registered with flow
+      // analysis before they're used.  But this can't be relied on when the
+      // analyzer is doing error recovery.  So if we encounter a variable that
+      // hasn't been registered with flow analysis yet, assume it's unassigned.
+      return false;
+    } else {
+      return variableInfo.assigned;
+    }
   }
 
   void isExpression_end(
@@ -692,12 +687,7 @@
   void tryFinallyStatement_end(Set<Variable> assignedInFinally) {
     _variablesReferenced(assignedInFinally);
     FlowModel<Variable, Type> afterBody = _stack.removeLast();
-    _current = _current.restrict(
-      typeOperations,
-      _emptySet,
-      afterBody,
-      assignedInFinally,
-    );
+    _current = _current.restrict(typeOperations, afterBody, assignedInFinally);
   }
 
   void tryFinallyStatement_finallyBegin(Iterable<Variable> assignedInBody) {
@@ -737,7 +727,7 @@
   /// Register write of the given [variable] in the current state.
   void write(Variable variable) {
     _variableReferenced(variable);
-    _current = _current.write(typeOperations, _emptySet, variable);
+    _current = _current.write(typeOperations, variable);
   }
 
   void _conditionalEnd(Expression condition) {
@@ -787,10 +777,6 @@
   /// Indicates whether this point in the control flow is reachable.
   final bool reachable;
 
-  /// The set of variables that are not yet definitely assigned at this point in
-  /// the control flow.
-  final _VariableSet<Variable> notAssigned;
-
   /// For each variable being tracked by flow analysis, the variable's model.
   ///
   /// Flow analysis has no awareness of scope, so variables that are out of
@@ -810,15 +796,10 @@
   FlowModel(bool reachable)
       : this._(
           reachable,
-          new _VariableSet<Variable>._(const []),
           const {},
         );
 
-  FlowModel._(
-    this.reachable,
-    this.notAssigned,
-    this.variableInfo,
-  ) {
+  FlowModel._(this.reachable, this.variableInfo) {
     assert(() {
       for (VariableModel<Type> value in variableInfo.values) {
         assert(value != null);
@@ -831,17 +812,11 @@
   /// optional [assigned] boolean indicates whether the variable is assigned at
   /// the point of declaration.
   FlowModel<Variable, Type> add(Variable variable, {bool assigned: false}) {
-    _VariableSet<Variable> newNotAssigned =
-        assigned ? notAssigned : notAssigned.add(variable);
     Map<Variable, VariableModel<Type>> newVariableInfo =
         new Map<Variable, VariableModel<Type>>.from(variableInfo);
-    newVariableInfo[variable] = new VariableModel<Type>(null);
+    newVariableInfo[variable] = new VariableModel<Type>(null, assigned);
 
-    return new FlowModel<Variable, Type>._(
-      reachable,
-      newNotAssigned,
-      newVariableInfo,
-    );
+    return new FlowModel<Variable, Type>._(reachable, newVariableInfo);
   }
 
   /// Updates the state to indicate that the given [variable] has been
@@ -914,11 +889,7 @@
 
     if (identical(newVariableInfo, variableInfo)) return this;
 
-    return new FlowModel<Variable, Type>._(
-      reachable,
-      notAssigned,
-      newVariableInfo,
-    );
+    return new FlowModel<Variable, Type>._(reachable, newVariableInfo);
   }
 
   /// Updates the state to reflect a control path that is known to have
@@ -942,22 +913,10 @@
   /// block are considered "unsafe" because the assignment might have cancelled
   /// the effect of any promotion that occurred inside the `try` block.
   FlowModel<Variable, Type> restrict(
-    TypeOperations<Variable, Type> typeOperations,
-    _VariableSet<Variable> emptySet,
-    FlowModel<Variable, Type> other,
-    Set<Variable> unsafe,
-  ) {
+      TypeOperations<Variable, Type> typeOperations,
+      FlowModel<Variable, Type> other,
+      Set<Variable> unsafe) {
     bool newReachable = reachable && other.reachable;
-    _VariableSet<Variable> newNotAssigned = notAssigned.intersect(
-      empty: emptySet,
-      other: other.notAssigned,
-    );
-    if (newNotAssigned.variables.length == notAssigned.variables.length) {
-      newNotAssigned = notAssigned;
-    } else if (newNotAssigned.variables.length ==
-        other.notAssigned.variables.length) {
-      newNotAssigned = other.notAssigned;
-    }
 
     Map<Variable, VariableModel<Type>> newVariableInfo =
         <Variable, VariableModel<Type>>{};
@@ -985,13 +944,7 @@
       newVariableInfo = other.variableInfo;
     }
 
-    return _identicalOrNew(
-      this,
-      other,
-      newReachable,
-      newNotAssigned,
-      newVariableInfo,
-    );
+    return _identicalOrNew(this, other, newReachable, newVariableInfo);
   }
 
   /// Updates the state to indicate whether the control flow path is
@@ -999,54 +952,23 @@
   FlowModel<Variable, Type> setReachable(bool reachable) {
     if (this.reachable == reachable) return this;
 
-    return new FlowModel<Variable, Type>._(
-      reachable,
-      notAssigned,
-      variableInfo,
-    );
+    return new FlowModel<Variable, Type>._(reachable, variableInfo);
   }
 
   @override
-  String toString() => '($reachable, $notAssigned, $variableInfo)';
+  String toString() => '($reachable, $variableInfo)';
 
   /// Updates the state to indicate that an assignment was made to the given
   /// [variable].  The variable is marked as definitely assigned, and any
   /// previous type promotion is removed.
   ///
   /// TODO(paulberry): allow for writes that preserve type promotions.
-  FlowModel<Variable, Type> write(TypeOperations<Variable, Type> typeOperations,
-      _VariableSet<Variable> emptySet, Variable variable) {
-    _VariableSet<Variable> newNotAssigned =
-        typeOperations.isLocalVariable(variable)
-            ? notAssigned.remove(emptySet, variable)
-            : notAssigned;
-
-    Map<Variable, VariableModel<Type>> newVariableInfo =
-        _removePromoted(variableInfo, variable);
-
-    if (identical(newNotAssigned, notAssigned) &&
-        identical(newVariableInfo, variableInfo)) {
-      return this;
-    }
-
-    return new FlowModel<Variable, Type>._(
-      reachable,
-      newNotAssigned,
-      newVariableInfo,
-    );
-  }
-
-  /// Updates a "variableInfo" [map] to indicate that a [variable] is no longer
-  /// promoted, treating the map as immutable.
-  Map<Variable, VariableModel<Type>> _removePromoted(
-      Map<Variable, VariableModel<Type>> map, Variable variable) {
-    VariableModel<Type> info = map[variable];
-    if (info.promotedType == null) return map;
-
-    Map<Variable, VariableModel<Type>> result =
-        new Map<Variable, VariableModel<Type>>.from(map);
-    result[variable] = info.withPromotedType(null);
-    return result;
+  FlowModel<Variable, Type> write(
+      TypeOperations<Variable, Type> typeOperations, Variable variable) {
+    VariableModel<Type> infoForVar = variableInfo[variable];
+    VariableModel<Type> newInfoForVar = infoForVar.write();
+    if (identical(newInfoForVar, infoForVar)) return this;
+    return _updateVariableInfo(variable, newInfoForVar);
   }
 
   /// Updates a "variableInfo" [map] to indicate that a set of [variable] is no
@@ -1084,8 +1006,7 @@
     Map<Variable, VariableModel<Type>> newVariableInfo =
         new Map<Variable, VariableModel<Type>>.from(variableInfo);
     newVariableInfo[variable] = model;
-    return new FlowModel<Variable, Type>._(
-        reachable, notAssigned, newVariableInfo);
+    return new FlowModel<Variable, Type>._(reachable, newVariableInfo);
   }
 
   /// Forms a new state to reflect a control flow path that might have come from
@@ -1109,19 +1030,12 @@
     if (!first.reachable && second.reachable) return second;
 
     bool newReachable = first.reachable || second.reachable;
-    _VariableSet<Variable> newNotAssigned =
-        first.notAssigned.union(second.notAssigned);
     Map<Variable, VariableModel<Type>> newVariableInfo =
         FlowModel.joinVariableInfo(
             typeOperations, first.variableInfo, second.variableInfo);
 
     return FlowModel._identicalOrNew(
-      first,
-      second,
-      newReachable,
-      newNotAssigned,
-      newVariableInfo,
-    );
+        first, second, newReachable, newVariableInfo);
   }
 
   /// Joins two "variable info" maps.  See [join] for details.
@@ -1161,28 +1075,20 @@
   /// Creates a new [FlowModel] object, unless it is equivalent to either
   /// [first] or [second], in which case one of those objects is re-used.
   static FlowModel<Variable, Type> _identicalOrNew<Variable, Type>(
-    FlowModel<Variable, Type> first,
-    FlowModel<Variable, Type> second,
-    bool newReachable,
-    _VariableSet<Variable> newNotAssigned,
-    Map<Variable, VariableModel<Type>> newVariableInfo,
-  ) {
+      FlowModel<Variable, Type> first,
+      FlowModel<Variable, Type> second,
+      bool newReachable,
+      Map<Variable, VariableModel<Type>> newVariableInfo) {
     if (first.reachable == newReachable &&
-        identical(first.notAssigned, newNotAssigned) &&
         identical(first.variableInfo, newVariableInfo)) {
       return first;
     }
     if (second.reachable == newReachable &&
-        identical(second.notAssigned, newNotAssigned) &&
         identical(second.variableInfo, newVariableInfo)) {
       return second;
     }
 
-    return new FlowModel<Variable, Type>._(
-      newReachable,
-      newNotAssigned,
-      newVariableInfo,
-    );
+    return new FlowModel<Variable, Type>._(newReachable, newVariableInfo);
   }
 
   /// Determines whether the given "variableInfo" maps are equivalent.
@@ -1199,13 +1105,9 @@
         if (p2Value != null) return false;
       } else {
         if (p2Value == null) return false;
-        Type p1Type = p1Value.promotedType;
-        Type p2Type = p2Value.promotedType;
-        if (p1Type == null) {
-          if (p2Type != null) return false;
-        } else {
-          if (p2Type == null) return false;
-          if (!typeOperations.isSameType(p1Type, p2Type)) return false;
+        if (!VariableModel._variableModelsEqual<Type>(
+            typeOperations, p1Value, p2Value)) {
+          return false;
         }
       }
     }
@@ -1259,12 +1161,16 @@
   /// is not promoted.
   final Type promotedType;
 
-  VariableModel(this.promotedType);
+  /// Indicates whether the variable has definitely been assigned.
+  final bool assigned;
+
+  VariableModel(this.promotedType, this.assigned);
 
   @override
   bool operator ==(Object other) {
     return other is VariableModel<Type> &&
-        this.promotedType == other.promotedType;
+        this.promotedType == other.promotedType &&
+        this.assigned == other.assigned;
   }
 
   /// Returns an updated model reflect a control path that is known to have
@@ -1274,27 +1180,31 @@
       VariableModel<Type> otherModel, bool unsafe) {
     Type thisType = promotedType;
     Type otherType = otherModel?.promotedType;
+    bool newAssigned = assigned || otherModel.assigned;
     if (!unsafe) {
       if (otherType != null &&
           (thisType == null ||
               typeOperations.isSubtypeOf(otherType, thisType))) {
-        return _identicalOrNew(this, otherModel, otherType);
+        return _identicalOrNew(this, otherModel, otherType, newAssigned);
       }
     }
-    if (thisType != null) {
-      return _identicalOrNew(this, otherModel, thisType);
-    } else {
-      return _identicalOrNew(this, otherModel, null);
-    }
+    return _identicalOrNew(this, otherModel, thisType, newAssigned);
   }
 
   @override
-  String toString() => 'VariableModel($promotedType)';
+  String toString() => 'VariableModel($promotedType, $assigned)';
 
   /// Returns a new [VariableModel] where the promoted type is replaced with
   /// [promotedType].
   VariableModel<Type> withPromotedType(Type promotedType) =>
-      new VariableModel<Type>(promotedType);
+      new VariableModel<Type>(promotedType, assigned);
+
+  /// Returns a new [VariableModel] reflecting the fact that the variable was
+  /// just written to.
+  VariableModel<Type> write() {
+    if (promotedType == null && assigned) return this;
+    return new VariableModel<Type>(null, true);
+  }
 
   /// Joins two variable models.  See [FlowModel.join] for details.
   static VariableModel<Type> join<Type>(
@@ -1315,115 +1225,39 @@
     } else {
       newPromotedType = null;
     }
-    return _identicalOrNew(first, second, newPromotedType);
+    bool newAssigned = first.assigned && second.assigned;
+    return _identicalOrNew(first, second, newPromotedType, newAssigned);
   }
 
   /// Creates a new [VariableModel] object, unless it is equivalent to either
   /// [first] or [second], in which case one of those objects is re-used.
   static VariableModel<Type> _identicalOrNew<Type>(VariableModel<Type> first,
-      VariableModel<Type> second, Type newPromotedType) {
-    if (identical(first.promotedType, newPromotedType)) {
+      VariableModel<Type> second, Type newPromotedType, bool newAssigned) {
+    if (identical(first.promotedType, newPromotedType) &&
+        first.assigned == newAssigned) {
       return first;
-    } else if (identical(second.promotedType, newPromotedType)) {
+    } else if (identical(second.promotedType, newPromotedType) &&
+        second.assigned == newAssigned) {
       return second;
     } else {
-      return new VariableModel<Type>(newPromotedType);
+      return new VariableModel<Type>(newPromotedType, newAssigned);
     }
   }
-}
 
-/// List based immutable set of variables.
-class _VariableSet<Variable> {
-  final List<Variable> variables;
-
-  _VariableSet._(this.variables);
-
-  _VariableSet<Variable> add(Variable addedVariable) {
-    if (contains(addedVariable)) {
-      return this;
+  /// Determines whether the given variable models are equivalent.
+  static bool _variableModelsEqual<Type>(
+      TypeOperations<Object, Type> typeOperations,
+      VariableModel<Type> model1,
+      VariableModel<Type> model2) {
+    Type p1Type = model1.promotedType;
+    Type p2Type = model2.promotedType;
+    if (p1Type == null) {
+      if (p2Type != null) return false;
+    } else {
+      if (p2Type == null) return false;
+      if (!typeOperations.isSameType(p1Type, p2Type)) return false;
     }
-
-    int length = variables.length;
-    List<Variable> newVariables = new List<Variable>(length + 1);
-    for (int i = 0; i < length; ++i) {
-      newVariables[i] = variables[i];
-    }
-    newVariables[length] = addedVariable;
-    return new _VariableSet._(newVariables);
-  }
-
-  _VariableSet<Variable> addAll(Iterable<Variable> variables) {
-    _VariableSet<Variable> result = this;
-    for (Variable variable in variables) {
-      result = result.add(variable);
-    }
-    return result;
-  }
-
-  bool contains(Variable variable) {
-    int length = variables.length;
-    for (int i = 0; i < length; ++i) {
-      if (identical(variables[i], variable)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  _VariableSet<Variable> intersect({
-    _VariableSet<Variable> empty,
-    _VariableSet<Variable> other,
-  }) {
-    if (identical(other, empty)) return empty;
-    if (identical(this, other)) return this;
-
-    // TODO(scheglov) optimize
-    List<Variable> newVariables =
-        variables.toSet().intersection(other.variables.toSet()).toList();
-
-    if (newVariables.isEmpty) return empty;
-    return new _VariableSet._(newVariables);
-  }
-
-  _VariableSet<Variable> remove(
-    _VariableSet<Variable> empty,
-    Variable removedVariable,
-  ) {
-    if (!contains(removedVariable)) {
-      return this;
-    }
-
-    int length = variables.length;
-    if (length == 1) {
-      return empty;
-    }
-
-    List<Variable> newVariables = new List<Variable>(length - 1);
-    int newIndex = 0;
-    for (int i = 0; i < length; ++i) {
-      Variable variable = variables[i];
-      if (!identical(variable, removedVariable)) {
-        newVariables[newIndex++] = variable;
-      }
-    }
-
-    return new _VariableSet._(newVariables);
-  }
-
-  @override
-  String toString() => variables.isEmpty ? '{}' : '{ ${variables.join(', ')} }';
-
-  _VariableSet<Variable> union(_VariableSet<Variable> other) {
-    if (other.variables.isEmpty) {
-      return this;
-    }
-
-    _VariableSet<Variable> result = this;
-    List<Variable> otherVariables = other.variables;
-    for (int i = 0; i < otherVariables.length; ++i) {
-      Variable otherVariable = otherVariables[i];
-      result = result.add(otherVariable);
-    }
-    return result;
+    if (model1.assigned != model2.assigned) return false;
+    return true;
   }
 }
diff --git a/pkg/front_end/test/fasta/flow_analysis/flow_analysis_test.dart b/pkg/front_end/test/fasta/flow_analysis/flow_analysis_test.dart
index 3a90834..29fafc1 100644
--- a/pkg/front_end/test/fasta/flow_analysis/flow_analysis_test.dart
+++ b/pkg/front_end/test/fasta/flow_analysis/flow_analysis_test.dart
@@ -551,7 +551,6 @@
   });
 
   group('State', () {
-    var emptySet = FlowModel<_Var, _Type>(true).notAssigned;
     var intVar = _Var('x', _Type('int'));
     var intQVar = _Var('x', _Type('int?'));
     var objectQVar = _Var('x', _Type('Object?'));
@@ -568,7 +567,6 @@
           var s = initial.setReachable(newReachability);
           expect(s, isNot(same(initial)));
           expect(s.reachable, newReachability);
-          expect(s.notAssigned, same(initial.notAssigned));
           expect(s.variableInfo, same(initial.variableInfo));
         }
 
@@ -582,24 +580,21 @@
         // By default, added variables are considered unassigned.
         var s1 = FlowModel<_Var, _Type>(true);
         var s2 = s1.add(intVar);
-        expect(s2.notAssigned.contains(intVar), true);
         expect(s2.reachable, true);
-        expect(s2.variableInfo, {intVar: VariableModel<_Type>(null)});
+        expect(s2.variableInfo, {intVar: VariableModel<_Type>(null, false)});
       });
 
       test('unassigned', () {
         var s1 = FlowModel<_Var, _Type>(true);
         var s2 = s1.add(intVar, assigned: false);
-        expect(s2.notAssigned.contains(intVar), true);
         expect(s2.reachable, true);
-        expect(s2.variableInfo, {intVar: VariableModel<_Type>(null)});
+        expect(s2.variableInfo, {intVar: VariableModel<_Type>(null, false)});
       });
 
       test('assigned', () {
         var s1 = FlowModel<_Var, _Type>(true);
         var s2 = s1.add(intVar, assigned: true);
-        expect(s2.notAssigned.contains(intVar), false);
-        expect(s2.variableInfo, {intVar: VariableModel<_Type>(null)});
+        expect(s2.variableInfo, {intVar: VariableModel<_Type>(null, true)});
       });
     });
 
@@ -630,10 +625,9 @@
         var s1 = FlowModel<_Var, _Type>(true).add(intQVar);
         var s2 = s1.promote(h, intQVar, _Type('int'));
         expect(s2.reachable, true);
-        expect(s2.notAssigned, same(s1.notAssigned));
         _Type.allowComparisons(() {
-          expect(
-              s2.variableInfo, {intQVar: VariableModel<_Type>(_Type('int'))});
+          expect(s2.variableInfo,
+              {intQVar: VariableModel<_Type>(_Type('int'), false)});
         });
       });
 
@@ -671,10 +665,9 @@
             .promote(h, objectQVar, _Type('int?'));
         var s2 = s1.promote(h, objectQVar, _Type('int'));
         expect(s2.reachable, true);
-        expect(s2.notAssigned, same(s1.notAssigned));
         _Type.allowComparisons(() {
           expect(s2.variableInfo,
-              {objectQVar: VariableModel<_Type>(_Type('int'))});
+              {objectQVar: VariableModel<_Type>(_Type('int'), false)});
         });
       });
     });
@@ -684,17 +677,16 @@
       test('unchanged', () {
         var h = _Harness();
         var s1 = FlowModel<_Var, _Type>(true).add(objectQVar, assigned: true);
-        var s2 = s1.write(h, emptySet, objectQVar);
+        var s2 = s1.write(h, objectQVar);
         expect(s2, same(s1));
       });
 
       test('marks as assigned', () {
         var h = _Harness();
         var s1 = FlowModel<_Var, _Type>(true).add(objectQVar, assigned: false);
-        var s2 = s1.write(h, emptySet, objectQVar);
+        var s2 = s1.write(h, objectQVar);
         expect(s2.reachable, true);
-        expect(s2.notAssigned.contains(objectQVar), false);
-        expect(s2.variableInfo, same(s1.variableInfo));
+        expect(s2.variableInfo[objectQVar], VariableModel<_Type>(null, true));
       });
 
       test('un-promotes', () {
@@ -703,10 +695,9 @@
             .add(objectQVar, assigned: true)
             .promote(h, objectQVar, _Type('int'));
         expect(s1.variableInfo, contains(objectQVar));
-        var s2 = s1.write(h, emptySet, objectQVar);
+        var s2 = s1.write(h, objectQVar);
         expect(s2.reachable, true);
-        expect(s2.notAssigned, same(s1.notAssigned));
-        expect(s2.variableInfo, {objectQVar: VariableModel<_Type>(null)});
+        expect(s2.variableInfo, {objectQVar: VariableModel<_Type>(null, true)});
       });
     });
 
@@ -723,8 +714,9 @@
         var s1 = FlowModel<_Var, _Type>(true).add(intQVar);
         var s2 = s1.markNonNullable(h, intQVar);
         expect(s2.reachable, true);
-        expect(s2.notAssigned, same(s1.notAssigned));
-        expect(s2.variableInfo[intQVar].promotedType.type, 'int');
+        _Type.allowComparisons(() {
+          expect(s2.variableInfo[intQVar], VariableModel(_Type('int'), false));
+        });
       });
 
       test('promoted -> unchanged', () {
@@ -743,10 +735,9 @@
             .promote(h, objectQVar, _Type('int?'));
         var s2 = s1.markNonNullable(h, objectQVar);
         expect(s2.reachable, true);
-        expect(s2.notAssigned, same(s1.notAssigned));
         _Type.allowComparisons(() {
           expect(s2.variableInfo,
-              {objectQVar: VariableModel<_Type>(_Type('int'))});
+              {objectQVar: VariableModel<_Type>(_Type('int'), false)});
         });
       });
     });
@@ -771,11 +762,10 @@
             .promote(h, intQVar, _Type('int'));
         var s2 = s1.removePromotedAll([intQVar], null);
         expect(s2.reachable, true);
-        expect(s2.notAssigned, same(s1.notAssigned));
         _Type.allowComparisons(() {
           expect(s2.variableInfo, {
-            objectQVar: VariableModel<_Type>(_Type('int')),
-            intQVar: VariableModel<_Type>(null)
+            objectQVar: VariableModel<_Type>(_Type('int'), false),
+            intQVar: VariableModel<_Type>(null, false)
           });
         });
       });
@@ -786,14 +776,10 @@
         var h = _Harness();
         var reachable = FlowModel<_Var, _Type>(true);
         var unreachable = reachable.setReachable(false);
-        expect(
-            reachable.restrict(h, emptySet, reachable, Set()), same(reachable));
-        expect(reachable.restrict(h, emptySet, unreachable, Set()),
-            same(unreachable));
-        expect(unreachable.restrict(h, emptySet, unreachable, Set()),
-            same(unreachable));
-        expect(unreachable.restrict(h, emptySet, unreachable, Set()),
-            same(unreachable));
+        expect(reachable.restrict(h, reachable, Set()), same(reachable));
+        expect(reachable.restrict(h, unreachable, Set()), same(unreachable));
+        expect(unreachable.restrict(h, unreachable, Set()), same(unreachable));
+        expect(unreachable.restrict(h, unreachable, Set()), same(unreachable));
       });
 
       test('assignments', () {
@@ -803,13 +789,13 @@
         var c = _Var('c', _Type('int'));
         var d = _Var('d', _Type('int'));
         var s0 = FlowModel<_Var, _Type>(true).add(a).add(b).add(c).add(d);
-        var s1 = s0.write(h, emptySet, a).write(h, emptySet, b);
-        var s2 = s0.write(h, emptySet, a).write(h, emptySet, c);
-        var result = s1.restrict(h, emptySet, s2, Set());
-        expect(result.notAssigned.contains(a), false);
-        expect(result.notAssigned.contains(b), false);
-        expect(result.notAssigned.contains(c), false);
-        expect(result.notAssigned.contains(d), true);
+        var s1 = s0.write(h, a).write(h, b);
+        var s2 = s0.write(h, a).write(h, c);
+        var result = s1.restrict(h, s2, Set());
+        expect(result.variableInfo[a].assigned, true);
+        expect(result.variableInfo[b].assigned, true);
+        expect(result.variableInfo[c].assigned, true);
+        expect(result.variableInfo[d].assigned, false);
       });
 
       test('promotion', () {
@@ -820,8 +806,7 @@
           var s0 = FlowModel<_Var, _Type>(true).add(x, assigned: true);
           var s1 = thisType == null ? s0 : s0.promote(h, x, _Type(thisType));
           var s2 = otherType == null ? s0 : s0.promote(h, x, _Type(otherType));
-          var result =
-              s1.restrict(h, emptySet, s2, unsafe ? [x].toSet() : Set());
+          var result = s1.restrict(h, s2, unsafe ? [x].toSet() : Set());
           if (expectedType == null) {
             expect(result.variableInfo, contains(x));
             expect(result.variableInfo[x].promotedType, isNull);
@@ -849,10 +834,10 @@
         var x = _Var('x', _Type('Object?'));
         var s0 = FlowModel<_Var, _Type>(true);
         var s1 = s0.add(x, assigned: true);
-        expect(s0.restrict(h, emptySet, s1, {}), same(s0));
-        expect(s0.restrict(h, emptySet, s1, {x}), same(s0));
-        expect(s1.restrict(h, emptySet, s0, {}), same(s1));
-        expect(s1.restrict(h, emptySet, s0, {x}), same(s1));
+        expect(s0.restrict(h, s1, {}), same(s0));
+        expect(s0.restrict(h, s1, {x}), same(s0));
+        expect(s1.restrict(h, s0, {}), same(s1));
+        expect(s1.restrict(h, s0, {x}), same(s1));
       });
     });
   });
@@ -865,7 +850,7 @@
     var stringType = _Type('String');
     const emptyMap = <Null, VariableModel<Null>>{};
 
-    VariableModel<_Type> model(_Type type) => VariableModel<_Type>(type);
+    VariableModel<_Type> model(_Type type) => VariableModel<_Type>(type, true);
 
     group('without input reuse', () {
       test('promoted with unpromoted', () {
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt
index 050c481..f94657b 100644
--- a/pkg/front_end/test/spell_checking_list_common.txt
+++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -865,6 +865,7 @@
 encoder
 encodes
 encoding
+encounter
 encountered
 encounters
 end
@@ -2178,6 +2179,7 @@
 refers
 refine
 refinement
+reflecting
 regardless
 region
 regions
@@ -2201,6 +2203,7 @@
 relative
 release
 relevant
+relied
 relies
 reloads
 rely