Move "mini-AST" type operations to their own class.

This will allow them to be re-used in other _fe_analyzer_shared test
files.

Change-Id: Ib9c321ba8985241f2ad2c1783e9896f3f1e57408
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/264960
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
index 9f0f424..590d082 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
@@ -5,7 +5,6 @@
 import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
 import 'package:_fe_analyzer_shared/src/type_inference/promotion_key_store.dart';
 import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart';
-import 'package:_fe_analyzer_shared/src/type_inference/type_operations.dart';
 
 import '../mini_ast.dart';
 import '../mini_ir.dart';
@@ -30,7 +29,7 @@
   final PromotionKeyStore<Var> promotionKeyStore = PromotionKeyStore();
 
   @override
-  TypeOperations<Type> get typeOperations => this;
+  Operations<Var, Type> get typeOperations => typeAnalyzer.typeOperations;
 }
 
 /// Helper class allowing tests to examine the values of variables' SSA nodes.
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 e6b1c35..b7c28c3 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
@@ -588,7 +588,7 @@
       var e = expr('Null');
       var s = if_(e, []);
       var flow = FlowAnalysis<Node, Statement, Expression, Var, Type>(
-          h, AssignedVariables<Node, Var>(),
+          h.typeOperations, AssignedVariables<Node, Var>(),
           respectImplicitlyTypedVarInitializers: true);
       flow.ifStatement_conditionBegin();
       flow.ifStatement_thenBegin(e, s);
@@ -3619,7 +3619,7 @@
         // This should not happen in valid code, but test that we don't crash.
 
         var s = FlowModel<Type>(Reachability.initial)._write(
-            h, null, objectQVar, Type('Object?'), new SsaNode<Type>(null), h);
+            h, null, objectQVar, Type('Object?'), new SsaNode<Type>(null));
         expect(s.variableInfo[objectQVar], isNull);
       });
 
@@ -3627,7 +3627,7 @@
         var s1 =
             FlowModel<Type>(Reachability.initial)._declare(h, objectQVar, true);
         var s2 = s1._write(
-            h, null, objectQVar, Type('Object?'), new SsaNode<Type>(null), h);
+            h, null, objectQVar, Type('Object?'), new SsaNode<Type>(null));
         expect(s2, isNot(same(s1)));
         expect(s2.reachable, same(s1.reachable));
         expect(
@@ -3643,7 +3643,7 @@
         var s1 = FlowModel<Type>(Reachability.initial)
             ._declare(h, objectQVar, false);
         var s2 = s1._write(
-            h, null, objectQVar, Type('int?'), new SsaNode<Type>(null), h);
+            h, null, objectQVar, Type('int?'), new SsaNode<Type>(null));
         expect(s2.reachable.overallReachable, true);
         expect(
             s2._infoFor(h, objectQVar),
@@ -3662,7 +3662,7 @@
         expect(s1.variableInfo,
             contains(h.promotionKeyStore.keyForVariable(objectQVar)));
         var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
-            Type('int?'), new SsaNode<Type>(null), h);
+            Type('int?'), new SsaNode<Type>(null));
         expect(s2.reachable.overallReachable, true);
         expect(s2.variableInfo, {
           h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
@@ -3688,7 +3688,7 @@
               unassigned: false)
         });
         var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
-            Type('num'), new SsaNode<Type>(null), h);
+            Type('num'), new SsaNode<Type>(null));
         expect(s2.reachable.overallReachable, true);
         expect(s2.variableInfo, {
           h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
@@ -3716,7 +3716,7 @@
               unassigned: false)
         });
         var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
-            Type('num'), new SsaNode<Type>(null), h);
+            Type('num'), new SsaNode<Type>(null));
         expect(s2.reachable.overallReachable, true);
         expect(s2.variableInfo, {
           h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
@@ -3742,7 +3742,7 @@
               unassigned: false)
         });
         var s2 = s1._write(
-            h, null, objectQVar, Type('num'), new SsaNode<Type>(null), h);
+            h, null, objectQVar, Type('num'), new SsaNode<Type>(null));
         expect(s2.reachable.overallReachable, true);
         expect(s2.variableInfo, isNot(same(s1.variableInfo)));
         expect(s2.variableInfo, {
@@ -3769,7 +3769,7 @@
               unassigned: false)
         });
         var s2 = s1._write(
-            h, null, objectQVar, Type('int'), new SsaNode<Type>(null), h);
+            h, null, objectQVar, Type('int'), new SsaNode<Type>(null));
         expect(s2.reachable.overallReachable, true);
         expect(s2.variableInfo, isNot(same(s1.variableInfo)));
         expect(s2.variableInfo, {
@@ -3791,8 +3791,7 @@
                 _matchVariableModel(chain: null),
           });
 
-          var s2 =
-              s1._write(h, null, x, Type('int'), new SsaNode<Type>(null), h);
+          var s2 = s1._write(h, null, x, Type('int'), new SsaNode<Type>(null));
           expect(s2.variableInfo, {
             h.promotionKeyStore.keyForVariable(x):
                 _matchVariableModel(chain: ['int']),
@@ -3815,8 +3814,7 @@
           });
 
           // 'x' is write-captured, so not promoted
