Version 2.13.0-181.0.dev

Merge commit 'fbc25f3dfff942a7513fd328793373bd3677377e' into 'dev'
diff --git a/pkg/analyzer/lib/src/error/type_arguments_verifier.dart b/pkg/analyzer/lib/src/error/type_arguments_verifier.dart
index eb3b8ec..a14d9f4 100644
--- a/pkg/analyzer/lib/src/error/type_arguments_verifier.dart
+++ b/pkg/analyzer/lib/src/error/type_arguments_verifier.dart
@@ -444,6 +444,7 @@
     if (parent is WithClause) return false;
     if (parent is ConstructorName) return false;
     if (parent is ImplementsClause) return false;
+    if (parent is GenericTypeAlias) return false;
     return true;
   }
 
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 291ac76..307171d 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -3821,15 +3821,15 @@
 
     int count = typeNames.length;
     List<bool> detectedRepeatOnIndex = List<bool>.filled(count, false);
-    for (int i = 0; i < detectedRepeatOnIndex.length; i++) {
-      detectedRepeatOnIndex[i] = false;
-    }
     for (int i = 0; i < count; i++) {
       if (!detectedRepeatOnIndex[i]) {
-        var element = typeNames[i].name.staticElement;
+        var iType = typeNames[i].type;
         for (int j = i + 1; j < count; j++) {
           TypeName typeName = typeNames[j];
-          if (typeName.name.staticElement == element) {
+          var jType = typeName.type;
+          if (iType is InterfaceType &&
+              jType is InterfaceType &&
+              iType.element == jType.element) {
             detectedRepeatOnIndex[j] = true;
             errorReporter
                 .reportErrorForNode(errorCode, typeName, [typeName.name.name]);
diff --git a/pkg/analyzer/test/src/dart/resolution/class_test.dart b/pkg/analyzer/test/src/dart/resolution/class_test.dart
index da64df0..f58dbf4 100644
--- a/pkg/analyzer/test/src/dart/resolution/class_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/class_test.dart
@@ -432,30 +432,6 @@
     assertType(a.supertype, 'Object');
   }
 
-  test_error_implementsRepeated() async {
-    await assertErrorsInCode(r'''
-class A {}
-class B implements A, A {} // ref
-''', [
-      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 33, 1),
-    ]);
-
-    var a = findElement.class_('A');
-    assertTypeName(findNode.typeName('A, A {} // ref'), a, 'A');
-    assertTypeName(findNode.typeName('A {} // ref'), a, 'A');
-  }
-
-  test_error_implementsRepeated_3times() async {
-    await assertErrorsInCode(r'''
-class A {} class C{}
-class B implements A, A, A, A {}
-''', [
-      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 43, 1),
-      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 46, 1),
-      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 49, 1),
-    ]);
-  }
-
   test_error_memberWithClassName_field() async {
     await assertErrorsInCode(r'''
 class C {
diff --git a/pkg/analyzer/test/src/dart/resolution/mixin_test.dart b/pkg/analyzer/test/src/dart/resolution/mixin_test.dart
index 7c8bd6a..71db9f6 100644
--- a/pkg/analyzer/test/src/dart/resolution/mixin_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/mixin_test.dart
@@ -359,15 +359,6 @@
     assertTypeName(typeRef, null, 'void');
   }
 
-  test_error_implementsRepeated() async {
-    await assertErrorsInCode(r'''
-class A {}
-mixin M implements A, A {}
-''', [
-      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 33, 1),
-    ]);
-  }
-
   test_error_memberWithClassName_getter() async {
     await assertErrorsInCode(r'''
 mixin M {
@@ -939,15 +930,6 @@
     ]);
   }
 
-  test_error_onRepeated() async {
-    await assertErrorsInCode(r'''
-class A {}
-mixin M on A, A {}
-''', [
-      error(CompileTimeErrorCode.ON_REPEATED, 25, 1),
-    ]);
-  }
-
   test_error_undefinedSuperMethod() async {
     await assertErrorsInCode(r'''
 class A {}
diff --git a/pkg/analyzer/test/src/diagnostics/implements_repeated_test.dart b/pkg/analyzer/test/src/diagnostics/implements_repeated_test.dart
new file mode 100644
index 0000000..01da5dc
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/implements_repeated_test.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2021, 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 'package:analyzer/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ImplementsRepeatedTest);
+  });
+}
+
+@reflectiveTest
+class ImplementsRepeatedTest extends PubPackageResolutionTest {
+  test_class_implements_2times() async {
+    await assertErrorsInCode(r'''
+class A {}
+class B implements A, A {} // ref
+''', [
+      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 33, 1),
+    ]);
+
+    var A = findElement.class_('A');
+    assertTypeName(findNode.typeName('A, A {} // ref'), A, 'A');
+    assertTypeName(findNode.typeName('A {} // ref'), A, 'A');
+  }
+
+  test_class_implements_2times_viaTypeAlias() async {
+    await assertErrorsInCode(r'''
+class A {}
+typedef B = A;
+class C implements A, B {} // ref
+''', [
+      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 48, 1),
+    ]);
+
+    assertTypeName(
+      findNode.typeName('A, B {} // ref'),
+      findElement.class_('A'),
+      'A',
+    );
+
+    assertTypeName(
+      findNode.typeName('B {} // ref'),
+      findElement.typeAlias('B'),
+      'A',
+    );
+  }
+
+  test_class_implements_4times() async {
+    await assertErrorsInCode(r'''
+class A {} class C{}
+class B implements A, A, A, A {}
+''', [
+      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 43, 1),
+      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 46, 1),
+      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 49, 1),
+    ]);
+  }
+
+  test_mixin_implements_2times() async {
+    await assertErrorsInCode(r'''
+class A {}
+mixin M implements A, A {}
+''', [
+      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 33, 1),
+    ]);
+  }
+
+  test_mixin_implements_4times() async {
+    await assertErrorsInCode(r'''
+class A {}
+mixin M implements A, A, A, A {}
+''', [
+      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 33, 1),
+      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 36, 1),
+      error(CompileTimeErrorCode.IMPLEMENTS_REPEATED, 39, 1),
+    ]);
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/on_repeated_test.dart b/pkg/analyzer/test/src/diagnostics/on_repeated_test.dart
new file mode 100644
index 0000000..41c3a90
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/on_repeated_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2021, 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 'package:analyzer/src/error/codes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(OnRepeatedTest);
+  });
+}
+
+@reflectiveTest
+class OnRepeatedTest extends PubPackageResolutionTest {
+  test_2times() async {
+    await assertErrorsInCode(r'''
+class A {}
+mixin M on A, A {}
+''', [
+      error(CompileTimeErrorCode.ON_REPEATED, 25, 1),
+    ]);
+  }
+
+  test_2times_viaTypeAlias() async {
+    await assertErrorsInCode(r'''
+class A {}
+typedef B = A;
+mixin M on A, B {}
+''', [
+      error(CompileTimeErrorCode.ON_REPEATED, 40, 1),
+    ]);
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index a227e93..6bd56ae 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -231,6 +231,7 @@
 import 'implements_deferred_class_test.dart' as implements_deferred_class;
 import 'implements_disallowed_class_test.dart' as implements_disallowed_class;
 import 'implements_non_class_test.dart' as implements_non_class;
+import 'implements_repeated_test.dart' as implements_repeated;
 import 'implements_super_class_test.dart' as implements_super_class;
 import 'implements_type_alias_expands_to_type_parameter_test.dart'
     as implements_type_alias_expands_to_type_parameter;
@@ -497,6 +498,7 @@
 import 'nullable_type_in_with_clause_test.dart' as nullable_type_in_with_clause;
 import 'object_cannot_extend_another_class_test.dart'
     as object_cannot_extend_another_class;
+import 'on_repeated_test.dart' as on_repeated;
 import 'optional_parameter_in_operator_test.dart'
     as optional_parameter_in_operator;
 import 'override_on_non_overriding_field_test.dart'
@@ -838,6 +840,7 @@
     implements_deferred_class.main();
     implements_disallowed_class.main();
     implements_non_class.main();
+    implements_repeated.main();
     implements_super_class.main();
     implements_type_alias_expands_to_type_parameter.main();
     implicit_this_reference_in_initializer.main();
@@ -1013,6 +1016,7 @@
     nullable_type_in_on_clause.main();
     nullable_type_in_with_clause.main();
     object_cannot_extend_another_class.main();
+    on_repeated.main();
     optional_parameter_in_operator.main();
     override_on_non_overriding_field.main();
     override_on_non_overriding_getter.main();
diff --git a/pkg/analyzer/test/src/diagnostics/type_argument_not_matching_bounds_test.dart b/pkg/analyzer/test/src/diagnostics/type_argument_not_matching_bounds_test.dart
index b8c2b5c..9d407aa 100644
--- a/pkg/analyzer/test/src/diagnostics/type_argument_not_matching_bounds_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/type_argument_not_matching_bounds_test.dart
@@ -495,6 +495,60 @@
     ]);
   }
 
+  test_nonFunctionTypeAlias_body_typeArgument_mismatch() async {
+    await assertErrorsInCode(r'''
+class A {}
+class B {}
+class G<T extends A> {}
+typedef X = G<B>;
+''', [
+      error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 60, 1),
+    ]);
+  }
+
+  test_nonFunctionTypeAlias_body_typeArgument_regularBounded() async {
+    await assertNoErrorsInCode(r'''
+class A {}
+class B extends A {}
+class G<T extends A> {}
+typedef X = G<B>;
+''');
+  }
+
+  test_nonFunctionTypeAlias_body_typeArgument_superBounded() async {
+    await assertNoErrorsInCode(r'''
+class A<T extends A<T>> {}
+typedef X = List<A>;
+''');
+  }
+
+  test_nonFunctionTypeAlias_interfaceType_body_mismatch() async {
+    await assertErrorsInCode(r'''
+class A {}
+class B {}
+class G<T extends A> {}
+typedef X = G<B>;
+''', [
+      error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 60, 1),
+    ]);
+  }
+
+  test_nonFunctionTypeAlias_interfaceType_body_regularBounded() async {
+    await assertNoErrorsInCode(r'''
+class A<T> {}
+typedef X<T> = A;
+''');
+  }
+
+  test_nonFunctionTypeAlias_interfaceType_body_superBounded() async {
+    await assertErrorsInCode(r'''
+class A<T extends A<T>> {}
+typedef X<T> = A;
+''', [
+      error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 42, 1),
+    ]);
+  }
+
   test_nonFunctionTypeAlias_interfaceType_parameter() async {
     await assertErrorsInCode(r'''
 class A {}
@@ -514,15 +568,7 @@
 ''');
   }
 
-  test_nonFunctionTypeAlias_interfaceType_parameter_superBounded() async {
-    await assertNoErrorsInCode(r'''
-class A {}
-typedef X<T extends A> = Map<int, T>;
-void f(X<Never> a) {}
-''');
-  }
-
-  test_notRegularBounded_notSuperBounded_invariant() async {
+  test_notRegularBounded_notSuperBounded_parameter_invariant() async {
     await assertErrorsInCode(r'''
 typedef A<X> = X Function(X);
 typedef G<X extends A<X>> = void Function<Y extends X>();
diff --git a/pkg/compiler/lib/src/commandline_options.dart b/pkg/compiler/lib/src/commandline_options.dart
index e0c45c8..45b02a0 100644
--- a/pkg/compiler/lib/src/commandline_options.dart
+++ b/pkg/compiler/lib/src/commandline_options.dart
@@ -106,6 +106,7 @@
   static const String dillDependencies = '--dill-dependencies';
   static const String readData = '--read-data';
   static const String writeData = '--write-data';
+  static const String noClosedWorldInData = '--no-closed-world-in-data';
   static const String writeClosedWorld = '--write-closed-world';
   static const String readClosedWorld = '--read-closed-world';
   static const String readCodegen = '--read-codegen';
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 571e8ce..687c158 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -236,6 +236,10 @@
         options.readCodegenUri == null;
   }
 
+  bool get onlyPerformCodegen {
+    return options.readClosedWorldUri != null && options.readDataUri != null;
+  }
+
   Future runInternal(Uri uri) async {
     clearState();
     assert(uri != null);
@@ -250,30 +254,32 @@
       GlobalTypeInferenceResults globalTypeInferenceResults =
           performGlobalTypeInference(closedWorld);
       if (options.writeDataUri != null) {
-        serializationTask
-            .serializeGlobalTypeInference(globalTypeInferenceResults);
+        if (options.noClosedWorldInData) {
+          serializationTask
+              .serializeGlobalTypeInference(globalTypeInferenceResults);
+        } else {
+          serializationTask
+              .serializeGlobalTypeInferenceLegacy(globalTypeInferenceResults);
+        }
         return;
       }
       await generateJavaScriptCode(globalTypeInferenceResults);
-    } else if (options.readDataUri != null) {
+    } else if (onlyPerformCodegen) {
       GlobalTypeInferenceResults globalTypeInferenceResults;
-      if (options.readClosedWorldUri != null) {
-        ir.Component component =
-            await serializationTask.deserializeComponentAndUpdateOptions();
-        JsClosedWorld closedWorld =
-            await serializationTask.deserializeClosedWorld(
-                environment, abstractValueStrategy, component);
-        globalTypeInferenceResults =
-            await serializationTask.deserializeGlobalAnalysis(
-                environment, abstractValueStrategy, component, closedWorld);
-      } else {
-        globalTypeInferenceResults = await serializationTask
-            .deserializeGlobalTypeInference(environment, abstractValueStrategy);
-      }
-      if (options.debugGlobalInference) {
-        performGlobalTypeInference(globalTypeInferenceResults.closedWorld);
-        return;
-      }
+      ir.Component component =
+          await serializationTask.deserializeComponentAndUpdateOptions();
+      JsClosedWorld closedWorld =
+          await serializationTask.deserializeClosedWorld(
+              environment, abstractValueStrategy, component);
+      globalTypeInferenceResults =
+          await serializationTask.deserializeGlobalTypeInferenceResults(
+              environment, abstractValueStrategy, component, closedWorld);
+      await generateJavaScriptCode(globalTypeInferenceResults);
+    } else if (options.readDataUri != null) {
+      // TODO(joshualitt) delete and clean up after google3 roll
+      var globalTypeInferenceResults =
+          await serializationTask.deserializeGlobalTypeInferenceLegacy(
+              environment, abstractValueStrategy);
       await generateJavaScriptCode(globalTypeInferenceResults);
     } else {
       KernelResult result = await kernelLoader.load(uri);
@@ -444,14 +450,18 @@
       GlobalTypeInferenceResults results) {
     SerializationStrategy strategy = const BytesInMemorySerializationStrategy();
     List<int> irData = strategy.unpackAndSerializeComponent(results);
-    List worldData = strategy.serializeGlobalTypeInferenceResults(results);
+    List<int> closedWorldData =
+        strategy.serializeClosedWorld(results.closedWorld);
+    List<int> globalTypeInferenceResultsData =
+        strategy.serializeGlobalTypeInferenceResults(results);
     return strategy.deserializeGlobalTypeInferenceResults(
         options,
         reporter,
         environment,
         abstractValueStrategy,
         strategy.deserializeComponent(irData),
-        worldData);
+        closedWorldData,
+        globalTypeInferenceResultsData);
   }
 
   void compileFromKernel(Uri rootLibraryUri, Iterable<Uri> libraries) {
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index d4a9558..b96bd5d 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -277,8 +277,15 @@
     if (argument != Flags.readData) {
       readDataUri = fe.nativeToUri(extractPath(argument, isDirectory: false));
     }
-    if (readStrategy != ReadStrategy.fromCodegen) {
+
+    if (readStrategy == ReadStrategy.fromDart) {
       readStrategy = ReadStrategy.fromData;
+    } else if (readStrategy == ReadStrategy.fromClosedWorld) {
+      readStrategy = ReadStrategy.fromDataAndClosedWorld;
+    } else if (readStrategy == ReadStrategy.fromCodegen) {
+      readStrategy = ReadStrategy.fromCodegenAndData;
+    } else if (readStrategy == ReadStrategy.fromCodegenAndClosedWorld) {
+      readStrategy = ReadStrategy.fromCodegenAndClosedWorldAndData;
     }
   }
 
@@ -287,7 +294,16 @@
       readClosedWorldUri =
           fe.nativeToUri(extractPath(argument, isDirectory: false));
     }