-          var s3 =
-              s2._write(h, null, x, Type('int'), new SsaNode<Type>(null), h);
+          var s3 = s2._write(h, null, x, Type('int'), new SsaNode<Type>(null));
           expect(s3.variableInfo, {
             h.promotionKeyStore.keyForVariable(x):
                 _matchVariableModel(chain: null, writeCaptured: true),
@@ -3835,7 +3833,7 @@
             ),
           });
           var s2 = s1._write(
-              h, null, objectQVar, Type('int'), new SsaNode<Type>(null), h);
+              h, null, objectQVar, Type('int'), new SsaNode<Type>(null));
           expect(s2.variableInfo, {
             h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
               chain: ['int?', 'int'],
@@ -3856,7 +3854,7 @@
             ),
           });
           var s2 = s1._write(
-              h, null, objectQVar, Type('int'), new SsaNode<Type>(null), h);
+              h, null, objectQVar, Type('int'), new SsaNode<Type>(null));
           expect(s2.variableInfo, {
             h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
               chain: ['Object', 'int'],
@@ -3878,7 +3876,7 @@
           ),
         });
         var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
-            Type('num?'), new SsaNode<Type>(null), h);
+            Type('num?'), new SsaNode<Type>(null));
         expect(s2.variableInfo, {
           h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
             chain: ['num?'],
@@ -3901,7 +3899,7 @@
           ),
         });
         var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
-            Type('int?'), new SsaNode<Type>(null), h);
+            Type('int?'), new SsaNode<Type>(null));
         expect(s2.variableInfo, {
           h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
             chain: ['num?', 'int?'],
@@ -3966,8 +3964,7 @@
               ),
             });
 
-            var s2 =
-                s1._write(h, null, x, Type('C'), new SsaNode<Type>(null), h);
+            var s2 = s1._write(h, null, x, Type('C'), new SsaNode<Type>(null));
             expect(s2.variableInfo, {
               h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
                 chain: ['Object', 'B'],
@@ -3992,8 +3989,7 @@
               ),
             });
 
-            var s2 =
-                s1._write(h, null, x, Type('C'), new SsaNode<Type>(null), h);
+            var s2 = s1._write(h, null, x, Type('C'), new SsaNode<Type>(null));
             expect(s2.variableInfo, {
               h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
                 chain: ['Object', 'B'],
@@ -4018,8 +4014,7 @@
               ),
             });
 
-            var s2 =
-                s1._write(h, null, x, Type('B'), new SsaNode<Type>(null), h);
+            var s2 = s1._write(h, null, x, Type('B'), new SsaNode<Type>(null));
             expect(s2.variableInfo, {
               h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
                 chain: ['Object', 'A'],
@@ -4045,7 +4040,7 @@
               ),
             });
             var s2 = s1._write(
-                h, null, objectQVar, Type('int'), new SsaNode<Type>(null), h);
+                h, null, objectQVar, Type('int'), new SsaNode<Type>(null));
             // It's ambiguous whether to promote to num? or num*, so we don't
             // promote.
             expect(s2, isNot(same(s1)));
@@ -4073,7 +4068,7 @@
             ),
           });
           var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
-              Type('num?'), new SsaNode<Type>(null), h);
+              Type('num?'), new SsaNode<Type>(null));
           // It's ambiguous whether to promote to num? or num*, but since the
           // written type is exactly num?, we use that.
           expect(s2.variableInfo, {
@@ -4104,7 +4099,7 @@
         });
 
         var s2 = s1._write(h, _MockNonPromotionReason(), x, Type('double'),
-            new SsaNode<Type>(null), h);
+            new SsaNode<Type>(null));
         expect(s2.variableInfo, {
           h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
             chain: ['num?', 'num'],
@@ -4234,14 +4229,21 @@
       test('reachability', () {
         var reachable = FlowModel<Type>(Reachability.initial);
         var unreachable = reachable.setUnreachable();
-        expect(reachable.rebaseForward(h, reachable), same(reachable));
-        expect(reachable.rebaseForward(h, unreachable), same(unreachable));
+        expect(reachable.rebaseForward(h.typeOperations, reachable),
+            same(reachable));
+        expect(reachable.rebaseForward(h.typeOperations, unreachable),
+            same(unreachable));
         expect(
-            unreachable.rebaseForward(h, reachable).reachable.overallReachable,
+            unreachable
+                .rebaseForward(h.typeOperations, reachable)
+                .reachable
+                .overallReachable,
             false);
-        expect(unreachable.rebaseForward(h, reachable).variableInfo,
+        expect(
+            unreachable.rebaseForward(h.typeOperations, reachable).variableInfo,
             same(unreachable.variableInfo));
-        expect(unreachable.rebaseForward(h, unreachable), same(unreachable));
+        expect(unreachable.rebaseForward(h.typeOperations, unreachable),
+            same(unreachable));
       });
 
       test('assignments', () {
@@ -4255,12 +4257,12 @@
             ._declare(h, c, false)
             ._declare(h, d, false);
         var s1 = s0
-            ._write(h, null, a, Type('int'), new SsaNode<Type>(null), h)
-            ._write(h, null, b, Type('int'), new SsaNode<Type>(null), h);
+            ._write(h, null, a, Type('int'), new SsaNode<Type>(null))
+            ._write(h, null, b, Type('int'), new SsaNode<Type>(null));
         var s2 = s0
-            ._write(h, null, a, Type('int'), new SsaNode<Type>(null), h)
-            ._write(h, null, c, Type('int'), new SsaNode<Type>(null), h);
-        var result = s1.rebaseForward(h, s2);
+            ._write(h, null, a, Type('int'), new SsaNode<Type>(null))
+            ._write(h, null, c, Type('int'), new SsaNode<Type>(null));
+        var result = s1.rebaseForward(h.typeOperations, s2);
         expect(result._infoFor(h, a).assigned, true);
         expect(result._infoFor(h, b).assigned, true);
         expect(result._infoFor(h, c).assigned, true);
@@ -4280,7 +4282,7 @@
         // In s1, a and b are write captured.  In s2, a and c are.
         var s1 = s0._conservativeJoin(h, [a, b], [a, b]);
         var s2 = s1._conservativeJoin(h, [a, c], [a, c]);
-        var result = s1.rebaseForward(h, s2);
+        var result = s1.rebaseForward(h.typeOperations, s2);
         expect(
           result._infoFor(h, a),
           _matchVariableModel(writeCaptured: true, unassigned: false),
@@ -4306,11 +4308,11 @@
         var s1 = s0._conservativeJoin(h, [a], [a]);
         var s2 = s0._tryPromoteForTypeCheck(h, a, 'int').ifTrue;
         expect(
-          s1.rebaseForward(h, s2)._infoFor(h, a),
+          s1.rebaseForward(h.typeOperations, s2)._infoFor(h, a),
           _matchVariableModel(writeCaptured: true, chain: isNull),
         );
         expect(
-          s2.rebaseForward(h, s1)._infoFor(h, a),
+          s2.rebaseForward(h.typeOperations, s1)._infoFor(h, a),
           _matchVariableModel(writeCaptured: true, chain: isNull),
         );
       });
@@ -4322,8 +4324,8 @@
           var s0 = FlowModel<Type>(Reachability.initial)._declare(h, x, true);
           var s1 = s0;
           if (unsafe) {
-            s1 = s1._write(
-                h, null, x, Type('Object?'), new SsaNode<Type>(null), h);
+            s1 =
+                s1._write(h, null, x, Type('Object?'), new SsaNode<Type>(null));
           }
           if (thisType != null) {
             s1 = s1._tryPromoteForTypeCheck(h, x, thisType).ifTrue;
@@ -4331,7 +4333,7 @@
           var s2 = otherType == null
               ? s0
               : s0._tryPromoteForTypeCheck(h, x, otherType).ifTrue;
-          var result = s2.rebaseForward(h, s1);
+          var result = s2.rebaseForward(h.typeOperations, s1);
           if (expectedChain == null) {
             expect(result.variableInfo,
                 contains(h.promotionKeyStore.keyForVariable(x)));
@@ -4400,7 +4402,7 @@
           var expectedFinallyChain = before.toList()..addAll(inFinally);
           _checkChain(
               finallyModel._infoFor(h, x).promotedTypes, expectedFinallyChain);
-          var result = tryModel.rebaseForward(h, finallyModel);
+          var result = tryModel.rebaseForward(h.typeOperations, finallyModel);
           _checkChain(result._infoFor(h, x).promotedTypes, expectedResult);
           // And verify that the inputs are unchanged.
           _checkChain(initialModel._infoFor(h, x).promotedTypes, before);
@@ -4436,11 +4438,11 @@
         var s1 = s0._tryPromoteForTypeCheck(h, a, 'int').ifFalse;
         var s2 = s0._tryPromoteForTypeCheck(h, a, 'String').ifFalse;
         expect(
-          s1.rebaseForward(h, s2)._infoFor(h, a),
+          s1.rebaseForward(h.typeOperations, s2)._infoFor(h, a),
           _matchVariableModel(ofInterest: ['int', 'String']),
         );
         expect(
-          s2.rebaseForward(h, s1)._infoFor(h, a),
+          s2.rebaseForward(h.typeOperations, s1)._infoFor(h, a),
           _matchVariableModel(ofInterest: ['int', 'String']),
         );
       });
@@ -4449,8 +4451,8 @@
         var x = Var('x')..type = Type('Object?');
         var s0 = FlowModel<Type>(Reachability.initial);
         var s1 = s0._declare(h, x, true);
-        expect(s1.rebaseForward(h, s0), same(s0));
-        expect(s0.rebaseForward(h, s1), same(s1));
+        expect(s1.rebaseForward(h.typeOperations, s0), same(s0));
+        expect(s0.rebaseForward(h.typeOperations, s1), same(s1));
       });
     });
   });