-    readStrategy = ReadStrategy.fromClosedWorld;
+
+    if (readStrategy == ReadStrategy.fromDart) {
+      readStrategy = ReadStrategy.fromClosedWorld;
+    } else if (readStrategy == ReadStrategy.fromData) {
+      readStrategy = ReadStrategy.fromDataAndClosedWorld;
+    } else if (readStrategy == ReadStrategy.fromCodegen) {
+      readStrategy = ReadStrategy.fromCodegenAndClosedWorld;
+    } else if (readStrategy == ReadStrategy.fromCodegenAndData) {
+      readStrategy = ReadStrategy.fromCodegenAndClosedWorldAndData;
+    }
   }
 
   void setDillDependencies(String argument) {
@@ -318,7 +334,16 @@
       readCodegenUri =
           fe.nativeToUri(extractPath(argument, isDirectory: false));
     }
-    readStrategy = ReadStrategy.fromCodegen;
+
+    if (readStrategy == ReadStrategy.fromDart) {
+      readStrategy = ReadStrategy.fromCodegen;
+    } else if (readStrategy == ReadStrategy.fromClosedWorld) {
+      readStrategy = ReadStrategy.fromCodegenAndClosedWorld;
+    } else if (readStrategy == ReadStrategy.fromData) {
+      readStrategy = ReadStrategy.fromCodegenAndData;
+    } else if (readStrategy == ReadStrategy.fromDataAndClosedWorld) {
+      readStrategy = ReadStrategy.fromCodegenAndClosedWorldAndData;
+    }
   }
 
   void setWriteData(String argument) {
@@ -466,6 +491,7 @@
     new OptionHandler('${Flags.dillDependencies}=.+', setDillDependencies),
     new OptionHandler('${Flags.readData}|${Flags.readData}=.+', setReadData),
     new OptionHandler('${Flags.writeData}|${Flags.writeData}=.+', setWriteData),
+    new OptionHandler(Flags.noClosedWorldInData, passThrough),
     new OptionHandler('${Flags.readClosedWorld}|${Flags.readClosedWorld}=.+',
         setReadClosedWorld),
     new OptionHandler('${Flags.writeClosedWorld}|${Flags.writeClosedWorld}=.+',
@@ -709,7 +735,9 @@
       if (readStrategy == ReadStrategy.fromCodegen) {
         fail("Cannot read and write serialized codegen simultaneously.");
       }
-      if (readStrategy != ReadStrategy.fromData) {
+      // TODO(joshualitt) cleanup after google3 roll.
+      if (readStrategy != ReadStrategy.fromData &&
+          readStrategy != ReadStrategy.fromDataAndClosedWorld) {
         fail("Can only write serialized codegen from serialized data.");
       }
       if (codegenShards == null) {
@@ -737,6 +765,8 @@
       options.add('${Flags.readClosedWorld}=${readClosedWorldUri}');
       break;
     case ReadStrategy.fromData:
+      // TODO(joshualitt): fail after Google3 roll.
+      // fail("Must read from closed world and data.");
       readDataUri ??= Uri.base.resolve('$scriptName.data');
       options.add('${Flags.readData}=${readDataUri}');
       break;
@@ -747,6 +777,8 @@
       options.add('${Flags.readData}=${readDataUri}');
       break;
     case ReadStrategy.fromCodegen:
+    case ReadStrategy.fromCodegenAndData:
+      // TODO(joshualitt): fall through to fail after google3 roll.
       readDataUri ??= Uri.base.resolve('$scriptName.data');
       options.add('${Flags.readData}=${readDataUri}');
       readCodegenUri ??= Uri.base.resolve('$scriptName.code');
@@ -760,6 +792,9 @@
       options.add('${Flags.codegenShards}=$codegenShards');
       break;
     case ReadStrategy.fromCodegenAndClosedWorld:
+      fail("Must read from closed world, data, and codegen");
+      break;
+    case ReadStrategy.fromCodegenAndClosedWorldAndData:
       readClosedWorldUri ??= Uri.base.resolve('$scriptName.world');
       options.add('${Flags.readClosedWorld}=${readClosedWorldUri}');
       readDataUri ??= Uri.base.resolve('$scriptName.data');
@@ -816,6 +851,8 @@
         summary = 'Data files $input and $dataInput ';
         break;
       case ReadStrategy.fromData:
+        // TODO(joshualitt): fail after google3 roll.
+        //fail("Must read from closed world and data.");
         inputName = 'bytes data';
         inputSize = inputProvider.dartCharactersRead;
         String dataInput =
@@ -832,6 +869,8 @@
         summary = 'Data files $input, $worldInput, and $dataInput ';
         break;
       case ReadStrategy.fromCodegen:
+      case ReadStrategy.fromCodegenAndData:
+        // TODO(joshualitt): Fall through to fail after google3 roll.
         inputName = 'bytes data';
         inputSize = inputProvider.dartCharactersRead;
         String dataInput =
@@ -842,6 +881,9 @@
             '${codeInput}[0-${codegenShards - 1}] ';
         break;
       case ReadStrategy.fromCodegenAndClosedWorld:
+        fail("Must read from closed world, data, and codegen");
+        break;
+      case ReadStrategy.fromCodegenAndClosedWorldAndData:
         inputName = 'bytes data';
         inputSize = inputProvider.dartCharactersRead;
         String worldInput =
@@ -1373,12 +1415,17 @@
   });
 }
 
+// TODO(joshualitt): Clean up the combinatorial explosion of read strategies.
+// Right now only fromClosedWorld, fromDataAndClosedWorld, and
+// fromCodegenAndClosedWorldAndData are valid.
 enum ReadStrategy {
   fromDart,
   fromClosedWorld,
   fromData,
   fromDataAndClosedWorld,
   fromCodegen,
-  fromCodegenAndClosedWorld
+  fromCodegenAndClosedWorld,
+  fromCodegenAndData,
+  fromCodegenAndClosedWorldAndData,
 }
 enum WriteStrategy { toKernel, toClosedWorld, toData, toCodegen, toJs }
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index 03edd91..ce9cddb 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -77,6 +77,11 @@
   /// If this is set, the compilation stops after type inference.
   Uri writeDataUri;
 
+  /// Serialize data without the closed world.
+  /// TODO(joshualitt) make this the default right after landing in Google3 and
+  /// clean up.
+  bool noClosedWorldInData = false;
+
   /// Location from which the serialized closed world is read.
   ///
   /// If this is set, the [entryPoint] is expected to be a .dill file and the
@@ -542,6 +547,7 @@
           _extractUriListOption(options, '${Flags.dillDependencies}')
       ..readDataUri = _extractUriOption(options, '${Flags.readData}=')
       ..writeDataUri = _extractUriOption(options, '${Flags.writeData}=')
+      ..noClosedWorldInData = _hasOption(options, Flags.noClosedWorldInData)
       ..readClosedWorldUri =
           _extractUriOption(options, '${Flags.readClosedWorld}=')
       ..writeClosedWorldUri =
diff --git a/pkg/compiler/lib/src/serialization/strategies.dart b/pkg/compiler/lib/src/serialization/strategies.dart
index 37fbe89..9269eed 100644
--- a/pkg/compiler/lib/src/serialization/strategies.dart
+++ b/pkg/compiler/lib/src/serialization/strategies.dart
@@ -49,7 +49,8 @@
       Environment environment,
       AbstractValueStrategy abstractValueStrategy,
       ir.Component component,
-      List<T> data);
+      List<T> closedWorldData,
+      List<T> globalTypeInferenceResultsData);
 
   List<T> serializeClosedWorld(JsClosedWorld closedWorld);
 
@@ -83,10 +84,28 @@
       Environment environment,
       AbstractValueStrategy abstractValueStrategy,
       ir.Component component,
-      List<int> data) {
-    DataSource source = new BinarySourceImpl(data, useDataKinds: useDataKinds);
-    return deserializeGlobalTypeInferenceResultsFromSource(options, reporter,
-        environment, abstractValueStrategy, component, source);
+      List<int> closedWorldData,
+      List<int> globalTypeInferenceResultsData) {
+    DataSource closedWorldSource =
+        BinarySourceImpl(closedWorldData, useDataKinds: useDataKinds);
+    DataSource globalTypeInferenceResultsSource = BinarySourceImpl(
+        globalTypeInferenceResultsData,
+        useDataKinds: useDataKinds);
+    JsClosedWorld closedWorld = deserializeClosedWorldFromSource(
+        options,
+        reporter,
+        environment,
+        abstractValueStrategy,
+        component,
+        closedWorldSource);
+    return deserializeGlobalTypeInferenceResultsFromSource(
+        options,
+        reporter,
+        environment,
+        abstractValueStrategy,
+        component,
+        closedWorld,
+        globalTypeInferenceResultsSource);
   }
 
   @override
@@ -134,10 +153,28 @@
       Environment environment,
       AbstractValueStrategy abstractValueStrategy,
       ir.Component component,
-      List<int> data) {
-    DataSource source = new BinarySourceImpl(data, useDataKinds: useDataKinds);
-    return deserializeGlobalTypeInferenceResultsFromSource(options, reporter,
-        environment, abstractValueStrategy, component, source);
+      List<int> closedWorldData,
+      List<int> globalTypeInferenceResultsData) {
+    DataSource closedWorldSource =
+        BinarySourceImpl(closedWorldData, useDataKinds: useDataKinds);
+    DataSource globalTypeInferenceResultsSource = BinarySourceImpl(
+        globalTypeInferenceResultsData,
+        useDataKinds: useDataKinds);
+    JsClosedWorld closedWorld = deserializeClosedWorldFromSource(
+        options,
+        reporter,
+        environment,
+        abstractValueStrategy,
+        component,
+        closedWorldSource);
+    return deserializeGlobalTypeInferenceResultsFromSource(
+        options,
+        reporter,
+        environment,
+        abstractValueStrategy,
+        component,
+        closedWorld,
+        globalTypeInferenceResultsSource);
   }
 
   @override
@@ -186,10 +223,28 @@
       Environment environment,
       AbstractValueStrategy abstractValueStrategy,
       ir.Component component,
-      List<Object> data) {
-    DataSource source = new ObjectSource(data, useDataKinds: useDataKinds);
-    return deserializeGlobalTypeInferenceResultsFromSource(options, reporter,
-        environment, abstractValueStrategy, component, source);
+      List<Object> closedWorldData,
+      List<Object> globalTypeInferenceResultsData) {
+    DataSource closedWorldSource =
+        ObjectSource(closedWorldData, useDataKinds: useDataKinds);
+    DataSource globalTypeInferenceResultsSource = ObjectSource(
+        globalTypeInferenceResultsData,
+        useDataKinds: useDataKinds);
+    JsClosedWorld closedWorld = deserializeClosedWorldFromSource(
+        options,
+        reporter,
+        environment,
+        abstractValueStrategy,
+        component,
+        closedWorldSource);
+    return deserializeGlobalTypeInferenceResultsFromSource(
+        options,
+        reporter,
+        environment,
+        abstractValueStrategy,
+        component,
+        closedWorld,
+        globalTypeInferenceResultsSource);
   }
 
   @override
diff --git a/pkg/compiler/lib/src/serialization/task.dart b/pkg/compiler/lib/src/serialization/task.dart
index 6e5c4c8..cfc2b0d 100644
--- a/pkg/compiler/lib/src/serialization/task.dart
+++ b/pkg/compiler/lib/src/serialization/task.dart
@@ -19,6 +19,7 @@
 import '../js_backend/backend.dart';
 import '../js_backend/inferred_data.dart';
 import '../js_model/js_world.dart';
+import '../js_model/element_map_impl.dart';
 import '../js_model/locals.dart';
 import '../options.dart';
 import '../util/sink_adapter.dart';
@@ -30,40 +31,50 @@
   JsClosedWorld closedWorld = results.closedWorld;
   GlobalLocalsMap globalLocalsMap = results.globalLocalsMap;
   InferredData inferredData = results.inferredData;
-  closedWorld.writeToDataSink(sink);
   globalLocalsMap.writeToDataSink(sink);
   inferredData.writeToDataSink(sink);
   results.writeToDataSink(sink, closedWorld.elementMap);
   sink.close();
 }
 
-GlobalTypeInferenceResults deserializeGlobalAnalysisFromSource(
-    CompilerOptions options,
-    DiagnosticReporter reporter,
-    Environment environment,
-    AbstractValueStrategy abstractValueStrategy,
-    ir.Component component,
-    JsClosedWorld newClosedWorld,
-    DataSource source) {
-  GlobalLocalsMap newGlobalLocalsMap = GlobalLocalsMap.readFromDataSource(
-      newClosedWorld.closureDataLookup.getEnclosingMember, source);
-  InferredData newInferredData =
-      InferredData.readFromDataSource(source, newClosedWorld);
-  return GlobalTypeInferenceResults.readFromDataSource(
-      source,
-      newClosedWorld.elementMap,
-      newClosedWorld,
-      newGlobalLocalsMap,
-      newInferredData);
-}
-
 GlobalTypeInferenceResults deserializeGlobalTypeInferenceResultsFromSource(
     CompilerOptions options,
     DiagnosticReporter reporter,
     Environment environment,
     AbstractValueStrategy abstractValueStrategy,
     ir.Component component,
+    JsClosedWorld closedWorld,
     DataSource source) {
+  source.registerComponentLookup(ComponentLookup(component));
+  source.registerEntityLookup(ClosedEntityLookup(closedWorld.elementMap));
+  GlobalLocalsMap globalLocalsMap = GlobalLocalsMap.readFromDataSource(
+      closedWorld.closureDataLookup.getEnclosingMember, source);
+  InferredData inferredData =
+      InferredData.readFromDataSource(source, closedWorld);
+  return GlobalTypeInferenceResults.readFromDataSource(source,
+      closedWorld.elementMap, closedWorld, globalLocalsMap, inferredData);
+}
+
+void serializeGlobalTypeInferenceResultsToSinkLegacy(
+    GlobalTypeInferenceResults results, DataSink sink) {
+  JsClosedWorld closedWorld = results.closedWorld;
+  GlobalLocalsMap globalLocalsMap = results.globalLocalsMap;
+  InferredData inferredData = results.inferredData;
+  closedWorld.writeToDataSink(sink);
+  globalLocalsMap.writeToDataSink(sink);
+  inferredData.writeToDataSink(sink);
+  results.writeToDataSink(sink, closedWorld.elementMap);
+  sink.close();
+}
+
+GlobalTypeInferenceResults
+    deserializeGlobalTypeInferenceResultsFromSourceLegacy(
+        CompilerOptions options,
+        DiagnosticReporter reporter,
+        Environment environment,
+        AbstractValueStrategy abstractValueStrategy,
+        ir.Component component,
+        DataSource source) {
   JsClosedWorld newClosedWorld = new JsClosedWorld.readFromDataSource(
       options, reporter, environment, abstractValueStrategy, component, source);
   GlobalLocalsMap newGlobalLocalsMap = GlobalLocalsMap.readFromDataSource(
@@ -213,23 +224,7 @@
     });
   }
 