@@ -4462,34 +4464,45 @@
     var objectType = Type('Object');
 
     test('should handle nulls', () {
-      expect(VariableModel.joinPromotedTypes(null, null, h), null);
-      expect(VariableModel.joinPromotedTypes(null, [intType], h), null);
-      expect(VariableModel.joinPromotedTypes([intType], null, h), null);
+      expect(
+          VariableModel.joinPromotedTypes(null, null, h.typeOperations), null);
+      expect(VariableModel.joinPromotedTypes(null, [intType], h.typeOperations),
+          null);
+      expect(VariableModel.joinPromotedTypes([intType], null, h.typeOperations),
+          null);
     });
 
     test('should return null if there are no common types', () {
-      expect(VariableModel.joinPromotedTypes([intType], [doubleType], h), null);
+      expect(
+          VariableModel.joinPromotedTypes(
+              [intType], [doubleType], h.typeOperations),
+          null);
     });
 
     test('should return common prefix if there are common types', () {
       expect(
-          VariableModel.joinPromotedTypes(
-              [objectType, intType], [objectType, doubleType], h),
+          VariableModel.joinPromotedTypes([objectType, intType],
+              [objectType, doubleType], h.typeOperations),
           _matchPromotionChain(['Object']));
       expect(
           VariableModel.joinPromotedTypes([objectType, numType, intType],
-              [objectType, numType, doubleType], h),
+              [objectType, numType, doubleType], h.typeOperations),
           _matchPromotionChain(['Object', 'num']));
     });
 
     test('should return an input if it is a prefix of the other', () {
       var prefix = [objectType, numType];
       var largerChain = [objectType, numType, intType];
-      expect(VariableModel.joinPromotedTypes(prefix, largerChain, h),
+      expect(
+          VariableModel.joinPromotedTypes(
+              prefix, largerChain, h.typeOperations),
           same(prefix));
-      expect(VariableModel.joinPromotedTypes(largerChain, prefix, h),
+      expect(
+          VariableModel.joinPromotedTypes(
+              largerChain, prefix, h.typeOperations),
           same(prefix));
-      expect(VariableModel.joinPromotedTypes(prefix, prefix, h), same(prefix));
+      expect(VariableModel.joinPromotedTypes(prefix, prefix, h.typeOperations),
+          same(prefix));
     });
 
     test('should intersect', () {
@@ -4519,12 +4532,12 @@
 
       void check(List<Type> chain1, List<Type> chain2, Matcher matcher) {
         expect(
-          VariableModel.joinPromotedTypes(chain1, chain2, h),
+          VariableModel.joinPromotedTypes(chain1, chain2, h.typeOperations),
           matcher,
         );
 
         expect(
-          VariableModel.joinPromotedTypes(chain2, chain1, h),
+          VariableModel.joinPromotedTypes(chain2, chain1, h.typeOperations),
           matcher,
         );
       }
@@ -4575,24 +4588,24 @@
       var s1 = _makeTypes(['double', 'int']);
       var s2 = _makeTypes(['double', 'int', 'bool']);
       var expected = _matchOfInterestSet(['double', 'int', 'bool']);
-      expect(VariableModel.joinTested(s1, s2, h), expected);
-      expect(VariableModel.joinTested(s2, s1, h), expected);
+      expect(VariableModel.joinTested(s1, s2, h.typeOperations), expected);
+      expect(VariableModel.joinTested(s2, s1, h.typeOperations), expected);
     });
 
     test('common prefix', () {
       var s1 = _makeTypes(['double', 'int', 'String']);
       var s2 = _makeTypes(['double', 'int', 'bool']);
       var expected = _matchOfInterestSet(['double', 'int', 'String', 'bool']);
-      expect(VariableModel.joinTested(s1, s2, h), expected);
-      expect(VariableModel.joinTested(s2, s1, h), expected);
+      expect(VariableModel.joinTested(s1, s2, h.typeOperations), expected);
+      expect(VariableModel.joinTested(s2, s1, h.typeOperations), expected);
     });
 
     test('order mismatch', () {
       var s1 = _makeTypes(['double', 'int']);
       var s2 = _makeTypes(['int', 'double']);
       var expected = _matchOfInterestSet(['double', 'int']);
-      expect(VariableModel.joinTested(s1, s2, h), expected);
-      expect(VariableModel.joinTested(s2, s1, h), expected);
+      expect(VariableModel.joinTested(s1, s2, h.typeOperations), expected);
+      expect(VariableModel.joinTested(s2, s1, h.typeOperations), expected);
     });
 
     test('small common prefix', () {
@@ -4600,8 +4613,8 @@
       var s2 = _makeTypes(['int', 'List', 'bool', 'Future']);
       var expected = _matchOfInterestSet(
           ['int', 'double', 'String', 'bool', 'List', 'Future']);
-      expect(VariableModel.joinTested(s1, s2, h), expected);
-      expect(VariableModel.joinTested(s2, s1, h), expected);
+      expect(VariableModel.joinTested(s1, s2, h.typeOperations), expected);
+      expect(VariableModel.joinTested(s2, s1, h.typeOperations), expected);
     });
   });
 
@@ -4641,7 +4654,7 @@
           x: model(null),
           y: model([intType])
         };
-        expect(FlowModel.joinVariableInfo(h, p1, p2, emptyMap), {
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap), {
           x: _matchVariableModel(chain: null, ofInterest: ['int']),
           y: _matchVariableModel(chain: null, ofInterest: ['int'])
         });
@@ -4653,7 +4666,8 @@
           x: model([intType]),
           y: model([stringType])
         };
-        expect(FlowModel.joinVariableInfo(h, p, p, emptyMap), same(p));
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p, p, emptyMap),
+            same(p));
       });
 
       test('one input empty', () {
@@ -4662,8 +4676,10 @@
           y: model([stringType])
         };
         var p2 = <int, VariableModel<Type>>{};
-        expect(FlowModel.joinVariableInfo(h, p1, p2, emptyMap), same(emptyMap));
-        expect(FlowModel.joinVariableInfo(h, p2, p1, emptyMap), same(emptyMap));
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap),
+            same(emptyMap));
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p2, p1, emptyMap),
+            same(emptyMap));
       });
 
       test('promoted with unpromoted', () {
@@ -4674,8 +4690,10 @@
         var expected = {
           x: _matchVariableModel(chain: null, ofInterest: ['int'])
         };
-        expect(FlowModel.joinVariableInfo(h, p1, p2, emptyMap), expected);
-        expect(FlowModel.joinVariableInfo(h, p2, p1, emptyMap), expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap),
+            expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p2, p1, emptyMap),
+            expected);
       });
 
       test('related type chains', () {
@@ -4688,8 +4706,10 @@
         var expected = {
           x: _matchVariableModel(chain: ['int?'], ofInterest: ['int?', 'int'])
         };
-        expect(FlowModel.joinVariableInfo(h, p1, p2, emptyMap), expected);
-        expect(FlowModel.joinVariableInfo(h, p2, p1, emptyMap), expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap),
+            expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p2, p1, emptyMap),
+            expected);
       });
 
       test('unrelated type chains', () {
@@ -4702,8 +4722,10 @@
         var expected = {
           x: _matchVariableModel(chain: null, ofInterest: ['String', 'int'])
         };
-        expect(FlowModel.joinVariableInfo(h, p1, p2, emptyMap), expected);
-        expect(FlowModel.joinVariableInfo(h, p2, p1, emptyMap), expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap),
+            expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p2, p1, emptyMap),
+            expected);
       });
 
       test('sub-map', () {
@@ -4713,8 +4735,10 @@
           y: model([stringType])
         };
         var p2 = {x: xModel};
-        expect(FlowModel.joinVariableInfo(h, p1, p2, emptyMap), same(p2));
-        expect(FlowModel.joinVariableInfo(h, p2, p1, emptyMap), same(p2));
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap),
+            same(p2));
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p2, p1, emptyMap),
+            same(p2));
       });
 
       test('sub-map with matched subtype', () {
@@ -4728,8 +4752,10 @@
         var expected = {
           x: _matchVariableModel(chain: ['int?'], ofInterest: ['int?', 'int'])
         };
-        expect(FlowModel.joinVariableInfo(h, p1, p2, emptyMap), expected);
-        expect(FlowModel.joinVariableInfo(h, p2, p1, emptyMap), expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap),
+            expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p2, p1, emptyMap),
+            expected);
       });
 
       test('sub-map with mismatched subtype', () {
@@ -4743,8 +4769,10 @@
         var expected = {
           x: _matchVariableModel(chain: ['int?'], ofInterest: ['int?', 'int'])
         };
-        expect(FlowModel.joinVariableInfo(h, p1, p2, emptyMap), expected);
-        expect(FlowModel.joinVariableInfo(h, p2, p1, emptyMap), expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap),
+            expected);
+        expect(FlowModel.joinVariableInfo(h.typeOperations, p2, p1, emptyMap),
+            expected);
       });
 
       test('assigned', () {
@@ -4752,7 +4780,8 @@
         var assigned = model(null, assigned: true);
         var p1 = {x: assigned, y: assigned, z: unassigned, w: unassigned};
         var p2 = {x: assigned, y: unassigned, z: assigned, w: unassigned};
-        var joined = FlowModel.joinVariableInfo(h, p1, p2, emptyMap);
+        var joined =
+            FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap);
         expect(joined, {
           x: same(assigned),
           y: _matchVariableModel(
@@ -4778,7 +4807,8 @@
           z: writeCapturedModel,
           w: intQModel
         };
-        var joined = FlowModel.joinVariableInfo(h, p1, p2, emptyMap);
+        var joined =
+            FlowModel.joinVariableInfo(h.typeOperations, p1, p2, emptyMap);
         expect(joined, {
           x: same(writeCapturedModel),
           y: same(writeCapturedModel),
@@ -4810,7 +4840,7 @@
 
     test('first is null', () {
       var s1 = FlowModel.withInfo(Reachability.initial.split(), emptyMap);
-      var result = FlowModel.merge(h, null, s1, emptyMap);
+      var result = FlowModel.merge(h.typeOperations, null, s1, emptyMap);
       expect(result.reachable, same(Reachability.initial));
     });
 
@@ -4818,7 +4848,7 @@
       var splitPoint = Reachability.initial.split();
       var afterSplit = splitPoint.split();
       var s1 = FlowModel.withInfo(afterSplit, emptyMap);
-      var result = FlowModel.merge(h, s1, null, emptyMap);
+      var result = FlowModel.merge(h.typeOperations, s1, null, emptyMap);
       expect(result.reachable, same(splitPoint));
     });
 
@@ -4831,7 +4861,7 @@
       var s2 = FlowModel.withInfo(afterSplit, {
         x: varModel([stringType])
       });
-      var result = FlowModel.merge(h, s1, s2, emptyMap);
+      var result = FlowModel.merge(h.typeOperations, s1, s2, emptyMap);
       expect(result.reachable, same(splitPoint));
       expect(result.variableInfo[x]!.promotedTypes, isNull);
     });
@@ -4845,7 +4875,7 @@
       var s2 = FlowModel.withInfo(afterSplit, {
         x: varModel([stringType])
       });
-      var result = FlowModel.merge(h, s1, s2, emptyMap);
+      var result = FlowModel.merge(h.typeOperations, s1, s2, emptyMap);
       expect(result.reachable, same(splitPoint));
       expect(result.variableInfo, same(s2.variableInfo));
     });
@@ -4859,7 +4889,7 @@
       var s2 = FlowModel.withInfo(afterSplit.setUnreachable(), {
         x: varModel([stringType])
       });
-      var result = FlowModel.merge(h, s1, s2, emptyMap);
+      var result = FlowModel.merge(h.typeOperations, s1, s2, emptyMap);
       expect(result.reachable, same(splitPoint));
       expect(result.variableInfo, same(s1.variableInfo));
     });
@@ -4873,7 +4903,7 @@
       var s2 = FlowModel.withInfo(afterSplit.setUnreachable(), {
         x: varModel([stringType])
       });
-      var result = FlowModel.merge(h, s1, s2, emptyMap);
+      var result = FlowModel.merge(h.typeOperations, s1, s2, emptyMap);
       expect(result.reachable.locallyReachable, false);
       expect(result.reachable.parent, same(splitPoint.parent));
       expect(result.variableInfo[x]!.promotedTypes, isNull);