-  Future<GlobalTypeInferenceResults> deserializeGlobalTypeInference(
-      Environment environment,
-      AbstractValueStrategy abstractValueStrategy) async {
-    ir.Component component = await deserializeComponentAndUpdateOptions();
-
-    return await measureIoSubtask('deserialize data', () async {
-      _reporter.log('Reading data from ${_options.readDataUri}');
-      api.Input<List<int>> dataInput = await _provider
-          .readFromUri(_options.readDataUri, inputKind: api.InputKind.binary);
-      DataSource source =
-          BinarySourceImpl(dataInput.data, stringInterner: _stringInterner);
-      return deserializeGlobalTypeInferenceResultsFromSource(_options,
-          _reporter, environment, abstractValueStrategy, component, source);
-    });
-  }
-
-  Future<GlobalTypeInferenceResults> deserializeGlobalAnalysis(
+  Future<GlobalTypeInferenceResults> deserializeGlobalTypeInferenceResults(
       Environment environment,
       AbstractValueStrategy abstractValueStrategy,
       ir.Component component,
@@ -240,8 +235,45 @@
           .readFromUri(_options.readDataUri, inputKind: api.InputKind.binary);
       DataSource source =
           BinarySourceImpl(dataInput.data, stringInterner: _stringInterner);
-      return deserializeGlobalAnalysisFromSource(_options, _reporter,
-          environment, abstractValueStrategy, component, closedWorld, source);
+      return deserializeGlobalTypeInferenceResultsFromSource(
+          _options,
+          _reporter,
+          environment,
+          abstractValueStrategy,
+          component,
+          closedWorld,
+          source);
+    });
+  }
+
+  // TODO(joshualitt) get rid of legacy functions after Google3 roll.
+  void serializeGlobalTypeInferenceLegacy(GlobalTypeInferenceResults results) {
+    JsClosedWorld closedWorld = results.closedWorld;
+    ir.Component component = closedWorld.elementMap.programEnv.mainComponent;
+    serializeComponent(component);
+
+    measureSubtask('serialize data', () {
+      _reporter.log('Writing data to ${_options.writeDataUri}');
+      api.BinaryOutputSink dataOutput =
+          _outputProvider.createBinarySink(_options.writeDataUri);
+      DataSink sink = new BinarySink(new BinaryOutputSinkAdapter(dataOutput));
+      serializeGlobalTypeInferenceResultsToSinkLegacy(results, sink);
+    });
+  }
+
+  Future<GlobalTypeInferenceResults> deserializeGlobalTypeInferenceLegacy(
+      Environment environment,
+      AbstractValueStrategy abstractValueStrategy) async {
+    ir.Component component = await deserializeComponentAndUpdateOptions();
+
+    return await measureIoSubtask('deserialize data', () async {
+      _reporter.log('Reading data from ${_options.readDataUri}');
+      api.Input<List<int>> dataInput = await _provider
+          .readFromUri(_options.readDataUri, inputKind: api.InputKind.binary);
+      DataSource source =
+          BinarySourceImpl(dataInput.data, stringInterner: _stringInterner);
+      return deserializeGlobalTypeInferenceResultsFromSourceLegacy(_options,
+          _reporter, environment, abstractValueStrategy, component, source);
     });
   }
 
diff --git a/pkg/compiler/test/serialization/on_disk_split_test.dart b/pkg/compiler/test/serialization/on_disk_split_test.dart
index 00ce798..22cedb1 100644
--- a/pkg/compiler/test/serialization/on_disk_split_test.dart
+++ b/pkg/compiler/test/serialization/on_disk_split_test.dart
@@ -17,17 +17,19 @@
     Uri dillUri = dir.uri.resolve('out.dill');
     Uri outUri = dir.uri.resolve('out.js');
     var commonArgs = [
-      Flags.writeData,
       Flags.verbose,
       '--libraries-spec=$sdkLibrariesSpecificationUri',
     ];
     await internalMain([
           'samples-dev/swarm/swarm.dart',
+          Flags.writeClosedWorld,
           '--out=${dillUri}',
         ] +
         commonArgs);
     await internalMain([
           '${dillUri}',
+          Flags.readClosedWorld,
+          Flags.writeData,
           '--out=${outUri}',
         ] +
         commonArgs);
diff --git a/pkg/compiler/test/serialization/serialization_test_helper.dart b/pkg/compiler/test/serialization/serialization_test_helper.dart
index b429960..b7b771c 100644
--- a/pkg/compiler/test/serialization/serialization_test_helper.dart
+++ b/pkg/compiler/test/serialization/serialization_test_helper.dart
@@ -184,7 +184,8 @@
 GlobalTypeInferenceResults cloneInferenceResults(Compiler compiler,
     GlobalTypeInferenceResults results, SerializationStrategy strategy) {
   List<int> irData = strategy.unpackAndSerializeComponent(results);
-
+  List<int> closedWorldData =
+      strategy.serializeClosedWorld(results.closedWorld);
   List<int> worldData = strategy.serializeGlobalTypeInferenceResults(results);
   print('data size: ${worldData.length}');
 
@@ -196,6 +197,7 @@
           compiler.environment,
           compiler.abstractValueStrategy,
           newComponent,
+          closedWorldData,
           worldData);
   List<int> newWorldData =
       strategy.serializeGlobalTypeInferenceResults(newResults);
diff --git a/pkg/compiler/tool/modular_test_suite.dart b/pkg/compiler/tool/modular_test_suite.dart
index 0aa6ebe..780c182 100644
--- a/pkg/compiler/tool/modular_test_suite.dart
+++ b/pkg/compiler/tool/modular_test_suite.dart
@@ -54,7 +54,7 @@
         new IOPipeline([
           SourceToDillStep(),
           ComputeClosedWorldStep(),
-          GlobalAnalysisStep(),
+          LegacyGlobalAnalysisStep(),
           LegacyDart2jsCodegenStep(codeId0),
           LegacyDart2jsCodegenStep(codeId1),
           LegacyDart2jsEmissionStep(),
@@ -312,6 +312,8 @@
       for (String flag in flags) '--enable-experiment=$flag',
       '${Flags.readClosedWorld}=${toUri(module, closedWorldId)}',
       '${Flags.writeData}=${toUri(module, globalDataId)}',
+      // TODO(joshualitt): delete this flag after google3 roll
+      '${Flags.noClosedWorldInData}',
     ];
     var result =
         await _runProcess(Platform.resolvedExecutable, args, root.toFilePath());
@@ -425,6 +427,50 @@
   }
 }
 
+// TODO(joshualitt): delete after google3 roll.
+class LegacyGlobalAnalysisStep implements IOModularStep {
+  @override
+  List<DataId> get resultData => const [globalDataId];
+
+  @override
+  bool get needsSources => false;
+
+  @override
+  List<DataId> get dependencyDataNeeded => const [updatedDillId];
+
+  @override
+  List<DataId> get moduleDataNeeded => const [closedWorldId, updatedDillId];
+
+  @override
+  bool get onlyOnMain => true;
+
+  @override
+  Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
+      List<String> flags) async {
+    if (_options.verbose) print("\nstep: dart2js global analysis on $module");
+    List<String> args = [
+      '--packages=${sdkRoot.toFilePath()}/.packages',
+      _dart2jsScript,
+      // TODO(sigmund): remove this dependency on libraries.json
+      if (_options.useSdk) '--libraries-spec=$_librarySpecForSnapshot',
+      '${toUri(module, updatedDillId)}',
+      for (String flag in flags) '--enable-experiment=$flag',
+      '${Flags.readClosedWorld}=${toUri(module, closedWorldId)}',
+      '${Flags.writeData}=${toUri(module, globalDataId)}',
+    ];
+    var result =
+        await _runProcess(Platform.resolvedExecutable, args, root.toFilePath());
+
+    _checkExitCode(result, this, module);
+  }
+
+  @override
+  void notifyCached(Module module) {
+    if (_options.verbose)
+      print("\ncached step: dart2js global analysis on $module");
+  }
+}
+
 // Step that invokes the dart2js code generation on the main module given the
 // results of the global analysis step and produces one shard of the codegen
 // output.
diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart
index 4c953dd..0e85567 100644
--- a/pkg/test_runner/lib/src/command_output.dart
+++ b/pkg/test_runner/lib/src/command_output.dart
@@ -552,7 +552,7 @@
   static void parseErrors(String stderr, List<StaticError> errors,
       [List<StaticError> warnings]) {
     for (var error in AnalyzerError.parseStderr(stderr)) {
-      var staticError = StaticError({ErrorSource.analyzer: error.errorCode},
+      var staticError = StaticError(ErrorSource.analyzer, error.errorCode,
           line: error.line, column: error.column, length: error.length);
 
       if (error.severity == 'ERROR') {
@@ -1315,8 +1315,7 @@
       var line = int.parse(match.group(2));
       var column = int.parse(match.group(3));
       var message = match.group(4);
-      errors
-          .add(StaticError({errorSource: message}, line: line, column: column));
+      errors.add(StaticError(errorSource, message, line: line, column: column));
     }
   }
 
@@ -1417,7 +1416,7 @@
     assert(errorSource != null);
 
     var expected = testCase.testFile.expectedErrors
-        .where((error) => error.hasError(errorSource));
+        .where((error) => error.source == errorSource);
 
     var validation = StaticError.validateExpectations(
       expected,
diff --git a/pkg/test_runner/lib/src/static_error.dart b/pkg/test_runner/lib/src/static_error.dart
index 08104d7..d8ede3a 100644
--- a/pkg/test_runner/lib/src/static_error.dart
+++ b/pkg/test_runner/lib/src/static_error.dart
@@ -36,7 +36,7 @@
   const ErrorSource._(this.name);
 }
 
-/// Describes one or more static errors that should be reported at a specific
+/// Describes a single static error reported by a single front end at a specific
 /// location.
 ///
 /// These can be parsed from comments in [TestFile]s, in which case they
@@ -45,11 +45,6 @@
 /// produces the expected compile-time errors. This same class is also used for
 /// *reported* errors when parsing the output of a front end.
 ///
-/// Because there are multiple front ends that each report errors somewhat
-/// differently, each [StaticError] has a map to associate an error message
-/// with each front end. If there is no message for a given front end, it means
-/// the error is not reported by that front end.
-///
 /// For analyzer errors, the error "message" is actually the constant name for
 /// the error code, like "CompileTimeErrorCode.WRONG_TYPE".
 class StaticError implements Comparable<StaticError> {
@@ -98,85 +93,6 @@
   static List<StaticError> parseExpectations(String source) =>
       _ErrorExpectationParser(source)._parse();
 
-  /// Collapses overlapping [errors] into a shorter list of errors where
-  /// possible.
-  ///
-  /// Errors on the same location can be collapsed if none of them both define
-  /// a message for the same front end.
-  static List<StaticError> simplify(List<StaticError> errors) {
-    var result = errors.toList();
-    result.sort();
-
-    for (var i = 0; i < result.length - 1; i++) {
-      var a = result[i];
-
-      // Look for a later error we can merge with this one. Usually, it will be
-      // adjacent to this one, but if there are multiple errors with no length
-      // on the same location, those will all be next to each other and their
-      // merge targets will come later. This happens when CFE reports multiple
-      // errors at the same location (messages but no length) and analyzer does
-      // too (codes and lengths but no messages).
-      for (var j = i + 1; j < result.length; j++) {
-        var b = result[j];
-
-        // Position must be the same. If the position is different, we can
-        // stop looking because all same-position errors will be adjacent.
-        if (a.line != b.line) break;
-        if (a.column != b.column) break;
-
-        // If they both have lengths that are different, we can't discard that
-        // information.
-        if (a.length != null && b.length != null && a.length != b.length) {
-          continue;
-        }
-
-        // Can't lose any messages.
-        if (ErrorSource.all
-            .any((source) => a.hasError(source) && b.hasError(source))) {
-          continue;
-        }
-
-        // TODO(rnystrom): Now that there are more than two front ends, this
-        // isn't as smart as it could be. It could try to pack all of the
-        // messages in a given location into as few errors as possible by
-        // taking only the non-colliding messages from one error. But that's
-        // weird.
-        //
-        // A cleaner model is to have each StaticError represent a unique error
-        // location. It would have an open-ended list of every message that
-        // occurs at that location, across the various front-ends, including
-        // multiple messages for the same front end. But that would change how
-        // the existing static error tests look since something like:
-        //
-        //     // ^^^
-        //     // [cfe] Message 1.
-        //     // ^^^
-        //     // [cfe] Message 2.
-        //
-        // Would turn into:
-        //
-        //     // ^^^
-        //     // [cfe] Message 1.
-        //     // [cfe] Message 2.
-        //
-        // That's a good change to do, but should probably wait until after
-        // NNBD.
-
-        // Merge the two errors.
-        result[i] = StaticError({...a._errors, ...b._errors},
-            line: a.line, column: a.column, length: a.length ?? b.length);
-
-        // Continue trying to merge this merged error with more since multiple
-        // errors might collapse into a single one.
-        result.removeAt(j);
-        a = result[i];
-        j--;
-      }
-    }
-
-    return result;
-  }
-
   /// Determines whether all [actualErrors] match the given [expectedErrors].
   ///
   /// If they match, returns `null`. Otherwise returns a string describing the
@@ -185,88 +101,118 @@
   /// An expected error that is completely identical to an actual error is
   /// treated as a match. Everything else is a failure.
   ///
-  /// It treats line number as the "identity" of an error. So if there are two
-  /// errors on the same line that differ in other properties, it reports that
-  /// as a "wrong" error. Any expected error on a line containing no actual
-  /// error is reported as a "missing" error. Conversely, an actual error on a
-  /// line containing no expected error is an "unexpected" error.
+  /// It has a few heuristics to try to determine what the discrepancies mean,
+  /// which it applies in order:
   ///
-  /// By not treating the error's index in the list to be its identity, we
-  /// gracefully handle extra or missing errors without causing cascading
-  /// failures for later errors in the lists.
+  /// *   If it sees an actual errors with the same message but different
+  ///     location as expected ones, it considers those to be the same error
+  ///     but with the wrong location.
+  ///
+  /// *   If it sees an actual errors at the same location as expected ones,
+  ///     it considers those to be wrong messages.
+  ///
+  /// *   Otherwise, any remaining expected errors are considered missing
+  ///     errors and remaining actual errors are considered unexpected.
   static String validateExpectations(Iterable<StaticError> expectedErrors,
       Iterable<StaticError> actualErrors) {
-    // Don't require the test or front end to output in any specific order.
-    var sortedExpected = expectedErrors.toList();
-    var sortedActual = actualErrors.toList();
-    sortedExpected.sort();
-    sortedActual.sort();
+    var expected = expectedErrors.toList();
+    var actual = actualErrors.toList();
+
+    // Put them in a deterministic order.
+    expected.sort();
+    actual.sort();
+
+    // Discard matched errors.
+    for (var i = 0; i < expected.length; i++) {
+      var matchedExpected = false;
+
+      for (var j = 0; j < actual.length; j++) {
+        if (actual[j] == null) continue;
+
+        if (expected[i]._matchLocation(actual[j]) == null &&
+            (expected[i].message == actual[j].message ||
+                !expected[i].isSpecified)) {
+          actual[j] = null;
+          matchedExpected = true;
+
+          // If the expected error is unspecified, keep going so that it can
+          // consume multiple errors on the same line.
+          if (expected[i].isSpecified) break;
+        }
+      }
+
+      if (matchedExpected) expected[i] = null;
+    }
+
+    expected.removeWhere((error) => error == null);
+    actual.removeWhere((error) => error == null);
+
+    // If everything matched, we're done.
+    if (expected.isEmpty && actual.isEmpty) return null;
 
     var buffer = StringBuffer();
 
-    describeError(String adjective, StaticError error, String verb) {
-      buffer.writeln("$adjective static error at ${error.location}:");
-
-      for (var source in ErrorSource.all) {
-        var sourceError = error._errors[source];
-        if (sourceError == _unspecified) {
-          buffer.writeln("- $verb unspecified ${source.name} error.");
-        } else if (sourceError != null) {
-          buffer.writeln("- $verb ${source.name} error '$sourceError'.");
-        }
+    void fail(StaticError error, String label, [String secondary]) {
+      if (error.isSpecified) {
+        buffer.writeln("- $label ${error.location}: ${error.message}");
+      } else {
+        label = label.replaceAll("error", "unspecified error");
+        buffer.writeln("- $label ${error.location}.");
       }
 
+      if (secondary != null) buffer.writeln("  $secondary");
       buffer.writeln();
     }
 
-    var expectedIndex = 0;
-    var actualIndex = 0;
-    for (;
-        expectedIndex < sortedExpected.length &&
-            actualIndex < sortedActual.length;) {
-      var expected = sortedExpected[expectedIndex];
-      var actual = sortedActual[actualIndex];
+    // Look for matching messages, which means a wrong location.
+    for (var i = 0; i < expected.length; i++) {
+      if (expected[i] == null) continue;
 
-      var differences = expected.describeDifferences(actual);
-      if (differences == null) {
-        // Consume this actual error.
-        actualIndex++;
+      for (var j = 0; j < actual.length; j++) {
+        if (actual[j] == null) continue;
 
-        // Consume the expectation, unless it's an unspecified error that can
-        // match more actual errors.
-        if (expected.isSpecifiedFor(actual) ||
-            actualIndex == sortedActual.length ||
-            sortedActual[actualIndex].line != expected.line) {
-          expectedIndex++;
+        if (expected[i].message == actual[j].message) {
+          fail(expected[i], "Wrong error location",
+              expected[i]._matchLocation(actual[j]));
+
+          // Only report this mismatch once.
+          expected[i] = null;
+          actual[j] = null;
+          break;
         }
-      } else if (expected.line == actual.line) {
-        buffer.writeln("Wrong static error at ${expected.location}:");
-        for (var difference in differences) {
-          buffer.writeln("- $difference");
-        }
-        buffer.writeln();
-        expectedIndex++;
-        actualIndex++;
-      } else if (expected.line < actual.line) {
-        describeError("Missing", expected, "Expected");
-        expectedIndex++;
-      } else {
-        describeError("Unexpected", actual, "Had");
-        actualIndex++;
       }
     }
 
-    // Output any trailing expected or actual errors if the lengths of the
-    // lists differ.
-    for (; expectedIndex < sortedExpected.length; expectedIndex++) {
-      describeError("Missing", sortedExpected[expectedIndex], "Expected");
+    // Look for matching locations, which means a wrong message.
+    for (var i = 0; i < expected.length; i++) {
+      if (expected[i] == null) continue;
+      for (var j = 0; j < actual.length; j++) {
+        if (actual[j] == null) continue;
+
+        if (expected[i]._matchLocation(actual[j]) == null) {
+          fail(actual[j], "Wrong message at",
+              "Expected: ${expected[i].message}");
+
+          // Only report this mismatch once.
+          expected[i] = null;
+          actual[j] = null;
+          break;
+        }
+      }
     }
 
-    for (; actualIndex < sortedActual.length; actualIndex++) {
-      describeError("Unexpected", sortedActual[actualIndex], "Had");
+    // Any remaining expected errors are missing.
+    for (var i = 0; i < expected.length; i++) {
+      if (expected[i] == null) continue;
+      fail(expected[i], "Missing expected error at");
     }
 
-    if (buffer.isEmpty) return null;
+    // Any remaining actual errors are unexpected.
+    for (var j = 0; j < actual.length; j++) {
+      if (actual[j] == null) continue;
+      fail(actual[j], "Unexpected error at");
+    }
+
     return buffer.toString().trimRight();
   }
 
@@ -281,27 +227,18 @@
   /// This is optional. The CFE only reports error location, but not length.
   final int length;
 
-  /// The error messages that should be or were reported by each front end.
-  final Map<ErrorSource, String> _errors;
+  /// The front end this error is for.
+  final ErrorSource source;
 
-  /// Whether this static error exists for [source].
-  bool hasError(ErrorSource source) => _errors.containsKey(source);
+  final String message;
 
-  /// The error for [source] or `null` if this error isn't expected to
-  /// reported by that source.
-  String errorFor(ErrorSource source) => _errors[source];
-
-  /// The zero-based index of the first line in the [TestFile] containing the
-  /// marker comments that define this error.
+  /// The zero-based numbers of the lines in the [TestFile] containing comments
+  /// that were parsed to produce this error.
   ///
-  /// If this error was not parsed from a file, this may be `null`.
-  final int markerStartLine;
-
-  /// The zero-based index of the last line in the [TestFile] containing the
-  /// marker comments that define this error, inclusive.
-  ///
-  /// If this error was not parsed from a file, this may be `null`.
-  final int markerEndLine;
+  /// This includes a line for the location comment, as well as lines for the
+  /// error message. Note that lines may not be contiguous and multiple errors
+  /// may share the same line number for a shared location marker.
+  final Set<int> sourceLines;
 
   /// Creates a new StaticError at the given location with the given expected
   /// error code and message.
@@ -312,19 +249,12 @@
   /// code or message be the special string "unspecified". When an unspecified
   /// error is tested, a front end is expected to report *some* error on that
   /// error's line, but it can be any location, error code, or message.
-  StaticError(Map<ErrorSource, String> errors,
-      {this.line,
-      this.column,
-      this.length,
-      this.markerStartLine,
-      this.markerEndLine})
-      : _errors = errors {
+  StaticError(this.source, this.message,
+      {this.line, this.column, this.length, Set<int> sourceLines})
+      : sourceLines = {...?sourceLines} {
     // Must have a location.
     assert(line != null);
     assert(column != null);
-
-    // Must have at least one piece of description.
-    assert(_errors.isNotEmpty);
   }
 
   /// A textual description of this error's location.
@@ -334,37 +264,33 @@
     return result;
   }
 
+  /// True if this error has a specific expected message and location.
+  ///
+  /// Otherwise, it is an "unspecified error", which means that as long as some
+  /// actual error is reported on this error's line, then the expectation is
+  /// met.
+  bool get isSpecified => message != _unspecified;
+
   /// Whether this error is only considered a warning on all front ends that
   /// report it.
   bool get isWarning {
-    var analyzer = _errors[ErrorSource.analyzer];
-    if (analyzer != null && !_analyzerWarningCodes.contains(analyzer)) {
-      return false;
+    switch (source) {
+      case ErrorSource.analyzer:
+        return _analyzerWarningCodes.contains(message);
+      case ErrorSource.cfe:
+        // TODO(42787): Once CFE starts reporting warnings, encode that in the
+        // message somehow and then look for it here.
+        return false;
+      case ErrorSource.web:
+        // TODO(rnystrom): If the web compilers report warnings, encode that in
+        // the message somehow and then look for it here.
+        return false;
     }
 
-    // TODO(42787): Once CFE starts reporting warnings, encode that in the
-    // message somehow and then look for it here.
-    if (hasError(ErrorSource.cfe)) return false;
-
-    // TODO(rnystrom): If the web compilers report warnings, encode that in the
-    // message somehow and then look for it here.
-    if (hasError(ErrorSource.web)) return false;
-
-    return true;
+    throw FallThroughError();
   }
 
-  String toString() {
-    var result = "Error at $location";
-
-    for (var source in ErrorSource.all) {
-      var sourceError = _errors[source];
-      if (sourceError != null) {
-        result += "\n[${source.name.toLowerCase()}] $sourceError";
-      }
-    }
-
-    return result;
-  }
+  String toString() => "[${source.marker} $location] $message";
 
   /// Orders errors primarily by location, then by other fields if needed.
   @override
@@ -377,15 +303,11 @@
     if (length != null && other.length == null) return -1;
     if (length != other.length) return length.compareTo(other.length);
 
-    for (var source in ErrorSource.all) {
-      var thisError = _errors[source] ?? "";
-      var otherError = other._errors[source] ?? "";
-      if (thisError != otherError) {
-        return thisError.compareTo(otherError);
-      }
+    if (source != other.source) {
+      return source.marker.compareTo(other.source.marker);
     }
 
-    return 0;
+    return message.compareTo(other.message);
   }
 
   @override
@@ -396,73 +318,43 @@
       3 * line.hashCode +
       5 * column.hashCode +
       7 * (length ?? 0).hashCode +
-      11 * (_errors[ErrorSource.analyzer] ?? "").hashCode +
-      13 * (_errors[ErrorSource.cfe] ?? "").hashCode +
-      17 * (_errors[ErrorSource.web] ?? "").hashCode;
+      11 * source.hashCode +
+      13 * message.hashCode;
 
-  /// Whether this error expectation is a specified error for the front end
-  /// reported by [actual].
-  bool isSpecifiedFor(StaticError actual) {
-    assert(actual._errors.length == 1,
-        "Actual error should only have one source.");
-
-    for (var source in ErrorSource.all) {
-      if (actual.hasError(source)) {
-        return hasError(source) && _errors[source] != _unspecified;
-      }
-    }
-
-    return false;
-  }
-
-  /// Compares this error expectation to [actual].
+  /// Returns a string describing how this error's expected location differs
+  /// from [actual], or `null` if [actual]'s location matches this one.
   ///
-  /// If this error correctly matches [actual], returns `null`. Otherwise
-  /// returns a list of strings describing the mismatch.
-  ///
-  /// Note that this does *not* check to see that [actual] matches the front
-  /// ends that this error expects. For example, if [actual] only reports an
-  /// analyzer error and this error only specifies a CFE error, this will still
-  /// report differences in location information. This method expects that error
-  /// expectations have already been filtered by platform so this will only be
-  /// called in cases where the platforms do match.
-  List<String> describeDifferences(StaticError actual) {
-    var differences = <String>[];
+  /// Takes into account unspecified errors and errors without lengths.
+  String _matchLocation(StaticError actual) {
+    var expectedMismatches = <String>[];
+    var actualMismatches = <String>[];
 
     if (line != actual.line) {
-      differences.add("Expected on line $line but was on ${actual.line}.");
+      expectedMismatches.add("line $line");
+      actualMismatches.add("line ${actual.line}");
     }
 
-    // If the error is unspecified on the front end being tested, the column
-    // and length can be any values.
-    if (isSpecifiedFor(actual)) {
+    // Ignore column and length for unspecified errors.
+    if (isSpecified) {
       if (column != actual.column) {
-        differences
-            .add("Expected on column $column but was on ${actual.column}.");
+        expectedMismatches.add("column $column");
+        actualMismatches.add("column ${actual.column}");
       }
 
-      // This error represents an expectation, so should have a length.
-      assert(length != null);
       if (actual.length != null && length != actual.length) {
-        differences.add("Expected length $length but was ${actual.length}.");
+        expectedMismatches.add("length $column");
+        actualMismatches.add("length ${actual.column}");
       }
     }
 
-    for (var source in ErrorSource.all) {
-      var error = _errors[source];
-      var actualError = actual._errors[source];
-
-      if (error != null &&
-          error != _unspecified &&
-          actualError != null &&
-          error != actualError) {
-        differences.add("Expected ${source.name} error '$error' "
-            "but was '$actualError'.");
-      }
+    if (expectedMismatches.isEmpty) {
+      // Everything matches.
+      return null;
     }
 
-    if (differences.isNotEmpty) return differences;
-    return null;
+    var expectedList = expectedMismatches.join(", ");
+    var actualList = actualMismatches.join(", ");
+    return "Expected $expectedList but was $actualList.";
   }
 }
 
@@ -500,9 +392,6 @@
   /// are part of it.
   static final _errorMessageRestRegExp = RegExp(r"^\s*//\s*(.*)");
 
-  /// Matches the multitest marker and yields the preceding content.
-  final _stripMultitestRegExp = RegExp(r"(.*)//#");
-
   final List<String> _lines;
   final List<StaticError> _errors = [];
   int _currentLine = 0;
@@ -522,7 +411,7 @@
           _fail("An error expectation must follow some code.");
         }
 
-        _parseErrorMessages(
+        _parseErrors(
             line: _lastRealLine,
             column: sourceLine.indexOf("^") + 1,
             length: match.group(1).length);
@@ -532,7 +421,7 @@
 
       match = _explicitLocationAndLengthRegExp.firstMatch(sourceLine);
       if (match != null) {
-        _parseErrorMessages(
+        _parseErrors(
             line: int.parse(match.group(1)),
             column: int.parse(match.group(2)),
             length: int.parse(match.group(3)));
@@ -542,7 +431,7 @@
 
       match = _explicitLocationRegExp.firstMatch(sourceLine);
       if (match != null) {
-        _parseErrorMessages(
+        _parseErrors(
             line: int.parse(match.group(1)), column: int.parse(match.group(2)));
         _advance();
         continue;
@@ -555,12 +444,12 @@
     return _errors;
   }
 
-  /// Finishes parsing an error expectation after parsing the location.
-  void _parseErrorMessages({int line, int column, int length}) {
-    var errors = <ErrorSource, String>{};
+  /// Finishes parsing a series of error expectations after parsing a location.
+  void _parseErrors({int line, int column, int length}) {
+    var locationLine = _currentLine;
+    var parsedError = false;
 
-    var startLine = _currentLine;
-
+    // Allow errors for multiple front-ends to share the same location marker.
     while (_canPeek(1)) {
       var match = _errorMessageRegExp.firstMatch(_peek(1));
       if (match == null) break;
@@ -571,6 +460,7 @@
 
       var message = match.group(2);
       _advance();
+      var sourceLines = {locationLine, _currentLine};
 
       // Consume as many additional error message lines as we find.
       while (_canPeek(1)) {
@@ -589,6 +479,7 @@
 
         message += "\n" + messageMatch.group(1);
         _advance();
+        sourceLines.add(_currentLine);
       }
 
       if (source == ErrorSource.analyzer &&
@@ -596,35 +487,28 @@
         _fail("An analyzer error expectation should be a dotted identifier.");
       }
 
-      if (errors.containsKey(source)) {
-        _fail("Already have an error for ${source.name}:\n${errors[source]}");
+      // Hack: If the error is CFE-only and the length is one, treat it as no
+      // length. The CFE does not output length information, and when the update
+      // tool writes a CFE-only error, it implicitly uses a length of one. Thus,
+      // when we parse back in a length one CFE error, we ignore the length so
+      // that the error round-trips correctly.
+      // TODO(rnystrom): Stop doing this when the CFE reports error lengths.
+      var errorLength = length;
+      if (errorLength == 1 && source == ErrorSource.cfe) {
+        errorLength = null;
       }
 
-      errors[source] = message;
+      _errors.add(StaticError(source, message,
+          line: line,
+          column: column,
+          length: errorLength,
+          sourceLines: sourceLines));
+      parsedError = true;
     }
 
-    if (errors.isEmpty) {
+    if (!parsedError) {
       _fail("An error expectation must specify at least one error message.");
     }
-
-    // Hack: If the error is CFE-only and the length is one, treat it as no
-    // length. The CFE does not output length information, and when the update
-    // tool writes a CFE-only error, it implicitly uses a length of one. Thus,
-    // when we parse back in a length one CFE error, we ignore the length so
-    // that the error round-trips correctly.
-    // TODO(rnystrom): Stop doing this when the CFE reports error lengths.
-    if (length == 1 &&
-        errors.length == 1 &&
-        errors.containsKey(ErrorSource.cfe)) {
-      length = null;
-    }
-
-    _errors.add(StaticError(errors,
-        line: line,
-        column: column,
-        length: length,
-        markerStartLine: startLine,
-        markerEndLine: _currentLine));
   }
 
   bool _canPeek(int offset) => _currentLine + offset < _lines.length;
@@ -637,9 +521,9 @@
     var line = _lines[_currentLine + offset];
 
     // Strip off any multitest marker.
-    var multitestMatch = _stripMultitestRegExp.firstMatch(line);
-    if (multitestMatch != null) {
-      line = multitestMatch.group(1).trimRight();
+    var index = line.indexOf("//#");
+    if (index != -1) {
+      line = line.substring(0, index).trimRight();
     }
 
     return line;
diff --git a/pkg/test_runner/lib/src/test_file.dart b/pkg/test_runner/lib/src/test_file.dart
index 01afcd9..7b92bc9 100644
--- a/pkg/test_runner/lib/src/test_file.dart
+++ b/pkg/test_runner/lib/src/test_file.dart
@@ -78,7 +78,7 @@
   /// These tests exist to validate that a Dart web compiler reports the right
   /// expected errors.
   bool get isWebStaticErrorTest =>
-      expectedErrors.any((error) => error.hasError(ErrorSource.web));
+      expectedErrors.any((error) => error.source == ErrorSource.web);
 
   /// If the tests has no static error expectations, or all of the expectations
   /// are warnings, then the test tests runtime semantics.
diff --git a/pkg/test_runner/lib/src/update_errors.dart b/pkg/test_runner/lib/src/update_errors.dart
index a961ea9a..5003a52 100644
--- a/pkg/test_runner/lib/src/update_errors.dart
+++ b/pkg/test_runner/lib/src/update_errors.dart
@@ -18,39 +18,43 @@
     {Set<ErrorSource> remove}) {
   remove ??= {};
 
+  // Split the existing errors into kept and deleted lists.
   var existingErrors = StaticError.parseExpectations(source);
+  var keptErrors = <StaticError>[];
+  var removedErrors = <StaticError>[];
+  for (var error in existingErrors) {
+    if (remove.contains(error.source)) {
+      removedErrors.add(error);
+    } else {
+      keptErrors.add(error);
+    }
+  }
+
   var lines = source.split("\n");
 
   // Keep track of the indentation on any existing expectation markers. If
   // found, it will try to preserve that indentation.
   var indentation = <int, int>{};
 
-  // Remove existing markers that should be removed.
-  var preservedErrors = <StaticError>[];
+  // Remove all existing marker comments in the file, even for errors we are
+  // preserving. We will regenerate marker comments for those errors too so
+  // they can properly share location comments with new errors if needed.
   for (var error in existingErrors) {
-    for (var i = error.markerStartLine; i <= error.markerEndLine; i++) {
-      indentation[i] = _countIndentation(lines[i]);
+    for (var line in error.sourceLines) {
+      if (lines[line] == null) continue;
+
+      indentation[line] = _countIndentation(lines[line]);
 
       // Null the line instead of removing it so that line numbers in the
       // reported errors are still correct.
-      lines[i] = null;
-    }
-
-    // Re-add errors for the portions we intend to preserve.
-    var keptErrors = {
-      for (var source in ErrorSource.all.toSet().difference(remove))
-        if (error.hasError(source)) source: error.errorFor(source)
-    };
-
-    if (keptErrors.isNotEmpty) {
-      preservedErrors.add(StaticError(keptErrors,
-          line: error.line, column: error.column, length: error.length));
+      lines[line] = null;
     }
   }
 
   // Merge the new errors with the preserved ones.
-  errors = StaticError.simplify([...errors, ...preservedErrors]);
+  errors = [...errors, ...keptErrors];
 
+  // Group errors by the line where they appear.
   var errorMap = <int, List<StaticError>>{};
   for (var error in errors) {
     // -1 to translate from one-based to zero-based index.
@@ -63,6 +67,7 @@
     errorList.sort();
   }
 
+  // Rebuild the source file a line at a time.
   var previousIndent = 0;
   var codeLine = 1;
   var result = <String>[];
@@ -82,6 +87,10 @@
     // Add expectations for any errors reported on this line.
     var errorsHere = errorMap[i];
     if (errorsHere == null) continue;
+
+    var previousColumn = -1;
+    var previousLength = -1;
+
     for (var error in errorsHere) {
       // Try to indent the line nicely to match either the existing expectation
       // that is being regenerated, or, barring that, the previous line of code.
@@ -90,44 +99,50 @@
       // If the error is to the left of the indent and the "//", sacrifice the
       // indentation.
       if (error.column - 1 < indent + 2) indent = 0;
-
       var comment = (" " * indent) + "//";
 
-      // If the error can't fit in a line comment, or no source location is
-      // sepcified, use an explicit location.
-      if (error.column <= 2 || error.length == 0) {
-        if (error.length == null) {
-          result.add("$comment [error line $codeLine, column "
-              "${error.column}]");
+      // Write the location line, unless we already have an identical one. Allow
+      // sharing locations between errors with and without explicit lengths.
+      if (error.column != previousColumn ||
+          (previousLength != null &&
+              error.length != null &&
+              error.length != previousLength)) {
+        // If the error can't fit in a line comment, or no source location is
+        // sepcified, use an explicit location.
+        if (error.column <= 2 || error.length == 0) {
+          if (error.length == null) {
+            result.add("$comment [error line $codeLine, column "
+                "${error.column}]");
+          } else {
+            result.add("$comment [error line $codeLine, column "
+                "${error.column}, length ${error.length}]");
+          }
         } else {
-          result.add("$comment [error line $codeLine, column "
-              "${error.column}, length ${error.length}]");
+          var spacing = " " * (error.column - 1 - 2 - indent);
+          // A CFE-only error may not have a length, so treat it as length 1.
+          var carets = "^" * (error.length ?? 1);
+          result.add("$comment$spacing$carets");
         }
-      } else {
-        var spacing = " " * (error.column - 1 - 2 - indent);
-        // A CFE-only error may not have a length, so treat it as length 1.
-        var carets = "^" * (error.length ?? 1);
-        result.add("$comment$spacing$carets");
       }
 
-      for (var source in ErrorSource.all) {
-        var sourceError = error.errorFor(source);
-        if (sourceError == null) continue;
+      // If multiple errors share the same location, let them share a location
+      // marker.
+      previousColumn = error.column;
+      previousLength = error.length;
 
-        var errorLines = sourceError.split("\n");
-        result.add("$comment [${source.marker}] ${errorLines[0]}");
-        for (var errorLine in errorLines.skip(1)) {
-          result.add("$comment $errorLine");
-        }
+      var errorLines = error.message.split("\n");
+      result.add("$comment [${error.source.marker}] ${errorLines[0]}");
+      for (var errorLine in errorLines.skip(1)) {
+        result.add("$comment $errorLine");
+      }
 
-        // If the very next line in the source is a line comment, it would
-        // become part of the inserted message. To prevent that, insert a blank
-        // line.
-        if (i < lines.length - 1 &&
-            lines[i + 1] != null &&
-            _lineCommentRegExp.hasMatch(lines[i + 1])) {
-          result.add("");
-        }
+      // If the very next line in the source is a line comment, it would
+      // become part of the inserted message. To prevent that, insert a blank
+      // line.
+      if (i < lines.length - 1 &&
+          lines[i + 1] != null &&
+          _lineCommentRegExp.hasMatch(lines[i + 1])) {
+        result.add("");
       }
     }
   }
diff --git a/pkg/test_runner/test/static_error_test.dart b/pkg/test_runner/test/static_error_test.dart
index a3e9897..891db234 100644
--- a/pkg/test_runner/test/static_error_test.dart
+++ b/pkg/test_runner/test/static_error_test.dart
@@ -9,74 +9,36 @@
 import 'utils.dart';
 
 void main() {
-  testHasError();
-  testErrorFor();
+  testProperties();
   testIsWarning();
-  testIsSpecifiedFor();
   testCompareTo();
-  testDescribeDifferences();
-  testSimplify();
+  // testDescribeDifferences();
   testValidate();
 }
 
-void testHasError() {
-  var analyzer =
-      makeError(line: 1, column: 2, length: 3, analyzerError: "E.CODE");
-  var cfe = makeError(line: 1, column: 2, length: 3, cfeError: "Error.");
-  var web = makeError(line: 1, column: 2, length: 3, webError: "Web.");
-  var all = makeError(
-      line: 1,
-      column: 2,
-      length: 3,
-      analyzerError: "E.CODE",
-      cfeError: "Error.",
-      webError: "Web.");
+void testProperties() {
+  var analyzer = StaticError(ErrorSource.analyzer, "E.CODE",
+      line: 1, column: 2, length: 3, sourceLines: {1, 3, 5});
+  Expect.equals(analyzer.source, ErrorSource.analyzer);
+  Expect.equals(analyzer.message, "E.CODE");
+  Expect.equals(analyzer.line, 1);
+  Expect.equals(analyzer.column, 2);
+  Expect.equals(analyzer.length, 3);
+  Expect.isTrue(analyzer.isSpecified);
+  Expect.setEquals({1, 3, 5}, analyzer.sourceLines);
 
-  Expect.isTrue(analyzer.hasError(ErrorSource.analyzer));
-  Expect.isFalse(analyzer.hasError(ErrorSource.cfe));
-  Expect.isFalse(analyzer.hasError(ErrorSource.web));
+  var cfe = StaticError(ErrorSource.cfe, "Error.", line: 4, column: 5);
+  Expect.equals(cfe.source, ErrorSource.cfe);
+  Expect.equals(cfe.message, "Error.");
+  Expect.equals(cfe.line, 4);
+  Expect.equals(cfe.column, 5);
+  Expect.isNull(cfe.length);
+  Expect.isTrue(cfe.isSpecified);
+  Expect.isTrue(cfe.sourceLines.isEmpty);
 
-  Expect.isFalse(cfe.hasError(ErrorSource.analyzer));
-  Expect.isTrue(cfe.hasError(ErrorSource.cfe));
-  Expect.isFalse(cfe.hasError(ErrorSource.web));
-
-  Expect.isFalse(web.hasError(ErrorSource.analyzer));
-  Expect.isFalse(web.hasError(ErrorSource.cfe));
-  Expect.isTrue(web.hasError(ErrorSource.web));
-
-  Expect.isTrue(all.hasError(ErrorSource.analyzer));
-  Expect.isTrue(all.hasError(ErrorSource.cfe));
-  Expect.isTrue(all.hasError(ErrorSource.web));
-}
-
-void testErrorFor() {
-  var analyzer =
-      makeError(line: 1, column: 2, length: 3, analyzerError: "E.CODE");
-  var cfe = makeError(line: 1, column: 2, length: 3, cfeError: "Error.");
-  var web = makeError(line: 1, column: 2, length: 3, webError: "Web.");
-  var all = makeError(
-      line: 1,
-      column: 2,
-      length: 3,
-      analyzerError: "E.CODE",
-      cfeError: "Error.",
-      webError: "Web.");
-
-  Expect.equals("E.CODE", analyzer.errorFor(ErrorSource.analyzer));
-  Expect.isNull(analyzer.errorFor(ErrorSource.cfe));
-  Expect.isNull(analyzer.errorFor(ErrorSource.web));
-
-  Expect.isNull(cfe.errorFor(ErrorSource.analyzer));
-  Expect.equals("Error.", cfe.errorFor(ErrorSource.cfe));
-  Expect.isNull(cfe.errorFor(ErrorSource.web));
-
-  Expect.isNull(web.errorFor(ErrorSource.analyzer));
-  Expect.isNull(web.errorFor(ErrorSource.cfe));
-  Expect.equals("Web.", web.errorFor(ErrorSource.web));
-
-  Expect.equals("E.CODE", all.errorFor(ErrorSource.analyzer));
-  Expect.equals("Error.", all.errorFor(ErrorSource.cfe));
-  Expect.equals("Web.", all.errorFor(ErrorSource.web));
+  var unspecified =
+      StaticError(ErrorSource.web, "unspecified", line: 1, column: 2);
+  Expect.isFalse(unspecified.isSpecified);
 }
 
 void testIsWarning() {
@@ -95,180 +57,29 @@
 
   // Web only.
   Expect.isFalse(makeError(webError: "Any error message.").isWarning);
-
-  // Multiple front ends.
-  Expect.isFalse(makeError(
-          analyzerError: "STATIC_WARNING.INVALID_OPTION",
-          cfeError: "Any error message.")
-      .isWarning);
-  Expect.isFalse(
-      makeError(cfeError: "Any error message.", webError: "Any error message.")
-          .isWarning);
-  Expect.isFalse(makeError(
-          analyzerError: "STATIC_WARNING.INVALID_OPTION",
-          webError: "Any error message.")
-      .isWarning);
-  Expect.isFalse(makeError(
-          analyzerError: "STATIC_WARNING.INVALID_OPTION",
-          cfeError: "Any error message.",
-          webError: "Any error message.")
-      .isWarning);
-}
-
-void testIsSpecifiedFor() {
-  var specifiedAll = makeError(
-      line: 1,
-      column: 2,
-      length: 3,
-      analyzerError: "ERR.CODE",
-      cfeError: "Message.",
-      webError: "Web.");
-  var unspecifiedAll = makeError(
-      line: 1,
-      column: 2,
-      length: 3,
-      analyzerError: "unspecified",
-      cfeError: "unspecified",
-      webError: "unspecified");
-  var specifiedAnalyzer = makeError(
-      line: 1,
-      column: 2,
-      length: 3,
-      analyzerError: "ERR.CODE",
-      cfeError: "unspecified",
-      webError: "unspecified");
-  var specifiedCfe = makeError(
-      line: 1,
-      column: 2,
-      length: 3,
-      analyzerError: "unspecified",
-      cfeError: "Message.",
-      webError: "unspecified");
-  var specifiedWeb = makeError(
-      line: 1,
-      column: 2,
-      length: 3,
-      analyzerError: "unspecified",
-      cfeError: "unspecified",
-      webError: "Web.");
-
-  var specifiedAnalyzerOnly =
-      makeError(line: 1, column: 2, length: 3, analyzerError: "ERR.CODE");
-  var specifiedCfeOnly =
-      makeError(line: 1, column: 2, length: 3, cfeError: "Message.");
-  var specifiedWebOnly =
-      makeError(line: 1, column: 2, length: 3, webError: "Web.");
-
-  var unspecifiedAnalyzerOnly =
-      makeError(line: 1, column: 2, length: 3, analyzerError: "unspecified");
-  var unspecifiedCfeOnly =
-      makeError(line: 1, column: 2, length: 3, cfeError: "unspecified");
-  var unspecifiedWebOnly =
-      makeError(line: 1, column: 2, length: 3, webError: "unspecified");
-
-  var analyzer =
-      makeError(line: 1, column: 2, length: 3, analyzerError: "E.CODE");
-  var cfe = makeError(line: 1, column: 2, length: 3, cfeError: "E.");
-  var web = makeError(line: 1, column: 2, length: 3, webError: "E.");
-
-  // isSpecifiedFor().
-  Expect.isTrue(specifiedAll.isSpecifiedFor(analyzer));
-  Expect.isTrue(specifiedAll.isSpecifiedFor(cfe));
-  Expect.isTrue(specifiedAll.isSpecifiedFor(web));
-
-  Expect.isFalse(unspecifiedAll.isSpecifiedFor(analyzer));
-  Expect.isFalse(unspecifiedAll.isSpecifiedFor(cfe));
-  Expect.isFalse(unspecifiedAll.isSpecifiedFor(web));
-
-  Expect.isTrue(specifiedAnalyzer.isSpecifiedFor(analyzer));
-  Expect.isFalse(specifiedAnalyzer.isSpecifiedFor(cfe));
-  Expect.isFalse(specifiedAnalyzer.isSpecifiedFor(web));
-
-  Expect.isFalse(specifiedCfe.isSpecifiedFor(analyzer));
-  Expect.isTrue(specifiedCfe.isSpecifiedFor(cfe));
-  Expect.isFalse(specifiedCfe.isSpecifiedFor(web));
-
-  Expect.isFalse(specifiedWeb.isSpecifiedFor(analyzer));
-  Expect.isFalse(specifiedWeb.isSpecifiedFor(cfe));
-  Expect.isTrue(specifiedWeb.isSpecifiedFor(web));
-
-  Expect.isTrue(specifiedAnalyzerOnly.isSpecifiedFor(analyzer));
-  Expect.isFalse(specifiedAnalyzerOnly.isSpecifiedFor(cfe));
-  Expect.isFalse(specifiedAnalyzerOnly.isSpecifiedFor(web));
-
-  Expect.isFalse(specifiedCfeOnly.isSpecifiedFor(analyzer));
-  Expect.isTrue(specifiedCfeOnly.isSpecifiedFor(cfe));
-  Expect.isFalse(specifiedCfeOnly.isSpecifiedFor(web));
-
-  Expect.isFalse(specifiedWebOnly.isSpecifiedFor(analyzer));
-  Expect.isFalse(specifiedWebOnly.isSpecifiedFor(cfe));
-  Expect.isTrue(specifiedWebOnly.isSpecifiedFor(web));
-
-  Expect.isFalse(unspecifiedAnalyzerOnly.isSpecifiedFor(analyzer));
-  Expect.isFalse(unspecifiedAnalyzerOnly.isSpecifiedFor(cfe));
-  Expect.isFalse(unspecifiedAnalyzerOnly.isSpecifiedFor(web));
-
-  Expect.isFalse(unspecifiedCfeOnly.isSpecifiedFor(analyzer));
-  Expect.isFalse(unspecifiedCfeOnly.isSpecifiedFor(cfe));
-  Expect.isFalse(unspecifiedCfeOnly.isSpecifiedFor(web));
-
-  Expect.isFalse(unspecifiedWebOnly.isSpecifiedFor(analyzer));
-  Expect.isFalse(unspecifiedWebOnly.isSpecifiedFor(cfe));
-  Expect.isFalse(unspecifiedWebOnly.isSpecifiedFor(web));
 }
 
 void testCompareTo() {
   var errors = [
     // Order by line.
-    makeError(
-        line: 1, column: 2, length: 2, analyzerError: "E.CODE", cfeError: "E."),
-    makeError(
-        line: 2, column: 1, length: 1, analyzerError: "E.CODE", cfeError: "E."),
+    makeError(line: 1, column: 2, length: 2, cfeError: "E."),
+    makeError(line: 2, column: 1, length: 1, cfeError: "E."),
 
     // Then column.
-    makeError(
-        line: 3, column: 1, length: 2, analyzerError: "E.CODE", cfeError: "E."),
-    makeError(
-        line: 3,
-        column: 2,
-        length: 1,
-        analyzerError: "Error.CODE",
-        cfeError: "E."),
+    makeError(line: 3, column: 1, length: 2, cfeError: "E."),
+    makeError(line: 3, column: 2, length: 1, cfeError: "E."),
 
     // Then length.
-    makeError(
-        line: 4, column: 1, length: 1, analyzerError: "Z.CODE", cfeError: "Z."),
-    makeError(
-        line: 4, column: 1, length: 2, analyzerError: "A.CODE", cfeError: "A."),
+    makeError(line: 4, column: 1, length: 1, cfeError: "Z."),
+    makeError(line: 4, column: 1, length: 2, cfeError: "A."),
 
-    // Then analyzer error.
-    makeError(line: 5, column: 1, length: 1, cfeError: "Z."),
-    makeError(
-        line: 5, column: 1, length: 1, analyzerError: "A.CODE", cfeError: "Z."),
-    makeError(
-        line: 5, column: 1, length: 1, analyzerError: "Z.CODE", cfeError: "Z."),
+    // Then source.
+    makeError(line: 5, column: 1, length: 1, analyzerError: "Z.CODE"),
+    makeError(line: 5, column: 1, length: 1, cfeError: "A."),
 
-    // Then CFE error.
-    makeError(line: 6, column: 1, length: 1, analyzerError: "E.CODE"),
-    makeError(
-        line: 6,
-        column: 1,
-        length: 1,
-        analyzerError: "E.CODE",
-        cfeError: "A.",
-        webError: "Z."),
-    makeError(
-        line: 6,
-        column: 1,
-        length: 1,
-        analyzerError: "E.CODE",
-        cfeError: "Z.",
-        webError: "A."),
-
-    // Then web error.
-    makeError(line: 7, column: 1, length: 1, cfeError: "E."),
-    makeError(line: 7, column: 1, length: 1, cfeError: "E.", webError: "A."),
-    makeError(line: 7, column: 1, length: 1, cfeError: "E.", webError: "Z."),
+    // Then message.
+    makeError(line: 6, column: 1, length: 1, cfeError: "A."),
+    makeError(line: 6, column: 1, length: 1, cfeError: "Z."),
   ];
 
   // Every pair of errors in the array should be ordered correctly.
@@ -281,347 +92,15 @@
   }
 }
 
-void testDescribeDifferences() {
-  var precise = makeError(
-      line: 2,
-      column: 3,
-      length: 4,
-      analyzerError: "Error.CODE",
-      cfeError: "Error message.",
-      webError: "Web error.");
-
-  // Perfect match.
-  expectNoDifferences(precise,
-      makeError(line: 2, column: 3, length: 4, analyzerError: "Error.CODE"));
-  expectNoDifferences(precise,
-      makeError(line: 2, column: 3, length: 4, cfeError: "Error message."));
-  expectNoDifferences(precise,
-      makeError(line: 2, column: 3, length: 4, webError: "Web error."));
-
-  // Ignore null analyzer error.
-  expectNoDifferences(
-      makeError(
-          line: 2,
-          column: 3,
-          length: 4,
-          cfeError: "Error message.",
-          webError: "Web error."),
-      makeError(line: 2, column: 3, length: 4, cfeError: "Error message."));
-
-  // Ignore null CFE error.
-  expectNoDifferences(
-      makeError(
-          line: 2,
-          column: 3,
-          length: 4,
-          analyzerError: "Error.CODE",
-          webError: "Web error."),
-      makeError(line: 2, column: 3, length: 4, analyzerError: "Error.CODE"));
-
-  // Ignore null web error.
-  expectNoDifferences(
-      makeError(
-          line: 2,
-          column: 3,
-          length: 4,
-          analyzerError: "Error.CODE",
-          cfeError: "Error message."),
-      makeError(line: 2, column: 3, length: 4, cfeError: "Error message."));
-
-  // Different line.
-  expectDifferences(precise,
-      makeError(line: 4, column: 3, length: 4, analyzerError: "Error.CODE"), """
-  Expected on line 2 but was on 4.
-  """);
-
-  // Different column.
-  expectDifferences(precise,
-      makeError(line: 2, column: 5, length: 4, cfeError: "Error message."), """
-  Expected on column 3 but was on 5.
-  """);
-
-  // Different length.
-  expectDifferences(precise,
-      makeError(line: 2, column: 3, length: 6, webError: "Web error."), """
-  Expected length 4 but was 6.
-  """);
-
-  // Different analyzer error.
-  expectDifferences(
-      precise,
-      makeError(line: 2, column: 3, length: 4, analyzerError: "Weird.ERROR"),
-      """
-  Expected analyzer error 'Error.CODE' but was 'Weird.ERROR'.
-  """);
-
-  // Different CFE error.
-  expectDifferences(precise,
-      makeError(line: 2, column: 3, length: 4, cfeError: "Funny story."), """
-  Expected CFE error 'Error message.' but was 'Funny story.'.
-  """);
-
-  // Different web error.
-  expectDifferences(precise,
-      makeError(line: 2, column: 3, length: 4, webError: "Funny story."), """
-  Expected web error 'Web error.' but was 'Funny story.'.
-  """);
-
-  // Multiple differences.
-  expectDifferences(
-      precise,
-      makeError(line: 4, column: 3, length: 6, analyzerError: "Weird.ERROR"),
-      """
-  Expected on line 2 but was on 4.
-  Expected length 4 but was 6.
-  Expected analyzer error 'Error.CODE' but was 'Weird.ERROR'.
-  """);
-
-  // Unspecified errors.
-  var unspecified = makeError(
-      line: 2,
-      column: 3,
-      length: 4,
-      analyzerError: "unspecified",
-      cfeError: "unspecified",
-      webError: "unspecified");
-  var specifiedAnalyzer = makeError(
-      line: 2,
-      column: 3,
-      length: 4,
-      analyzerError: "Error.CODE",
-      cfeError: "unspecified",
-      webError: "unspecified");
-  var specifiedCfe = makeError(
-      line: 2,
-      column: 3,
-      length: 4,
-      analyzerError: "unspecified",
-      cfeError: "Error message.",
-      webError: "unspecified");
-  var specifiedWeb = makeError(
-      line: 2,
-      column: 3,
-      length: 4,
-      analyzerError: "unspecified",
-      cfeError: "unspecified",
-      webError: "Web error.");
-
-  // Matches if line is right.
-  expectNoDifferences(unspecified,
-      makeError(line: 2, column: 3, length: 4, analyzerError: "Error.CODE"));
-
-  // Does not match if lines differ.
-  expectDifferences(unspecified,
-      makeError(line: 3, column: 3, length: 4, cfeError: "Error message."), """
-  Expected on line 2 but was on 3.
-  """);
-
-  // If error is specified on analyzer, must match fields when actual is
-  // analyzer error.
-  expectDifferences(
-      specifiedAnalyzer,
-      makeError(line: 2, column: 5, length: 6, analyzerError: "Weird.ERROR"),
-      """
-  Expected on column 3 but was on 5.
-  Expected length 4 but was 6.
-  Expected analyzer error 'Error.CODE' but was 'Weird.ERROR'.
-  """);
-  expectNoDifferences(specifiedAnalyzer,
-      makeError(line: 2, column: 3, length: 4, analyzerError: "Error.CODE"));
-
-  // If error is specified on CFE, must match fields when actual is
-  // CFE error.
-  expectDifferences(
-      specifiedCfe,
-      makeError(line: 2, column: 5, length: 6, cfeError: "Different message."),
-      """
-  Expected on column 3 but was on 5.
-  Expected length 4 but was 6.
-  Expected CFE error 'Error message.' but was 'Different message.'.
-  """);
-  expectNoDifferences(specifiedCfe,
-      makeError(line: 2, column: 3, length: 4, cfeError: "Error message."));
-
-  // If error is specified on web, must match fields when actual is web error.
-  expectDifferences(
-      specifiedWeb,
-      makeError(line: 2, column: 5, length: 6, webError: "Different message."),
-      """
-  Expected on column 3 but was on 5.
-  Expected length 4 but was 6.
-  Expected web error 'Web error.' but was 'Different message.'.
-  """);
-  expectNoDifferences(specifiedWeb,
-      makeError(line: 2, column: 3, length: 4, webError: "Web error."));
-}
-
-void testSimplify() {
-  // Merges errors if each has only one error.
-  expectSimplify([
-    makeError(line: 1, column: 2, length: 3, analyzerError: "Weird.ERROR"),
-    makeError(line: 1, column: 2, length: 3, cfeError: "Message."),
-    makeError(line: 1, column: 2, length: 3, webError: "Web.")
-  ], [
-    makeError(
-        line: 1,
-        column: 2,
-        length: 3,
-        analyzerError: "Weird.ERROR",
-        cfeError: "Message.",
-        webError: "Web.")
-  ]);
-
-  // Merges if length is null.
-  expectSimplify([
-    makeError(line: 1, column: 1, analyzerError: "A.ERR"),
-    makeError(line: 1, column: 1, length: 3, cfeError: "A."),
-    makeError(line: 2, column: 1, length: 4, analyzerError: "B.ERR"),
-    makeError(line: 2, column: 1, webError: "B."),
-    makeError(line: 3, column: 1, analyzerError: "C.ERR"),
-    makeError(line: 3, column: 1, cfeError: "C."),
-  ], [
-    makeError(
-        line: 1, column: 1, length: 3, analyzerError: "A.ERR", cfeError: "A."),
-    makeError(
-        line: 2, column: 1, length: 4, analyzerError: "B.ERR", webError: "B."),
-    makeError(line: 3, column: 1, analyzerError: "C.ERR", cfeError: "C."),
-  ]);
-
-  // Merges multiple errors with no length with errors that have length.
-  expectSimplify([
-    makeError(line: 1, column: 2, length: 3, analyzerError: "ERROR.A"),
-    makeError(line: 1, column: 4, length: 3, analyzerError: "ERROR.C"),
-    makeError(line: 1, column: 2, length: 5, analyzerError: "ERROR.B"),
-    makeError(line: 1, column: 2, cfeError: "One."),
-    makeError(line: 1, column: 4, cfeError: "Three."),
-    makeError(line: 1, column: 2, cfeError: "Two."),
-    makeError(line: 1, column: 2, webError: "Web 1."),
-    makeError(line: 1, column: 2, webError: "Web 2."),
-  ], [
-    makeError(
-        line: 1,
-        column: 2,
-        length: 3,
-        analyzerError: "ERROR.A",
-        cfeError: "One.",
-        webError: "Web 1."),
-    makeError(
-        line: 1,
-        column: 2,
-        length: 5,
-        analyzerError: "ERROR.B",
-        cfeError: "Two.",
-        webError: "Web 2."),
-    makeError(
-        line: 1,
-        column: 4,
-        length: 3,
-        analyzerError: "ERROR.C",
-        cfeError: "Three."),
-  ]);
-
-  // Merges even if not adjacent in input array.
-  expectSimplify([
-    makeError(line: 1, column: 2, length: 3, analyzerError: "Some.ERROR"),
-    makeError(line: 10, column: 2, length: 3, analyzerError: "Other.ERROR"),
-    makeError(line: 1, column: 2, length: 3, cfeError: "Message."),
-    makeError(line: 10, column: 2, length: 3, webError: "Web two."),
-    makeError(line: 1, column: 2, length: 3, webError: "Web."),
-  ], [
-    makeError(
-        line: 1,
-        column: 2,
-        length: 3,
-        analyzerError: "Some.ERROR",
-        cfeError: "Message.",
-        webError: "Web."),
-    makeError(
-        line: 10,
-        column: 2,
-        length: 3,
-        analyzerError: "Other.ERROR",
-        webError: "Web two.")
-  ]);
-
-  // Does not merge if positions differ.
-  expectSimplify([
-    makeError(line: 1, column: 1, length: 1, analyzerError: "A.ERR"),
-    makeError(line: 2, column: 1, length: 1, cfeError: "A."),
-  ], [
-    makeError(line: 1, column: 1, length: 1, analyzerError: "A.ERR"),
-    makeError(line: 2, column: 1, length: 1, cfeError: "A."),
-  ]);
-  expectSimplify([
-    makeError(line: 1, column: 1, length: 1, analyzerError: "A.ERR"),
-    makeError(line: 1, column: 2, length: 1, webError: "A."),
-  ], [
-    makeError(line: 1, column: 1, length: 1, analyzerError: "A.ERR"),
-    makeError(line: 1, column: 2, length: 1, webError: "A."),
-  ]);
-  expectSimplify([
-    makeError(line: 1, column: 1, length: 1, cfeError: "A."),
-    makeError(line: 1, column: 1, length: 2, webError: "W."),
-  ], [
-    makeError(line: 1, column: 1, length: 1, cfeError: "A."),
-    makeError(line: 1, column: 1, length: 2, webError: "W."),
-  ]);
-
-  // Does not merge if it would lose a message.
-  expectSimplify([
-    makeError(line: 1, column: 1, length: 1, analyzerError: "ERR.ONE"),
-    makeError(line: 1, column: 1, length: 1, analyzerError: "ERR.TWO"),
-    makeError(line: 2, column: 1, length: 1, cfeError: "One."),
-    makeError(line: 2, column: 1, length: 1, cfeError: "Two."),
-    makeError(line: 3, column: 1, length: 1, webError: "One."),
-    makeError(line: 3, column: 1, length: 1, webError: "Two."),
-  ], [
-    makeError(line: 1, column: 1, length: 1, analyzerError: "ERR.ONE"),
-    makeError(line: 1, column: 1, length: 1, analyzerError: "ERR.TWO"),
-    makeError(line: 2, column: 1, length: 1, cfeError: "One."),
-    makeError(line: 2, column: 1, length: 1, cfeError: "Two."),
-    makeError(line: 3, column: 1, length: 1, webError: "One."),
-    makeError(line: 3, column: 1, length: 1, webError: "Two."),
-  ]);
-
-  // Orders output.
-  expectSimplify([
-    makeError(line: 2, column: 1, length: 1, cfeError: "Two."),
-    makeError(line: 3, column: 1, length: 1, cfeError: "Three."),
-    makeError(line: 1, column: 1, length: 1, cfeError: "One."),
-  ], [
-    makeError(line: 1, column: 1, length: 1, cfeError: "One."),
-    makeError(line: 2, column: 1, length: 1, cfeError: "Two."),
-    makeError(line: 3, column: 1, length: 1, cfeError: "Three."),
-  ]);
-}
-
 void testValidate() {
   // No errors.
   expectValidate([], [], null);
 
   // Same errors.
   expectValidate([
-    makeError(
-        line: 1,
-        column: 2,
-        length: 3,
-        analyzerError: "ERR.A",
-        cfeError: "One.",
-        webError: "Web 1."),
-    makeError(
-        line: 2,
-        column: 2,
-        length: 3,
-        analyzerError: "ERR.B",
-        cfeError: "Two.",
-        webError: "Web 2."),
-    makeError(
-        line: 3,
-        column: 2,
-        length: 3,
-        analyzerError: "ERR.C",
-        cfeError: "Tres.",
-        webError: "Web 3."),
+    makeError(line: 1, column: 2, length: 3, analyzerError: "ERR.A"),
+    makeError(line: 2, column: 2, length: 3, analyzerError: "ERR.B"),
+    makeError(line: 3, column: 2, length: 3, analyzerError: "ERR.C"),
   ], [
     // Order doesn't matter.
     makeError(line: 3, column: 2, length: 3, analyzerError: "ERR.C"),
@@ -629,35 +108,6 @@
     makeError(line: 2, column: 2, length: 3, analyzerError: "ERR.B"),
   ], null);
 
-  // Ignore fields that aren't in actual errors.
-  expectValidate([
-    makeError(
-        line: 1,
-        column: 2,
-        length: 3,
-        analyzerError: "ERR.A",
-        cfeError: "One.",
-        webError: "Web 1."),
-    makeError(
-        line: 2,
-        column: 2,
-        length: 3,
-        analyzerError: "ERR.B",
-        cfeError: "Two.",
-        webError: "Web 2."),
-    makeError(
-        line: 3,
-        column: 2,
-        length: 3,
-        analyzerError: "ERR.C",
-        cfeError: "Tres.",
-        webError: "Web 3."),
-  ], [
-    makeError(line: 1, column: 2, cfeError: "One."),
-    makeError(line: 2, column: 2, length: 3, cfeError: "Two."),
-    makeError(line: 3, column: 2, length: 3, cfeError: "Tres."),
-  ], null);
-
   // Catches differences in any field.
   expectValidate([
     makeError(line: 1, column: 2, length: 3, analyzerError: "ERR.A"),
@@ -668,52 +118,36 @@
     makeError(line: 2, column: 2, length: 9, analyzerError: "ERR.B"),
     makeError(line: 3, column: 2, length: 3, analyzerError: "ERR.Z"),
   ], """
-Wrong static error at line 1, column 2, length 3:
-- Expected on column 2 but was on 9.
+- Wrong error location line 1, column 2, length 3: ERR.A
+  Expected column 2 but was column 9.
 
-Wrong static error at line 2, column 2, length 3:
-- Expected length 3 but was 9.
+- Wrong error location line 2, column 2, length 3: ERR.B
+  Expected length 2 but was length 2.
 
-Wrong static error at line 3, column 2, length 3:
-- Expected analyzer error 'ERR.C' but was 'ERR.Z'.""");
+- Wrong message at line 3, column 2, length 3: ERR.Z
+  Expected: ERR.C""");
 
   expectValidate([
     makeError(line: 4, column: 2, length: 3, cfeError: "Four."),
   ], [
     makeError(line: 4, column: 2, length: 3, cfeError: "Zzz."),
   ], """
-Wrong static error at line 4, column 2, length 3:
-- Expected CFE error 'Four.' but was 'Zzz.'.""");
+- Wrong message at line 4, column 2, length 3: Zzz.
+  Expected: Four.""");
 
   expectValidate([
     makeError(line: 5, column: 2, length: 3, webError: "Web 5."),
   ], [
     makeError(line: 5, column: 2, length: 3, webError: "Web Z."),
   ], """
-Wrong static error at line 5, column 2, length 3:
-- Expected web error 'Web 5.' but was 'Web Z.'.""");
+- Wrong message at line 5, column 2, length 3: Web Z.
+  Expected: Web 5.""");
 
   // Unexpected errors.
   expectValidate([
-    makeError(
-        line: 2,
-        column: 2,
-        length: 3,
-        analyzerError: "ERR.A",
-        cfeError: "One."),
-    makeError(
-        line: 4,
-        column: 2,
-        length: 3,
-        analyzerError: "ERR.B",
-        cfeError: "Two.",
-        webError: "Web 2."),
-    makeError(
-        line: 6,
-        column: 2,
-        length: 3,
-        analyzerError: "ERR.C",
-        cfeError: "Tres."),
+    makeError(line: 2, column: 2, length: 3, cfeError: "One."),
+    makeError(line: 4, column: 2, length: 3, cfeError: "Two."),
+    makeError(line: 6, column: 2, length: 3, cfeError: "Tres."),
   ], [
     makeError(line: 1, column: 2, length: 3, cfeError: "1."),
     makeError(line: 2, column: 2, length: 3, cfeError: "One."),
@@ -723,17 +157,13 @@
     makeError(line: 6, column: 2, length: 3, cfeError: "Tres."),
     makeError(line: 7, column: 2, length: 3, cfeError: "7."),
   ], """
-Unexpected static error at line 1, column 2, length 3:
-- Had CFE error '1.'.
+- Unexpected error at line 1, column 2, length 3: 1.
 
-Unexpected static error at line 3, column 2, length 3:
-- Had CFE error '3.'.
+- Unexpected error at line 3, column 2, length 3: 3.
 
-Unexpected static error at line 5, column 2, length 3:
-- Had CFE error '5.'.
+- Unexpected error at line 5, column 2, length 3: 5.
 
-Unexpected static error at line 7, column 2, length 3:
-- Had CFE error '7.'.""");
+- Unexpected error at line 7, column 2, length 3: 7.""");
 
   // Missing errors.
   expectValidate([
@@ -746,14 +176,11 @@
     makeError(line: 2, column: 2, length: 3, analyzerError: "ERR.B"),
     makeError(line: 4, column: 2, length: 3, analyzerError: "ERR.D"),
   ], """
-Missing static error at line 1, column 2, length 3:
-- Expected analyzer error 'ERR.A'.
+- Missing expected error at line 1, column 2, length 3: ERR.A
 
-Missing static error at line 3, column 2, length 3:
-- Expected analyzer error 'ERR.C'.
+- Missing expected error at line 3, column 2, length 3: ERR.C
 
-Missing static error at line 5, column 2, length 3:
-- Expected analyzer error 'ERR.E'.""");
+- Missing expected error at line 5, column 2, length 3: ERR.E""");
 
   // Unspecified errors.
   expectValidate([
@@ -768,161 +195,34 @@
     // Unexpected.
     makeError(line: 9, column: 9, length: 3, cfeError: "Actual 2."),
   ], """
-Missing static error at line 2, column 2, length 3:
-- Expected unspecified CFE error.
+- Missing expected unspecified error at line 2, column 2, length 3.
 
-Unexpected static error at line 9, column 9, length 3:
-- Had CFE error 'Actual 2.'.""");
+- Unexpected error at line 9, column 9, length 3: Actual 2.""");
 
   // Unspecified errors can match multiple errors on the same line.
-  var actualAnalyzer = [
+  expectValidate([
+    makeError(line: 1, column: 2, length: 3, analyzerError: "unspecified"),
+  ], [
     makeError(line: 1, column: 1, length: 3, analyzerError: "ERROR.CODE1"),
     makeError(line: 1, column: 2, length: 3, analyzerError: "ERROR.CODE2"),
     makeError(line: 1, column: 3, length: 3, analyzerError: "ERROR.CODE3"),
-  ];
-
-  var actualCfe = [
-    makeError(line: 1, column: 1, length: 3, cfeError: "Actual 1."),
-    makeError(line: 1, column: 2, length: 3, cfeError: "Actual 2."),
-    makeError(line: 1, column: 3, length: 3, cfeError: "Actual 3."),
-  ];
-
-  var actualWeb = [
-    makeError(line: 1, column: 1, length: 3, webError: "Web 1."),
-    makeError(line: 1, column: 2, length: 3, webError: "Web 2."),
-    makeError(line: 1, column: 3, length: 3, webError: "Web 3."),
-  ];
-
-  // Unspecified error specific to one front end.
-  expectValidate([
-    makeError(line: 1, column: 2, length: 3, analyzerError: "unspecified"),
-  ], actualAnalyzer, null);
+  ], null);
 
   expectValidate([
     makeError(line: 1, column: 2, length: 3, cfeError: "unspecified"),
-  ], actualCfe, null);
+  ], [
+    makeError(line: 1, column: 1, length: 3, cfeError: "Actual 1."),
+    makeError(line: 1, column: 2, length: 3, cfeError: "Actual 2."),
+    makeError(line: 1, column: 3, length: 3, cfeError: "Actual 3."),
+  ], null);
 
   expectValidate([
     makeError(line: 1, column: 2, length: 3, webError: "unspecified"),
-  ], actualWeb, null);
-
-  // Unspecified error on multiple front ends.
-  expectValidate([
-    makeError(
-        line: 1,
-        column: 2,
-        length: 3,
-        analyzerError: "unspecified",
-        cfeError: "unspecified"),
-  ], actualAnalyzer, null);
-
-  expectValidate([
-    makeError(
-        line: 1,
-        column: 2,
-        length: 3,
-        cfeError: "unspecified",
-        webError: "unspecified"),
-  ], actualCfe, null);
-
-  expectValidate([
-    makeError(
-        line: 1,
-        column: 2,
-        length: 3,
-        analyzerError: "unspecified",
-        webError: "unspecified"),
-  ], actualWeb, null);
-
-  expectValidate([
-    makeError(
-        line: 1,
-        column: 2,
-        length: 3,
-        analyzerError: "unspecified",
-        cfeError: "unspecified",
-        webError: "unspecified"),
-  ], actualAnalyzer, null);
-
-  // Specified on one, unspecified on another, no error at all on the third.
-  var specifiedAnalyzer = [
-    makeError(
-        line: 1,
-        column: 1,
-        length: 3,
-        analyzerError: "ERROR.CODE1",
-        cfeError: "unspecified")
-  ];
-
-  var specifiedCfe = [
-    makeError(
-        line: 1,
-        column: 1,
-        length: 3,
-        cfeError: "Actual 1.",
-        webError: "unspecified")
-  ];
-
-  var specifiedWeb = [
-    makeError(
-        line: 1,
-        column: 1,
-        length: 3,
-        analyzerError: "unspecified",
-        webError: "Web 1.")
-  ];
-
-  expectValidate(specifiedAnalyzer, actualCfe, null);
-  expectValidate(specifiedCfe, actualWeb, null);
-  expectValidate(specifiedWeb, actualAnalyzer, null);
-
-  expectValidate(specifiedAnalyzer, actualAnalyzer, """
-Unexpected static error at line 1, column 2, length 3:
-- Had analyzer error 'ERROR.CODE2'.
-
-Unexpected static error at line 1, column 3, length 3:
-- Had analyzer error 'ERROR.CODE3'.""");
-
-  expectValidate(specifiedCfe, actualCfe, """
-Unexpected static error at line 1, column 2, length 3:
-- Had CFE error 'Actual 2.'.
-
-Unexpected static error at line 1, column 3, length 3:
-- Had CFE error 'Actual 3.'.""");
-
-  expectValidate(specifiedWeb, actualWeb, """
-Unexpected static error at line 1, column 2, length 3:
-- Had web error 'Web 2.'.
-
-Unexpected static error at line 1, column 3, length 3:
-- Had web error 'Web 3.'.""");
-}
-
-void expectNoDifferences(StaticError expectedError, StaticError actualError) {
-  var actualLines = expectedError.describeDifferences(actualError);
-  if (actualLines != null) {
-    Expect.fail("Expected no differences, but got:\n${actualLines.join('\n')}");
-  }
-}
-
-void expectDifferences(StaticError expectedError, StaticError actualError,
-    String expectedDifferences) {
-  var expectedLines = expectedDifferences
-      .split("\n")
-      .map((line) => line.trim())
-      .where((line) => line.isNotEmpty)
-      .toList();
-  var actualLines = expectedError.describeDifferences(actualError);
-  if (actualLines == null) {
-    Expect.fail("Got no differences, but expected:\n$expectedDifferences");
-  }
-  Expect.listEquals(expectedLines, actualLines);
-}
-
-void expectSimplify(List<StaticError> input, List<StaticError> expected) {
-  var actual = StaticError.simplify(input);
-  Expect.listEquals(expected.map((error) => error.toString()).toList(),
-      actual.map((error) => error.toString()).toList());
+  ], [
+    makeError(line: 1, column: 1, length: 3, webError: "Web 1."),
+    makeError(line: 1, column: 2, length: 3, webError: "Web 2."),
+    makeError(line: 1, column: 3, length: 3, webError: "Web 3."),
+  ], null);
 }
 
 void expectValidate(List<StaticError> expected, List<StaticError> actual,
diff --git a/pkg/test_runner/test/test_file_test.dart b/pkg/test_runner/test/test_file_test.dart
index 251f60d..1e793ef 100644
--- a/pkg/test_runner/test/test_file_test.dart
+++ b/pkg/test_runner/test/test_file_test.dart
@@ -299,6 +299,7 @@
 /\/      ^^^
 /\/ [analyzer] CompileTimeErrorCode.WRONG_TYPE
 /\/ [cfe] Error: Can't assign a string to an int.
+/\/ [cfe] Another CFE error.
 /\/ [web] Web-specific error.
 
 num j = "str";
@@ -311,16 +312,25 @@
         line: 1,
         column: 9,
         length: 3,
-        analyzerError: "CompileTimeErrorCode.WRONG_TYPE",
-        cfeError: "Error: Can't assign a string to an int.",
-        webError: "Web-specific error."),
+        analyzerError: "CompileTimeErrorCode.WRONG_TYPE"),
     makeError(
-        line: 7,
+        line: 1,
+        column: 9,
+        length: 3,
+        cfeError: "Error: Can't assign a string to an int."),
+    makeError(line: 1, column: 9, length: 3, cfeError: "Another CFE error."),
+    makeError(line: 1, column: 9, length: 3, webError: "Web-specific error."),
+    makeError(
+        line: 8,
         column: 9,
         length: 5,
-        analyzerError: "CompileTimeErrorCode.ALSO_WRONG_TYPE",
-        cfeError: "Error: Can't assign a string to a num.",
-        webError: "Another web error.")
+        analyzerError: "CompileTimeErrorCode.ALSO_WRONG_TYPE"),
+    makeError(
+        line: 8,
+        column: 9,
+        length: 5,
+        cfeError: "Error: Can't assign a string to a num."),
+    makeError(line: 8, column: 9, length: 5, webError: "Another web error.")
   ]);
 
   // Explicit error location.
@@ -341,15 +351,15 @@
         line: 123,
         column: 45,
         length: 678,
-        analyzerError: "CompileTimeErrorCode.FIRST",
-        cfeError: "First error."),
+        analyzerError: "CompileTimeErrorCode.FIRST"),
+    makeError(line: 123, column: 45, length: 678, cfeError: "First error."),
     makeError(
         line: 23,
         column: 5,
         length: 78,
-        analyzerError: "CompileTimeErrorCode.SECOND",
-        cfeError: "Second error.",
-        webError: "Second web error."),
+        analyzerError: "CompileTimeErrorCode.SECOND"),
+    makeError(line: 23, column: 5, length: 78, cfeError: "Second error."),
+    makeError(line: 23, column: 5, length: 78, webError: "Second web error."),
     makeError(line: 9, column: 8, length: 7, cfeError: "Third."),
     makeError(line: 10, column: 9, cfeError: "No length.")
   ]);
@@ -372,8 +382,16 @@
         line: 1,
         column: 9,
         length: 3,
-        analyzerError: "CompileTimeErrorCode.WRONG_TYPE",
-        cfeError: "First line.\nSecond line.\nThird line.",
+        analyzerError: "CompileTimeErrorCode.WRONG_TYPE"),
+    makeError(
+        line: 1,
+        column: 9,
+        length: 3,
+        cfeError: "First line.\nSecond line.\nThird line."),
+    makeError(
+        line: 1,
+        column: 9,
+        length: 3,
         webError: "Web first line.\nWeb second line.\nWeb third line.")
   ]);
 
@@ -418,25 +436,13 @@
 /\/     ^^^
 // [web] unspecified
 """, [
-    makeError(
-        line: 1,
-        column: 8,
-        length: 3,
-        analyzerError: "unspecified",
-        cfeError: "unspecified",
-        webError: "unspecified"),
-    makeError(
-        line: 6,
-        column: 8,
-        length: 3,
-        analyzerError: "unspecified",
-        cfeError: "Message."),
-    makeError(
-        line: 10,
-        column: 8,
-        length: 3,
-        analyzerError: "Error.CODE",
-        cfeError: "unspecified"),
+    makeError(line: 1, column: 8, length: 3, analyzerError: "unspecified"),
+    makeError(line: 1, column: 8, length: 3, cfeError: "unspecified"),
+    makeError(line: 1, column: 8, length: 3, webError: "unspecified"),
+    makeError(line: 6, column: 8, length: 3, analyzerError: "unspecified"),
+    makeError(line: 6, column: 8, length: 3, cfeError: "Message."),
+    makeError(line: 10, column: 8, length: 3, analyzerError: "Error.CODE"),
+    makeError(line: 10, column: 8, length: 3, cfeError: "unspecified"),
     makeError(line: 14, column: 8, length: 3, analyzerError: "unspecified"),
     makeError(line: 17, column: 8, length: 3, cfeError: "unspecified"),
     makeError(line: 20, column: 8, length: 3, webError: "unspecified"),
@@ -453,11 +459,9 @@
 /\/ [cfe] Message.
 """, [
     makeError(
-        line: 1,
-        column: 9,
-        length: 3,
-        analyzerError: "ErrorCode.BAD_THING",
-        cfeError: "Message.\nMore message."),
+        line: 1, column: 9, length: 3, analyzerError: "ErrorCode.BAD_THING"),
+    makeError(
+        line: 1, column: 9, length: 3, cfeError: "Message.\nMore message."),
     makeError(line: 12, column: 34, length: 56, cfeError: "Message."),
   ]);
 
@@ -468,12 +472,9 @@
 /\/ [cfe] Error message.
 /\/ [analyzer] ErrorCode.BAD_THING
 """, [
+    makeError(line: 1, column: 9, length: 3, cfeError: "Error message."),
     makeError(
-        line: 1,
-        column: 9,
-        length: 3,
-        analyzerError: "ErrorCode.BAD_THING",
-        cfeError: "Error message."),
+        line: 1, column: 9, length: 3, analyzerError: "ErrorCode.BAD_THING"),
   ]);
   expectParseErrorExpectations("""
 int i = "s";
@@ -481,12 +482,9 @@
 /\/ [web] Web message.
 /\/ [analyzer] ErrorCode.BAD_THING
 """, [
+    makeError(line: 1, column: 9, length: 3, webError: "Web message."),
     makeError(
-        line: 1,
-        column: 9,
-        length: 3,
-        analyzerError: "ErrorCode.BAD_THING",
-        webError: "Web message."),
+        line: 1, column: 9, length: 3, analyzerError: "ErrorCode.BAD_THING"),
   ]);
   expectParseErrorExpectations("""
 int i = "s";
@@ -494,12 +492,8 @@
 /\/ [web] Web message.
 /\/ [cfe] Error message.
 """, [
-    makeError(
-        line: 1,
-        column: 9,
-        length: 3,
-        cfeError: "Error message.",
-        webError: "Web message."),
+    makeError(line: 1, column: 9, length: 3, webError: "Web message."),
+    makeError(line: 1, column: 9, length: 3, cfeError: "Error message."),
   ]);
 
   // Must have at least one error message.
@@ -538,7 +532,7 @@
 /\/ [analyzer] Not error code.
 """);
 
-  // A CFE-only error with length one is treated as having no length.
+  // A CFE error with length one is treated as having no length.
   expectParseErrorExpectations("""
 int i = "s";
 /\/      ^
@@ -555,40 +549,11 @@
 /\/ [web] Web message.
 """, [
     makeError(line: 1, column: 9, length: null, cfeError: "Message."),
-    makeError(
-        line: 5,
-        column: 9,
-        length: 1,
-        analyzerError: "Error.BAD",
-        cfeError: "Message."),
-    makeError(
-      line: 10,
-      column: 9,
-      length: 1,
-      cfeError: "Message.",
-      webError: "Web message.",
-    ),
+    makeError(line: 5, column: 9, length: 1, analyzerError: "Error.BAD"),
+    makeError(line: 5, column: 9, length: null, cfeError: "Message."),
+    makeError(line: 10, column: 9, length: null, cfeError: "Message."),
+    makeError(line: 10, column: 9, length: 1, webError: "Web message."),
   ]);
-
-  // Cannot have the same front end more than once.
-  expectFormatError("""
-int i = "s";
-/\/      ^^^
-/\/ [analyzer] ErrorCode.BAD_THING
-/\/ [analyzer] ErrorCode.ANOTHER_THING
-""");
-  expectFormatError("""
-int i = "s";
-/\/      ^^^
-/\/ [cfe] Message 1.
-/\/ [cfe] Message 2.
-""");
-  expectFormatError("""
-int i = "s";
-/\/      ^^^
-/\/ [web] Web 1.
-/\/ [web] Web 2.
-""");
 }
 
 void testIsRuntimeTest() {
diff --git a/pkg/test_runner/test/update_errors_test.dart b/pkg/test_runner/test/update_errors_test.dart
index 6c433625..1d459d4 100644
--- a/pkg/test_runner/test/update_errors_test.dart
+++ b/pkg/test_runner/test/update_errors_test.dart
@@ -53,31 +53,15 @@
 
 int last = "oops";
 """, errors: [
-    makeError(
-        line: 1,
-        column: 9,
-        length: 5,
-        analyzerError: "some.error",
-        cfeError: "Bad."),
-    makeError(
-        line: 3,
-        column: 15,
-        length: 7,
-        cfeError: "Another bad.",
-        webError: "Web.\nError."),
-    makeError(
-        line: 5,
-        column: 13,
-        length: 5,
-        analyzerError: "third.error",
-        webError: "Web error."),
-    makeError(
-        line: 7,
-        column: 12,
-        length: 6,
-        analyzerError: "last.error",
-        cfeError: "Final.\nError.",
-        webError: "Web error."),
+    makeError(line: 1, column: 9, length: 5, analyzerError: "some.error"),
+    makeError(line: 1, column: 9, length: 5, cfeError: "Bad."),
+    makeError(line: 3, column: 15, length: 7, cfeError: "Another bad."),
+    makeError(line: 3, column: 15, length: 7, webError: "Web.\nError."),
+    makeError(line: 5, column: 13, length: 5, analyzerError: "third.error"),
+    makeError(line: 5, column: 13, length: 5, webError: "Web error."),
+    makeError(line: 7, column: 12, length: 6, analyzerError: "last.error"),
+    makeError(line: 7, column: 12, length: 6, cfeError: "Final.\nError."),
+    makeError(line: 7, column: 12, length: 6, webError: "Web error."),
   ], expected: """
 int i = "bad";
 /\/      ^^^^^
@@ -215,12 +199,9 @@
     /\/    ^^
     /\/ [analyzer] previous.error
 """, errors: [
+    makeError(line: 1, column: 9, length: 5, analyzerError: "updated.error"),
     makeError(
-        line: 1,
-        column: 9,
-        length: 5,
-        analyzerError: "updated.error",
-        cfeError: "Long.\nError.\nMessage."),
+        line: 1, column: 9, length: 5, cfeError: "Long.\nError.\nMessage."),
   ], expected: """
 int i = "bad";
     /\/  ^^^^^
@@ -389,6 +370,28 @@
 /\/ [cfe] Wrong 1.
 """);
 
+  // Shared locations between errors with and without length.
+  expectUpdate("""
+someBadCode(arg);
+
+moreBadCode(arg);
+""", errors: [
+    makeError(line: 1, column: 13, length: 3, analyzerError: "Error.CODE"),
+    makeError(line: 1, column: 13, cfeError: "Wrong 1."),
+    makeError(line: 3, column: 13, cfeError: "Wrong 2."),
+    makeError(line: 3, column: 13, length: 3, webError: "Web error."),
+  ], expected: """
+someBadCode(arg);
+/\/          ^^^
+/\/ [analyzer] Error.CODE
+/\/ [cfe] Wrong 1.
+
+moreBadCode(arg);
+/\/          ^^^
+/\/ [web] Web error.
+/\/ [cfe] Wrong 2.
+""");
+
   // Doesn't crash with RangeError.
   expectUpdate("""
 x
diff --git a/pkg/test_runner/test/utils.dart b/pkg/test_runner/test/utils.dart
index b88d7ab..2a9152d 100644
--- a/pkg/test_runner/test/utils.dart
+++ b/pkg/test_runner/test/utils.dart
@@ -26,6 +26,9 @@
         List<TestFile> testFiles, String suite) =>
     _MockTestSuite(configuration, testFiles, suite);
 