@@ -4905,7 +4935,7 @@
       var m2 = FlowModel.withInfo(Reachability.initial, {
         x: model([stringType])
       });
-      expect(m1.inheritTested(h, m2).variableInfo[x]!.tested,
+      expect(m1.inheritTested(h.typeOperations, m2).variableInfo[x]!.tested,
           _matchOfInterestSet(['int', 'String']));
     });
 
@@ -4914,7 +4944,7 @@
         x: model([intType])
       });
       var m2 = FlowModel.withInfo(Reachability.initial, emptyMap);
-      expect(m1.inheritTested(h, m2), same(m1));
+      expect(m1.inheritTested(h.typeOperations, m2), same(m1));
     });
 
     test('returns identical model when no changes', () {
@@ -4924,7 +4954,7 @@
       var m2 = FlowModel.withInfo(Reachability.initial, {
         x: model([intType])
       });
-      expect(m1.inheritTested(h, m2), same(m1));
+      expect(m1.inheritTested(h.typeOperations, m2), same(m1));
     });
   });
 
@@ -6449,8 +6479,7 @@
           NonPromotionReason? nonPromotionReason,
           Var variable,
           Type writtenType,
-          SsaNode<Type> newSsaNode,
-          Operations<Var, Type> operations) =>
+          SsaNode<Type> newSsaNode) =>
       write(
           h,
           nonPromotionReason,
@@ -6458,5 +6487,5 @@
           h.promotionKeyStore.keyForVariable(variable),
           writtenType,
           newSsaNode,
-          operations);
+          h.typeOperations);
 }
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index 60004b9..00781d4 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -441,7 +441,172 @@
   }
 }
 