+/// Creates a [StaticError].
+///
+/// Only one of [analyzerError], [cfeError], or [webError] may be passed.
 StaticError makeError(
     {int line = 1,
     int column = 2,
@@ -33,12 +36,19 @@
     String analyzerError,
     String cfeError,
     String webError}) {
-  var errors = {
-    if (analyzerError != null) ErrorSource.analyzer: analyzerError,
-    if (cfeError != null) ErrorSource.cfe: cfeError,
-    if (webError != null) ErrorSource.web: webError,
-  };
-  return StaticError(errors, line: line, column: column, length: length);
+  if (analyzerError != null) {
+    assert(cfeError == null && webError == null);
+    return StaticError(ErrorSource.analyzer, analyzerError,
+        line: line, column: column, length: length);
+  } else if (cfeError != null) {
+    assert(webError == null);
+    return StaticError(ErrorSource.cfe, cfeError,
+        line: line, column: column, length: length);
+  } else {
+    assert(webError != null);
+    return StaticError(ErrorSource.web, webError,
+        line: line, column: column, length: length);
+  }
 }
 
 class _MockTestSuite extends StandardTestSuite {
diff --git a/pkg/test_runner/tool/update_static_error_tests.dart b/pkg/test_runner/tool/update_static_error_tests.dart
index 20ed7f5..96e521e 100644
--- a/pkg/test_runner/tool/update_static_error_tests.dart
+++ b/pkg/test_runner/tool/update_static_error_tests.dart
@@ -203,8 +203,6 @@
     }
   }
 