-class Harness
+class Harness {
+  final MiniAstOperations _operations = MiniAstOperations();
+
+  bool _started = false;
+
+  late final FlowAnalysis<Node, Statement, Expression, Var, Type> flow;
+
+  bool? _patternsEnabled;
+
+  Type? _thisType;
+
+  final Map<String, _PropertyElement> _members = {};
+
+  late final typeAnalyzer = _MiniAstTypeAnalyzer(
+      this,
+      TypeAnalyzerOptions(
+          nullSafetyEnabled: !_operations.legacy,
+          patternsEnabled: patternsEnabled));
+
+  /// Indicates whether initializers of implicitly typed variables should be
+  /// accounted for by SSA analysis.  (In an ideal world, they always would be,
+  /// but due to https://github.com/dart-lang/language/issues/1785, they weren't
+  /// always, and we need to be able to replicate the old behavior when
+  /// analyzing old language versions).
+  bool _respectImplicitlyTypedVarInitializers = true;
+
+  MiniIrBuilder get irBuilder => typeAnalyzer._irBuilder;
+
+  set legacy(bool value) {
+    assert(!_started);
+    _operations.legacy = value;
+  }
+
+  bool get patternsEnabled => _patternsEnabled ?? !_operations.legacy;
+
+  set patternsEnabled(bool value) {
+    assert(!_started);
+    _patternsEnabled = value;
+  }
+
+  set respectImplicitlyTypedVarInitializers(bool value) {
+    assert(!_started);
+    _respectImplicitlyTypedVarInitializers = value;
+  }
+
+  set thisType(String type) {
+    assert(!_started);
+    _thisType = Type(type);
+  }
+
+  /// Updates the harness so that when a [factor] query is invoked on types
+  /// [from] and [what], [result] will be returned.
+  void addFactor(String from, String what, String result) {
+    _operations.addFactor(from, what, result);
+  }
+
+  /// Updates the harness so that when member [memberName] is looked up on type
+  /// [targetType], a member is found having the given [type].
+  void addMember(String targetType, String memberName, String type,
+      {bool promotable = false}) {
+    var query = '$targetType.$memberName';
+    var member = _PropertyElement(Type(type));
+    _members[query] = member;
+    if (promotable) {
+      _operations.promotableFields.add(member);
+    }
+  }
+
+  void addPromotionException(String from, String to, String result) {
+    _operations.addPromotionException(from, to, result);
+  }
+
+  /// Updates the harness so that when an [isSubtypeOf] query is invoked on
+  /// types [leftType] and [rightType], [isSubtype] will be returned.
+  void addSubtype(String leftType, String rightType, bool isSubtype) {
+    _operations.addSubtype(leftType, rightType, isSubtype);
+  }
+
+  /// Attempts to look up a member named [memberName] in the given [type].  If
+  /// a member is found, returns its [_PropertyElement] object.  Otherwise the
+  /// test fails.
+  _PropertyElement getMember(Type type, String memberName) {
+    var query = '$type.$memberName';
+    return _members[query] ?? fail('Unknown member query: $query');
+  }
+
+  /// Runs the given [statements] through flow analysis, checking any assertions
+  /// they contain.
+  void run(List<Statement> statements,
+      {bool errorRecoveryOk = false, Set<String> expectedErrors = const {}}) {
+    _started = true;
+    if (_operations.legacy && patternsEnabled) {
+      fail('Patterns cannot be enabled in legacy mode');
+    }
+    var visitor = PreVisitor(typeAnalyzer.errors);
+    var b = _Block(statements, location: computeLocation());
+    b.preVisit(visitor);
+    flow = _operations.legacy
+        ? FlowAnalysis<Node, Statement, Expression, Var, Type>.legacy(
+            _operations, visitor._assignedVariables)
+        : FlowAnalysis<Node, Statement, Expression, Var, Type>(
+            _operations, visitor._assignedVariables,
+            respectImplicitlyTypedVarInitializers:
+                _respectImplicitlyTypedVarInitializers);
+    typeAnalyzer.dispatchStatement(b);
+    typeAnalyzer.finish();
+    expect(typeAnalyzer.errors._accumulatedErrors, expectedErrors);
+    var assertInErrorRecoveryStack =
+        typeAnalyzer.errors._assertInErrorRecoveryStack;
+    if (!errorRecoveryOk && assertInErrorRecoveryStack != null) {
+      fail('assertInErrorRecovery called but no errors reported: '
+          '$assertInErrorRecoveryStack');
+    }
+  }
+
+  Type _getIteratedType(Type iterableType) {
+    var typeStr = iterableType.type;
+    if (typeStr.startsWith('List<') && typeStr.endsWith('>')) {
+      return Type(typeStr.substring(5, typeStr.length - 1));
+    } else {
+      throw UnimplementedError('TODO(paulberry): getIteratedType($typeStr)');
+    }
+  }
+}
+
+class Label extends Node {
+  final String _name;
+
+  late final Node _binding;
+
+  Label(this._name) : super._(location: computeLocation());
+
+  CaseHeads then(CaseHeads caseHeads) =>
+      _CaseHeads(caseHeads._caseHeads, [this, ...caseHeads._labels]);
+
+  Statement thenStmt(Statement statement) {
+    if (statement is! _LabeledStatement) {
+      statement = _LabeledStatement(statement, location: computeLocation());
+    }
+    statement._labels.insert(0, this);
+    _binding = statement;
+    return statement;
+  }
+
+  @override
+  String toString() => _name;
+}
+
+/// Representation of an expression that can appear on the left hand side of an
+/// assignment (or as the target of `++` or `--`).  Methods in this class may be
+/// used to create more complex expressions based on this one.
+abstract class LValue extends Expression {
+  LValue._({required super.location});
+
+  @override
+  void preVisit(PreVisitor visitor, {_LValueDisposition disposition});
+
+  /// Creates an expression representing a write to this L-value.
+  Expression write(Expression? value) =>
+      new _Write(this, value, location: computeLocation());
+
+  void _visitWrite(Harness h, Expression assignmentExpression, Type writtenType,
+      Expression? rhs);
+}
+
+class MiniAstOperations
     with TypeOperations<Type>, TypeOperations2<Type>
     implements Operations<Var, Type> {
   static const Map<String, bool> _coreSubtypes = const {
@@ -602,16 +767,8 @@
     '?, Null': Type('Null'),
   };
 
-  bool _started = false;
-
-  late final FlowAnalysis<Node, Statement, Expression, Var, Type> flow;
-
   bool? _legacy;
 
-  bool? _patternsEnabled;
-
-  Type? _thisType;
-
   final Map<String, bool> _subtypes = Map.of(_coreSubtypes);
 
   final Map<String, Type> _factorResults = Map.of(_coreFactors);
@@ -620,50 +777,16 @@
 
   final Map<String, Type> _lubs = Map.of(_coreLubs);
 
-  final Map<String, _PropertyElement> _members = {};
-
   Map<String, Map<String, String>> _promotionExceptions = {};
 
-  late final typeAnalyzer = _MiniAstTypeAnalyzer(
-      this,
-      TypeAnalyzerOptions(
-          nullSafetyEnabled: !legacy, patternsEnabled: patternsEnabled));
-
-  /// Indicates whether initializers of implicitly typed variables should be
-  /// accounted for by SSA analysis.  (In an ideal world, they always would be,
-  /// but due to https://github.com/dart-lang/language/issues/1785, they weren't
-  /// always, and we need to be able to replicate the old behavior when
-  /// analyzing old language versions).
-  bool _respectImplicitlyTypedVarInitializers = true;
-
   final Set<_PropertyElement> promotableFields = {};
 
-  MiniIrBuilder get irBuilder => typeAnalyzer._irBuilder;
-
   bool get legacy => _legacy ?? false;
 
   set legacy(bool value) {
-    assert(!_started);
     _legacy = value;
   }
 
-  bool get patternsEnabled => _patternsEnabled ?? !legacy;
-
-  set patternsEnabled(bool value) {
-    assert(!_started);
-    _patternsEnabled = value;
-  }
-
-  set respectImplicitlyTypedVarInitializers(bool value) {
-    assert(!_started);
-    _respectImplicitlyTypedVarInitializers = value;
-  }
-
-  set thisType(String type) {
-    assert(!_started);
-    _thisType = Type(type);
-  }
-
   /// Updates the harness so that when a [factor] query is invoked on types
   /// [from] and [what], [result] will be returned.
   void addFactor(String from, String what, String result) {
@@ -671,18 +794,6 @@
     _factorResults[query] = Type(result);
   }
 
-  /// Updates the harness so that when member [memberName] is looked up on type
-  /// [targetType], a member is found having the given [type].
-  void addMember(String targetType, String memberName, String type,
-      {bool promotable = false}) {
-    var query = '$targetType.$memberName';
-    var member = _PropertyElement(Type(type));
-    _members[query] = member;
-    if (promotable) {
-      promotableFields.add(member);
-    }
-  }
-
   void addPromotionException(String from, String to, String result) {
     (_promotionExceptions[from] ??= {})[to] = result;
   }
@@ -711,14 +822,6 @@
     return _factorResults[query] ?? fail('Unknown factor query: $query');
   }
 