-  errors = StaticError.simplify(errors);
-
   var result = updateErrorExpectations(source, errors, remove: remove);
 
   stdout.writeln("\r${file.path} (Updated with ${errors.length} errors)");
@@ -291,8 +289,7 @@
       return dart2jsError.line == cfeError.line &&
           dart2jsError.column == cfeError.column &&
           dart2jsError.length == cfeError.length &&
-          dart2jsError.errorFor(ErrorSource.web) ==
-              cfeError.errorFor(ErrorSource.cfe);
+          dart2jsError.message == cfeError.message;
     });
   });
 
diff --git a/runtime/platform/utils.h b/runtime/platform/utils.h
index 788dd2a..a381505 100644
--- a/runtime/platform/utils.h
+++ b/runtime/platform/utils.h
@@ -292,6 +292,13 @@
     return static_cast<T>(static_cast<Unsigned>(a) * static_cast<Unsigned>(b));
   }
 
+  template <typename T = int64_t>
+  static inline T NegWithWrapAround(T a) {
+    // Avoid undefined behavior by doing arithmetic in the unsigned type.
+    using Unsigned = typename std::make_unsigned<T>::type;
+    return static_cast<T>(-static_cast<Unsigned>(a));
+  }
+
   // Shifts int64_t value left. Supports any non-negative number of bits and
   // silently discards shifted out bits.
   static inline int64_t ShiftLeftWithTruncation(int64_t a, int64_t b) {
diff --git a/runtime/vm/compiler/backend/loops.cc b/runtime/vm/compiler/backend/loops.cc
index 4ff2dc8..f28ae39 100644
--- a/runtime/vm/compiler/backend/loops.cc
+++ b/runtime/vm/compiler/backend/loops.cc
@@ -723,13 +723,16 @@
       // Invariant + Invariant : only for same or just one instruction.
       if (x->def_ == y->def_) {
         return new (zone_)
-            InductionVar(x->offset_ + y->offset_, x->mult_ + y->mult_, x->def_);
+            InductionVar(Utils::AddWithWrapAround(x->offset_, y->offset_),
+                         Utils::AddWithWrapAround(x->mult_, y->mult_), x->def_);
       } else if (y->mult_ == 0) {
         return new (zone_)
-            InductionVar(x->offset_ + y->offset_, x->mult_, x->def_);
+            InductionVar(Utils::AddWithWrapAround(x->offset_, y->offset_),
+                         x->mult_, x->def_);
       } else if (x->mult_ == 0) {
         return new (zone_)
-            InductionVar(x->offset_ + y->offset_, y->mult_, y->def_);
+            InductionVar(Utils::AddWithWrapAround(x->offset_, y->offset_),
+                         y->mult_, y->def_);
       }
     } else if (y != nullptr) {
       // Invariant + Induction.
@@ -768,13 +771,16 @@
       // Invariant + Invariant : only for same or just one instruction.
       if (x->def_ == y->def_) {
         return new (zone_)
-            InductionVar(x->offset_ - y->offset_, x->mult_ - y->mult_, x->def_);
+            InductionVar(Utils::SubWithWrapAround(x->offset_, y->offset_),
+                         Utils::SubWithWrapAround(x->mult_, y->mult_), x->def_);
       } else if (y->mult_ == 0) {
         return new (zone_)
-            InductionVar(x->offset_ - y->offset_, x->mult_, x->def_);
+            InductionVar(Utils::SubWithWrapAround(x->offset_, y->offset_),
+                         x->mult_, x->def_);
       } else if (x->mult_ == 0) {
         return new (zone_)
-            InductionVar(x->offset_ - y->offset_, -y->mult_, y->def_);
+            InductionVar(Utils::SubWithWrapAround(x->offset_, y->offset_),
+                         Utils::NegWithWrapAround(y->mult_), y->def_);
       }
     } else if (y != nullptr) {
       // Invariant - Induction.
@@ -823,7 +829,8 @@
   if (InductionVar::IsConstant(x) && y != nullptr) {
     if (y->kind_ == InductionVar::kInvariant) {
       return new (zone_)
-          InductionVar(x->offset_ * y->offset_, x->offset_ * y->mult_, y->def_);
+          InductionVar(Utils::MulWithWrapAround(x->offset_, y->offset_),
+                       Utils::MulWithWrapAround(x->offset_, y->mult_), y->def_);
     }
     return new (zone_)
         InductionVar(y->kind_, Mul(x, y->initial_), Mul(x, y->next_));
diff --git a/tests/co19/co19-co19.status b/tests/co19/co19-co19.status
index baf4965..d66d5fd 100644
--- a/tests/co19/co19-co19.status
+++ b/tests/co19/co19-co19.status
@@ -4,4 +4,3 @@
 
 LibTest/ffi/Array/PointerArray_A01_t01: Skip # https://github.com/dart-lang/co19/issues/1018
 LibTest/io/RawDatagramSocket/*: Skip # https://github.com/dart-lang/co19/issues/195
-
diff --git a/tools/VERSION b/tools/VERSION
index 2abc5be..2715580 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 180
+PRERELEASE 181
 PRERELEASE_PATCH 0
\ No newline at end of file