-  /// Attempts to look up a member named [memberName] in the given [type].  If
-  /// a member is found, returns its [_PropertyElement] object.  Otherwise the
-  /// test fails.
-  _PropertyElement getMember(Type type, String memberName) {
-    var query = '$type.$memberName';
-    return _members[query] ?? fail('Unknown member query: $query');
-  }
-
   @override
   Type glb(Type type1, Type type2) {
     if (type1.type == type2.type) return type1;
@@ -796,35 +899,6 @@
     }
   }
 
-  /// Runs the given [statements] through flow analysis, checking any assertions
-  /// they contain.
-  void run(List<Statement> statements,
-      {bool errorRecoveryOk = false, Set<String> expectedErrors = const {}}) {
-    _started = true;
-    if (legacy && patternsEnabled) {
-      fail('Patterns cannot be enabled in legacy mode');
-    }
-    var visitor = PreVisitor(typeAnalyzer.errors);
-    var b = _Block(statements, location: computeLocation());
-    b.preVisit(visitor);
-    flow = legacy
-        ? FlowAnalysis<Node, Statement, Expression, Var, Type>.legacy(
-            this, visitor._assignedVariables)
-        : FlowAnalysis<Node, Statement, Expression, Var, Type>(
-            this, visitor._assignedVariables,
-            respectImplicitlyTypedVarInitializers:
-                _respectImplicitlyTypedVarInitializers);
-    typeAnalyzer.dispatchStatement(b);
-    typeAnalyzer.finish();
-    expect(typeAnalyzer.errors._accumulatedErrors, expectedErrors);
-    var assertInErrorRecoveryStack =
-        typeAnalyzer.errors._assertInErrorRecoveryStack;
-    if (!errorRecoveryOk && assertInErrorRecoveryStack != null) {
-      fail('assertInErrorRecovery called but no errors reported: '
-          '$assertInErrorRecoveryStack');
-    }
-  }
-
   @override
   Type? tryPromoteToType(Type to, Type from) {
     var exception = (_promotionExceptions[from.type] ?? {})[to.type];
@@ -843,15 +917,6 @@
     return variable.type;
   }
 
-  Type _getIteratedType(Type iterableType) {
-    var typeStr = iterableType.type;
-    if (typeStr.startsWith('List<') && typeStr.endsWith('>')) {
-      return Type(typeStr.substring(5, typeStr.length - 1));
-    } else {
-      throw UnimplementedError('TODO(paulberry): getIteratedType($typeStr)');
-    }
-  }
-
   Type _lub(Type type1, Type type2) {
     if (isSameType(type1, type2)) {
       return type1;
@@ -878,46 +943,6 @@
   }
 }
 
-class Label extends Node {
-  final String _name;
-
-  late final Node _binding;
-
-  Label(this._name) : super._(location: computeLocation());
-
-  CaseHeads then(CaseHeads caseHeads) =>
-      _CaseHeads(caseHeads._caseHeads, [this, ...caseHeads._labels]);
-
-  Statement thenStmt(Statement statement) {
-    if (statement is! _LabeledStatement) {
-      statement = _LabeledStatement(statement, location: computeLocation());
-    }
-    statement._labels.insert(0, this);
-    _binding = statement;
-    return statement;
-  }
-
-  @override
-  String toString() => _name;
-}
-
-/// Representation of an expression that can appear on the left hand side of an
-/// assignment (or as the target of `++` or `--`).  Methods in this class may be
-/// used to create more complex expressions based on this one.
-abstract class LValue extends Expression {
-  LValue._({required super.location});
-
-  @override
-  void preVisit(PreVisitor visitor, {_LValueDisposition disposition});
-
-  /// Creates an expression representing a write to this L-value.
-  Expression write(Expression? value) =>
-      new _Write(this, value, location: computeLocation());
-
-  void _visitWrite(Harness h, Expression assignmentExpression, Type writtenType,
-      Expression? rhs);
-}
-
 /// Representation of an expression or statement in the pseudo-Dart language
 /// used for flow analysis testing.
 class Node {
@@ -2387,7 +2412,7 @@
   Type get thisType => _harness._thisType!;
 
   @override
-  TypeOperations2<Type> get typeOperations => _harness;
+  MiniAstOperations get typeOperations => _harness._operations;
 
   void analyzeAssertStatement(
       Statement node, Expression condition, Expression? message) {
@@ -2775,7 +2800,7 @@
   @override
   bool isVariablePattern(Node pattern) => pattern is _VariablePattern;
 
-  Type leastUpperBound(Type t1, Type t2) => _harness._lub(t1, t2);
+  Type leastUpperBound(Type t1, Type t2) => _harness._operations._lub(t1, t2);
 
   @override
   Type listType(Type elementType) =>
@@ -2798,7 +2823,8 @@
   Type variableTypeFromInitializerType(Type type) {
     // Variables whose initializer has type `Null` receive the inferred type
     // `dynamic`.
-    if (_harness.classifyType(type) == TypeClassification.nullOrEquivalent) {
+    if (_harness._operations.classifyType(type) ==
+        TypeClassification.nullOrEquivalent) {
       type = dynamicType;
     }
     // Variables whose initializer type includes a promoted type variable
@@ -2890,7 +2916,7 @@
     var rhsType =
         h.typeAnalyzer.analyzeExpression(rhs, h.typeAnalyzer.unknownType);
     h.flow.nullAwareAccess_end();
-    var type = h._lub(rhsType, Type('Null'));
+    var type = h._operations._lub(rhsType, Type('Null'));
     h.irBuilder.apply(
         _fakeMethodName, [Kind.expression, Kind.expression], Kind.expression,
         location: location);