Version 2.15.0-164.0.dev

Merge commit 'bb4e445b6e43937fa42c9ca75a24a51203c46dfe' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/remove_unnecessary_cast.dart b/pkg/analysis_server/lib/src/services/correction/dart/remove_unnecessary_cast.dart
index 4763735..850bba2 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/remove_unnecessary_cast.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/remove_unnecessary_cast.dart
@@ -11,7 +11,7 @@
 
 class RemoveUnnecessaryCast extends CorrectionProducer {
   @override
-  bool get canBeAppliedInBulk => false;
+  bool get canBeAppliedInBulk => true;
 
   @override
   bool get canBeAppliedToFile => true;
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_missing_overrides_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_missing_overrides_test.dart
index 694ecfc..638a199 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/create_missing_overrides_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/create_missing_overrides_test.dart
@@ -168,6 +168,29 @@
 ''');
   }
 
+  Future<void> test_functionTypedParameter_dynamic() async {
+    await resolveTestCode('''
+abstract class A {
+  void m(bool test(e));
+}
+
+class B extends A {
+}
+''');
+    await assertHasFix('''
+abstract class A {
+  void m(bool test(e));
+}
+
+class B extends A {
+  @override
+  void m(bool Function(dynamic e) test) {
+    // TODO: implement m
+  }
+}
+''');
+  }
+
   Future<void> test_functionTypedParameter_nullable() async {
     await resolveTestCode('''
 abstract class A {
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_cast_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_cast_test.dart
index c0805e5..24cb208 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_cast_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_unnecessary_cast_test.dart
@@ -11,12 +11,35 @@
 
 void main() {
   defineReflectiveSuite(() {
+    defineReflectiveTests(RemoveUnnecessaryCastBulkTest);
     defineReflectiveTests(RemoveUnnecessaryCastMultiTest);
     defineReflectiveTests(RemoveUnnecessaryCastTest);
   });
 }
 
 @reflectiveTest
+class RemoveUnnecessaryCastBulkTest extends BulkFixProcessorTest {
+  Future<void> test_assignment() async {
+    await resolveTestCode('''
+void f(Object p) {
+  if (p is String) {
+    var v = (p as String) as String;
+    print(v);
+  }
+}
+''');
+    await assertHasFix('''
+void f(Object p) {
+  if (p is String) {
+    var v = p;
+    print(v);
+  }
+}
+''');
+  }
+}
+
+@reflectiveTest
 class RemoveUnnecessaryCastMultiTest extends FixProcessorTest {
   @override
   FixKind get kind => DartFixKind.REMOVE_UNNECESSARY_CAST_MULTI;
@@ -25,7 +48,7 @@
     await resolveTestCode('''
 void f(Object p, Object q) {
   if (p is String) {
-    String v = ((p as String));
+    var v = (p as String) as String;
     print(v);
   }
   if (q is int) {
@@ -37,7 +60,7 @@
     await assertHasFixAllFix(HintCode.UNNECESSARY_CAST, '''
 void f(Object p, Object q) {
   if (p is String) {
-    String v = p;
+    var v = p;
     print(v);
   }
   if (q is int) {
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index 620c43d..410a7ce 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -772,10 +772,19 @@
   void collectAffected(String path, Set<FileState> affected) {
     final knownFiles = this.knownFiles.toList();
 
+    final fileToReferences = <FileState, List<FileState>>{};
+    for (var file in knownFiles) {
+      for (var referenced in file.directReferencedFiles) {
+        var references = fileToReferences[referenced] ??= [];
+        references.add(file);
+      }
+    }
+
     collectAffected(FileState file) {
       if (affected.add(file)) {
-        for (var other in knownFiles) {
-          if (other.directReferencedFiles.contains(file)) {
+        var references = fileToReferences[file];
+        if (references != null) {
+          for (var other in references) {
             collectAffected(other);
           }
         }
diff --git a/pkg/analyzer/tool/messages/error_code_info.dart b/pkg/analyzer/tool/messages/error_code_info.dart
index 322759b..56a4947 100644
--- a/pkg/analyzer/tool/messages/error_code_info.dart
+++ b/pkg/analyzer/tool/messages/error_code_info.dart
@@ -15,6 +15,84 @@
   return result;
 }
 
+/// Data tables mapping between CFE errors and their corresponding automatically
+/// generated analyzer errors.
+class CfeToAnalyzerErrorCodeTables {
+  /// List of CFE errors for which analyzer errors should be automatically
+  /// generated, organized by their `index` property.
+  final List<ErrorCodeInfo?> indexToInfo = [];
+
+  /// Map whose values are the CFE errors for which analyzer errors should be
+  /// automatically generated, and whose keys are the corresponding analyzer
+  /// error name.  (Names are simple identifiers; they are not prefixed by the
+  /// class name `ParserErrorCode`)
+  final Map<String, ErrorCodeInfo> analyzerCodeToInfo = {};
+
+  /// Map whose values are the CFE errors for which analyzer errors should be
+  /// automatically generated, and whose keys are the front end error name.
+  final Map<String, ErrorCodeInfo> frontEndCodeToInfo = {};
+
+  /// Map whose keys are the CFE errors for which analyzer errors should be
+  /// automatically generated, and whose values are the corresponding analyzer
+  /// error name.  (Names are simple identifiers; they are not prefixed by the
+  /// class name `ParserErrorCode`)
+  final Map<ErrorCodeInfo, String> infoToAnalyzerCode = {};
+
+  /// Map whose keys are the CFE errors for which analyzer errors should be
+  /// automatically generated, and whose values are the front end error name.
+  final Map<ErrorCodeInfo, String> infoToFrontEndCode = {};
+
+  CfeToAnalyzerErrorCodeTables(Map<String, ErrorCodeInfo> messages) {
+    for (var entry in messages.entries) {
+      var errorCodeInfo = entry.value;
+      var index = errorCodeInfo.index;
+      if (index == null || errorCodeInfo.analyzerCode.length != 1) {
+        continue;
+      }
+      var frontEndCode = entry.key;
+      if (index < 1) {
+        throw '''
+$frontEndCode specifies index $index but indices must be 1 or greater.
+For more information run:
+pkg/front_end/tool/fasta generate-messages
+''';
+      }
+      if (indexToInfo.length <= index) {
+        indexToInfo.length = index + 1;
+      }
+      var previousEntryForIndex = indexToInfo[index];
+      if (previousEntryForIndex != null) {
+        throw 'Index $index used by both '
+            '${infoToFrontEndCode[previousEntryForIndex]} and $frontEndCode';
+      }
+      indexToInfo[index] = errorCodeInfo;
+      frontEndCodeToInfo[frontEndCode] = errorCodeInfo;
+      infoToFrontEndCode[errorCodeInfo] = frontEndCode;
+      var analyzerCodeLong = errorCodeInfo.analyzerCode.single;
+      var expectedPrefix = 'ParserErrorCode.';
+      if (!analyzerCodeLong.startsWith(expectedPrefix)) {
+        throw 'Expected all analyzer error codes to be prefixed with '
+            '${json.encode(expectedPrefix)}.  Found '
+            '${json.encode(analyzerCodeLong)}.';
+      }
+      var analyzerCode = analyzerCodeLong.substring(expectedPrefix.length);
+      infoToAnalyzerCode[errorCodeInfo] = analyzerCode;
+      var previousEntryForAnalyzerCode = analyzerCodeToInfo[analyzerCode];
+      if (previousEntryForAnalyzerCode != null) {
+        throw 'Analyzer code $analyzerCode used by both '
+            '${infoToFrontEndCode[previousEntryForAnalyzerCode]} and '
+            '$frontEndCode';
+      }
+      analyzerCodeToInfo[analyzerCode] = errorCodeInfo;
+    }
+    for (int i = 1; i < indexToInfo.length; i++) {
+      if (indexToInfo[i] == null) {
+        throw 'Indices are not consecutive; no error code has index $i.';
+      }
+    }
+  }
+}
+
 /// In-memory representation of error code information obtained from a
 /// `messages.yaml` file.
 class ErrorCodeInfo {
diff --git a/pkg/analyzer/tool/messages/generate.dart b/pkg/analyzer/tool/messages/generate.dart
index d9b973c..3aa0e3e 100644
--- a/pkg/analyzer/tool/messages/generate.dart
+++ b/pkg/analyzer/tool/messages/generate.dart
@@ -35,18 +35,6 @@
     ..printSummary();
 }
 
-const invalidAnalyzerCode = """
-Error: Expected the text in the 'analyzerCode:' field to contain
-       the name of the class containing the error
-       and the name of the error separated by a `.`
-       (e.g. ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND).
-""";
-
-const shouldRunFastaGenerateMessagesFirst = """
-Error: After modifying messages.yaml, run this first:
-       pkg/front_end/tool/fasta generate-messages
-""";
-
 /// A list of all targets generated by this code generator.
 final List<GeneratedContent> allTargets = <GeneratedContent>[
   GeneratedFile('lib/src/dart/error/syntactic_errors.g.dart',
@@ -62,32 +50,12 @@
 final String analyzerPkgPath =
     normalize(join(pkg_root.packageRoot, 'analyzer'));
 
-/// Return an entry containing 2 strings,
-/// the name of the class containing the error and the name of the error,
-/// or throw an exception if 'analyzerCode:' field is invalid.
-List<String> nameForEntry(ErrorCodeInfo entry) {
-  final analyzerCode = entry.analyzerCode;
-  if (analyzerCode.length == 1) {
-    var code = analyzerCode.single;
-    if (!code.startsWith('ParserErrorCode.')) {
-      throw invalidAnalyzerCode;
-    }
-    List<String> name = code.split('.');
-    if (name.length != 2 || name[1].isEmpty) {
-      throw invalidAnalyzerCode;
-    }
-    return name;
-  }
-  throw invalidAnalyzerCode;
-}
-
 class _SyntacticErrorGenerator {
   final Map<String, ErrorCodeInfo> messages;
+  final CfeToAnalyzerErrorCodeTables tables;
   final String errorConverterSource;
   final String syntacticErrorsSource;
   final String parserSource;
-  final translatedEntries = <ErrorCodeInfo>[];
-  final translatedFastaErrorCodes = <String>{};
   final out = StringBuffer('''
 //
 // THIS FILE IS GENERATED. DO NOT EDIT.
@@ -121,15 +89,14 @@
   }
 
   _SyntacticErrorGenerator._(this.messages, this.errorConverterSource,
-      this.syntacticErrorsSource, this.parserSource);
+      this.syntacticErrorsSource, this.parserSource)
+      : tables = CfeToAnalyzerErrorCodeTables(messages);
 
   void checkForManualChanges() {
     // Check for ParserErrorCodes that could be removed from
     // error_converter.dart now that those ParserErrorCodes are auto generated.
     int converterCount = 0;
-    for (ErrorCodeInfo entry in translatedEntries) {
-      final name = nameForEntry(entry);
-      final errorCode = name[1];
+    for (var errorCode in tables.infoToAnalyzerCode.values) {
       if (errorConverterSource.contains('"$errorCode"')) {
         if (converterCount == 0) {
           print('');
@@ -147,9 +114,7 @@
     // Check that the public ParserErrorCodes have been updated
     // to reference the generated codes.
     int publicCount = 0;
-    for (ErrorCodeInfo entry in translatedEntries) {
-      final name = nameForEntry(entry);
-      final errorCode = name[1];
+    for (var errorCode in tables.infoToAnalyzerCode.values) {
       if (!syntacticErrorsSource.contains(' _$errorCode')) {
         if (publicCount == 0) {
           print('');
@@ -171,22 +136,10 @@
   }
 
   void generateErrorCodes() {
-    final sortedErrorCodes = <String>[];
-    final entryMap = <String, ErrorCodeInfo>{};
-    for (var entry in translatedEntries) {
-      final name = nameForEntry(entry);
-      final errorCode = name[1];
-      sortedErrorCodes.add(errorCode);
-      if (entryMap[errorCode] == null) {
-        entryMap[errorCode] = entry;
-      } else {
-        throw 'Error: Duplicate error code $errorCode';
-      }
-    }
-    sortedErrorCodes.sort();
-    for (var errorCode in sortedErrorCodes) {
+    final entryMap = tables.analyzerCodeToInfo;
+    for (var errorCode in entryMap.keys.toList()..sort()) {
       final entry = entryMap[errorCode]!;
-      final className = nameForEntry(entry)[0];
+      final className = 'ParserErrorCode';
       out.writeln();
       out.writeln('const $className _$errorCode =');
       out.writeln(entry.toAnalyzerCode(className, errorCode));
@@ -194,37 +147,15 @@
   }
 
   void generateFastaAnalyzerErrorCodeList() {
-    final sorted = List<ErrorCodeInfo?>.filled(translatedEntries.length, null);
-    for (var entry in translatedEntries) {
-      var index = entry.index;
-      if (index is int && index >= 1 && index <= sorted.length) {
-        if (sorted[index - 1] == null) {
-          sorted[index - 1] = entry;
-          continue;
-        }
-      }
-      throw shouldRunFastaGenerateMessagesFirst;
-    }
-    out.writeln('final fastaAnalyzerErrorCodes = <ErrorCode?>[null,');
-    for (var entry in sorted) {
-      List<String> name = nameForEntry(entry!);
-      out.writeln('_${name[1]},');
+    out.writeln('final fastaAnalyzerErrorCodes = <ErrorCode?>[');
+    for (var entry in tables.indexToInfo) {
+      var name = tables.infoToAnalyzerCode[entry];
+      out.writeln('${name == null ? 'null' : '_$name'},');
     }
     out.writeln('];');
   }
 
   void generateFormatCode() {
-    messages.forEach((name, entry) {
-      if (entry.index != null) {
-        // TODO(paulberry): handle multiple analyzer codes
-        if (entry.analyzerCode.length == 1) {
-          translatedFastaErrorCodes.add(name);
-          translatedEntries.add(entry);
-        } else {
-          throw invalidAnalyzerCode;
-        }
-      }
-    });
     generateFastaAnalyzerErrorCodeList();
     generateErrorCodes();
   }
@@ -246,13 +177,13 @@
     }
 
     // Remove entries that have already been translated
-    for (ErrorCodeInfo entry in translatedEntries) {
+    for (ErrorCodeInfo entry in tables.infoToAnalyzerCode.keys) {
       messageToName.remove(messageFromEntryTemplate(entry));
     }
 
     // Print the # of autogenerated ParserErrorCodes.
-    print('${translatedEntries.length} of ${messageToName.length}'
-        ' ParserErrorCodes generated.');
+    print('${tables.infoToAnalyzerCode.length} of '
+        '${messageToName.length} ParserErrorCodes generated.');
 
     // List the ParserErrorCodes that could easily be auto generated
     // but have not been already.
@@ -298,7 +229,7 @@
           }
         }
         if (fastaErrorCode != null &&
-            !translatedFastaErrorCodes.contains(fastaErrorCode)) {
+            tables.frontEndCodeToInfo[fastaErrorCode] == null) {
           untranslatedFastaErrorCodes.add(fastaErrorCode);
         }
       }
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
index bc399fb..9b0c895 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
@@ -516,12 +516,14 @@
       ExecutableElement? methodBeingCopied,
       String? nameGroupName,
       DartType? type,
-      String? typeGroupName}) {
+      String? typeGroupName,
+      bool isRequiredType = false}) {
     bool writeType() {
       if (typeGroupName != null) {
         late bool hasType;
         addLinkedEdit(typeGroupName, (DartLinkedEditBuilder builder) {
-          hasType = _writeType(type, methodBeingCopied: methodBeingCopied);
+          hasType = _writeType(type,
+              methodBeingCopied: methodBeingCopied, required: isRequiredType);
           builder.addSuperTypesAsSuggestions(type);
         });
         return hasType;
@@ -596,7 +598,7 @@
 
   @override
   void writeParameters(Iterable<ParameterElement> parameters,
-      {ExecutableElement? methodBeingCopied}) {
+      {ExecutableElement? methodBeingCopied, bool requiredTypes = false}) {
     var parameterNames = <String>{};
     for (var i = 0; i < parameters.length; i++) {
       var name = parameters.elementAt(i).name;
@@ -639,7 +641,8 @@
           methodBeingCopied: methodBeingCopied,
           nameGroupName: parameter.isNamed ? null : '${groupPrefix}PARAM$i',
           type: parameter.type,
-          typeGroupName: '${groupPrefix}TYPE$i');
+          typeGroupName: '${groupPrefix}TYPE$i',
+          isRequiredType: requiredTypes);
       // default value
       var defaultCode = parameter.defaultValueCode;
       if (defaultCode != null) {
@@ -1233,7 +1236,8 @@
       write('Function');
       writeTypeParameters(type.typeFormals,
           methodBeingCopied: methodBeingCopied);
-      writeParameters(type.parameters, methodBeingCopied: methodBeingCopied);
+      writeParameters(type.parameters,
+          methodBeingCopied: methodBeingCopied, requiredTypes: true);
       if (type.nullabilitySuffix == NullabilitySuffix.question) {
         write('?');
       }
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
index 0707033..6bd6c78 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
@@ -201,13 +201,16 @@
   ///
   /// If [isRequiredNamed] is `true` then either the keyword `required` or the
   /// annotation `@required` will be included in the parameter declaration.
+  ///
+  /// If [isRequiredType] is `true` then the type is always written.
   void writeParameter(String name,
       {bool isCovariant,
       bool isRequiredNamed,
       ExecutableElement? methodBeingCopied,
       String? nameGroupName,
       DartType? type,
-      String? typeGroupName});
+      String? typeGroupName,
+      bool isRequiredType});
 
   /// Write the code for a parameter that would match the given [argument]. The
   /// name of the parameter will be generated based on the type of the argument,
@@ -223,8 +226,10 @@
   /// If a [methodBeingCopied] is provided, then type parameters defined by that
   /// method are assumed to be part of what is being written and hence valid
   /// types.
+  ///
+  /// If [requiredTypes] is `true`, then the types are always written.
   void writeParameters(Iterable<ParameterElement> parameters,
-      {ExecutableElement? methodBeingCopied});
+      {ExecutableElement? methodBeingCopied, bool requiredTypes});
 
   /// Write the code for a list of parameters that would match the given list of
   /// [arguments]. The surrounding parentheses are *not* written.
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
index c58eee8..047e7a1 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
@@ -1106,6 +1106,25 @@
     expect(edit.replacement, equalsIgnoringWhitespace('(int i, String s)'));
   }
 
+  Future<void> test_writeParameters_requiredTypes() async {
+    var path = convertPath('/home/test/lib/test.dart');
+    var content = 'void f(e) {}';
+    addSource(path, content);
+    var unit = (await resolveFile(path)).unit;
+    var f = unit.declarations[0] as FunctionDeclaration;
+    var parameters = f.functionExpression.parameters;
+    var elements = parameters?.parameters.map((p) => p.declaredElement!);
+
+    var builder = newBuilder();
+    await builder.addDartFileEdit(path, (builder) {
+      builder.addInsertion(content.length - 1, (builder) {
+        builder.writeParameters(elements!, requiredTypes: true);
+      });
+    });
+    var edit = getEdit(builder);
+    expect(edit.replacement, equals('(dynamic e)'));
+  }
+
   Future<void> test_writeParametersMatchingArguments_named() async {
     var path = convertPath('/home/test/lib/test.dart');
     var content = '''
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_legacy_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_legacy_test.dart
new file mode 100644
index 0000000..e124753
--- /dev/null
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_legacy_test.dart
@@ -0,0 +1,28 @@
+// 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.
+
+// @dart = 2.9
+
+import 'package:dev_compiler/dev_compiler.dart' show ModuleFormat;
+import 'package:test/test.dart';
+import 'expression_compiler_e2e_shared.dart';
+import 'expression_compiler_e2e_suite.dart';
+
+void main() async {
+  var driver = await TestDriver.init();
+
+  group('(Legacy code)', () {
+    tearDownAll(() async {
+      await driver.finish();
+    });
+
+    group('(AMD module system)', () {
+      var setup = SetupCompilerOptions(
+          soundNullSafety: false,
+          legacyCode: true,
+          moduleFormat: ModuleFormat.amd);
+      runAgnosticSharedTests(setup, driver);
+    });
+  });
+}
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_sound_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_sound_test.dart
index 9b56991..24e6238 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_sound_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_sound_test.dart
@@ -4,8 +4,6 @@
 
 // @dart = 2.9
 
-library dev_compiler.test.expression_compiler;
-
 import 'package:dev_compiler/dev_compiler.dart' show ModuleFormat;
 import 'package:test/test.dart';
 import 'expression_compiler_e2e_shared.dart';
@@ -21,8 +19,11 @@
 
     group('(AMD module system)', () {
       var setup = SetupCompilerOptions(
-          soundNullSafety: true, moduleFormat: ModuleFormat.amd);
-      runSharedTests(setup, driver);
+          soundNullSafety: true,
+          legacyCode: false,
+          moduleFormat: ModuleFormat.amd);
+      runAgnosticSharedTests(setup, driver);
+      runNullSafeSharedTests(setup, driver);
     });
   });
 }
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_unsound_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_unsound_test.dart
index 92a7c96..e7a1804 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_unsound_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_amd_unsound_test.dart
@@ -4,8 +4,6 @@
 
 // @dart = 2.9
 
-library dev_compiler.test.expression_compiler;
-
 import 'package:dev_compiler/dev_compiler.dart' show ModuleFormat;
 import 'package:test/test.dart';
 import 'expression_compiler_e2e_shared.dart';
@@ -21,8 +19,11 @@
 
     group('(AMD module system)', () {
       var setup = SetupCompilerOptions(
-          soundNullSafety: false, moduleFormat: ModuleFormat.amd);
-      runSharedTests(setup, driver);
+          soundNullSafety: false,
+          legacyCode: false,
+          moduleFormat: ModuleFormat.amd);
+      runAgnosticSharedTests(setup, driver);
+      runNullSafeSharedTests(setup, driver);
     });
   });
 }
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_legacy_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_legacy_test.dart
new file mode 100644
index 0000000..81952a8
--- /dev/null
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_legacy_test.dart
@@ -0,0 +1,28 @@
+// 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.
+
+// @dart = 2.9
+
+import 'package:dev_compiler/dev_compiler.dart' show ModuleFormat;
+import 'package:test/test.dart';
+import 'expression_compiler_e2e_shared.dart';
+import 'expression_compiler_e2e_suite.dart';
+
+void main() async {
+  var driver = await TestDriver.init();
+
+  group('(Legacy code)', () {
+    tearDownAll(() async {
+      await driver.finish();
+    });
+
+    group('(DDC module system)', () {
+      var setup = SetupCompilerOptions(
+          soundNullSafety: false,
+          legacyCode: true,
+          moduleFormat: ModuleFormat.ddc);
+      runAgnosticSharedTests(setup, driver);
+    });
+  });
+}
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_sound_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_sound_test.dart
index 682327e..6d40892 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_sound_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_sound_test.dart
@@ -4,8 +4,6 @@
 
 // @dart = 2.9
 
-library dev_compiler.test.expression_compiler;
-
 import 'package:dev_compiler/dev_compiler.dart' show ModuleFormat;
 import 'package:test/test.dart';
 import 'expression_compiler_e2e_shared.dart';
@@ -21,8 +19,11 @@
 
     group('(DDC module system)', () {
       var setup = SetupCompilerOptions(
-          soundNullSafety: true, moduleFormat: ModuleFormat.ddc);
-      runSharedTests(setup, driver);
+          soundNullSafety: true,
+          legacyCode: false,
+          moduleFormat: ModuleFormat.ddc);
+      runAgnosticSharedTests(setup, driver);
+      runNullSafeSharedTests(setup, driver);
     });
   });
 }
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_unsound_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_unsound_test.dart
index 21bc460..aa231e2 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_unsound_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_ddc_unsound_test.dart
@@ -4,8 +4,6 @@
 
 // @dart = 2.9
 
-library dev_compiler.test.expression_compiler;
-
 import 'package:dev_compiler/dev_compiler.dart' show ModuleFormat;
 import 'package:test/test.dart';
 import 'expression_compiler_e2e_shared.dart';
@@ -21,8 +19,11 @@
 
     group('(DDC module system)', () {
       var setup = SetupCompilerOptions(
-          soundNullSafety: false, moduleFormat: ModuleFormat.ddc);
-      runSharedTests(setup, driver);
+          soundNullSafety: false,
+          legacyCode: false,
+          moduleFormat: ModuleFormat.ddc);
+      runAgnosticSharedTests(setup, driver);
+      runNullSafeSharedTests(setup, driver);
     });
   });
 }
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
index be6d857..c121403 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_shared.dart
@@ -4,8 +4,6 @@
 
 // @dart = 2.9
 
-library dev_compiler.test.expression_compiler;
-
 import 'package:test/test.dart';
 import 'expression_compiler_e2e_suite.dart';
 
@@ -30,6 +28,12 @@
     var nop;
   }
 
+  C.named(this.field): _field = 42;
+
+  C.redirecting(int x) : this(x, 99);
+
+  factory C.factory() => C(42, 0);
+
   int methodFieldAccess(int x) {
     // Breakpoint: methodBP
     var inScope = 1;
@@ -57,7 +61,300 @@
 }
 ''';
 
-void runSharedTests(SetupCompilerOptions setup, TestDriver driver) {
+/// Shared tests that require a language version greater than 2.12.
+///
+/// Tests that exercise language features introduced with 2.12 or after are
+/// valid here.
+// TODO(nshahan) Merge with [runAgnosticSharedTests] after we no longer need to
+// test support for evaluation in legacy (pre-null safety) code.
+void runNullSafeSharedTests(SetupCompilerOptions setup, TestDriver driver) {
+  group('Correct null safety mode used', () {
+    var source = '''
+        const soundNullSafety = !(<Null>[] is List<int>);
+        main() {
+          // Breakpoint: bp
+          print('hello world');
+        }
+        ''';
+
+    setUpAll(() async {
+      await driver.initSource(setup, source);
+    });
+
+    tearDownAll(() async {
+      await driver.cleanupTest();
+    });
+
+    test('in original source compilation', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'soundNullSafety',
+          expectedResult: setup.soundNullSafety.toString());
+    });
+
+    test('in expression compilation', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: '!(<Null>[] is List<int>)',
+          expectedResult: setup.soundNullSafety.toString());
+    });
+  });
+
+  group('Expression compiler tests in method:', () {
+    var source = simpleClassSource;
+
+    setUpAll(() async {
+      await driver.initSource(setup, source);
+    });
+
+    tearDownAll(() async {
+      await driver.cleanupTest();
+    });
+
+    test('tear off default constructor', () async {
+      await driver.check(
+          breakpointId: 'methodBP',
+          expression: 'C.new.runtimeType.toString()',
+          expectedResult: '(int, int) => C');
+    });
+
+    test('call default constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'methodBP',
+          expression: '(C.new)(0, 0)',
+          expectedResult: 'test.C.new {Symbol(_unusedField): 4, '
+              'Symbol(C.field): 0, Symbol(_field): 0}');
+    });
+
+    test('tear off named constructor', () async {
+      await driver.check(
+          breakpointId: 'methodBP',
+          expression: 'C.named.runtimeType.toString()',
+          expectedResult: '(int) => C');
+    });
+
+    test('call named constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'methodBP',
+          expression: '(C.named)(0)',
+          expectedResult: 'test.C.named {Symbol(_unusedField): 4, '
+              'Symbol(C.field): 0, Symbol(_field): 42}');
+    });
+
+    test('tear off redirecting constructor', () async {
+      await driver.check(
+          breakpointId: 'methodBP',
+          expression: 'C.redirecting.runtimeType.toString()',
+          expectedResult: '(int) => C');
+    });
+
+    test('call redirecting constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'methodBP',
+          expression: '(C.redirecting)(0)',
+          expectedResult: 'test.C.redirecting { Symbol(_unusedField): 4, '
+              'Symbol(C.field): 0, Symbol(_field): 99}');
+    });
+
+    test('tear off factory constructor', () async {
+      await driver.check(
+          breakpointId: 'methodBP',
+          expression: 'C.factory.runtimeType.toString()',
+          expectedResult: '() => C');
+    });
+
+    test('call factory constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'methodBP',
+          expression: '(C.factory)()',
+          expectedResult: 'test.C.new { Symbol(_unusedField): 4, '
+              'Symbol(C.field): 42, Symbol(_field): 0}');
+    });
+  });
+
+  group('Expression compiler tests in global function:', () {
+    var source = simpleClassSource;
+
+    setUpAll(() async {
+      await driver.initSource(setup, source);
+    });
+
+    tearDownAll(() async {
+      await driver.cleanupTest();
+    });
+
+    test('tear off default constructor', () async {
+      await driver.check(
+          breakpointId: 'globalFunctionBP',
+          expression: 'C.new.runtimeType.toString()',
+          expectedResult: '(int, int) => C');
+    });
+
+    test('call default constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'globalFunctionBP',
+          expression: '(C.new)(0, 0)',
+          expectedResult: 'test.C.new {Symbol(_unusedField): 4, '
+              'Symbol(C.field): 0, Symbol(_field): 0}');
+    });
+
+    test('tear off named constructor', () async {
+      await driver.check(
+          breakpointId: 'globalFunctionBP',
+          expression: 'C.named.runtimeType.toString()',
+          expectedResult: '(int) => C');
+    });
+
+    test('call named constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'globalFunctionBP',
+          expression: '(C.named)(0)',
+          expectedResult: 'test.C.named {Symbol(_unusedField): 4, '
+              'Symbol(C.field): 0, Symbol(_field): 42}');
+    });
+
+    test('tear off redirecting constructor', () async {
+      await driver.check(
+          breakpointId: 'globalFunctionBP',
+          expression: 'C.redirecting.runtimeType.toString()',
+          expectedResult: '(int) => C');
+    });
+
+    test('call redirecting constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'globalFunctionBP',
+          expression: '(C.redirecting)(0)',
+          expectedResult: 'test.C.redirecting { Symbol(_unusedField): 4, '
+              'Symbol(C.field): 0, Symbol(_field): 99}');
+    });
+
+    test('tear off factory constructor', () async {
+      await driver.check(
+          breakpointId: 'globalFunctionBP',
+          expression: 'C.factory.runtimeType.toString()',
+          expectedResult: '() => C');
+    });
+
+    test('call factory constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'globalFunctionBP',
+          expression: '(C.factory)()',
+          expectedResult: 'test.C.new { Symbol(_unusedField): 4, '
+              'Symbol(C.field): 42, Symbol(_field): 0}');
+    });
+  });
+
+  group('Expression compiler tests in constructor:', () {
+    var source = simpleClassSource;
+
+    setUpAll(() async {
+      await driver.initSource(setup, source);
+    });
+
+    tearDownAll(() async {
+      await driver.cleanupTest();
+    });
+
+    test('tear off default constructor', () async {
+      await driver.check(
+          breakpointId: 'constructorBP',
+          expression: 'C.new.runtimeType.toString()',
+          expectedResult: '(int, int) => C');
+    });
+
+    test('call default constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'constructorBP',
+          expression: '(C.new)(0, 0)',
+          expectedResult: 'test.C.new {Symbol(_unusedField): 4, '
+              'Symbol(C.field): 0, Symbol(_field): 0}');
+    });
+
+    test('tear off named constructor', () async {
+      await driver.check(
+          breakpointId: 'constructorBP',
+          expression: 'C.named.runtimeType.toString()',
+          expectedResult: '(int) => C');
+    });
+
+    test('call named constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'constructorBP',
+          expression: '(C.named)(0)',
+          expectedResult: 'test.C.named {Symbol(_unusedField): 4, '
+              'Symbol(C.field): 0, Symbol(_field): 42}');
+    });
+
+    test('tear off redirecting constructor', () async {
+      await driver.check(
+          breakpointId: 'constructorBP',
+          expression: 'C.redirecting.runtimeType.toString()',
+          expectedResult: '(int) => C');
+    });
+
+    test('call redirecting constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'constructorBP',
+          expression: '(C.redirecting)(0)',
+          expectedResult: 'test.C.redirecting { Symbol(_unusedField): 4, '
+              'Symbol(C.field): 0, Symbol(_field): 99}');
+    });
+
+    test('tear off factory constructor', () async {
+      await driver.check(
+          breakpointId: 'constructorBP',
+          expression: 'C.factory.runtimeType.toString()',
+          expectedResult: '() => C');
+    });
+
+    test('call factory constructor tear off', () async {
+      await driver.check(
+          breakpointId: 'constructorBP',
+          expression: '(C.factory)()',
+          expectedResult: 'test.C.new { Symbol(_unusedField): 4, '
+              'Symbol(C.field): 42, Symbol(_field): 0}');
+    });
+  });
+}
+
+/// Shared tests that are valid in legacy (before 2.12) and are agnostic to
+/// changes in modern versions of Dart.
+///
+/// Tests that exercise language features introduced strictly before 2.12 are
+/// valid here.
+void runAgnosticSharedTests(SetupCompilerOptions setup, TestDriver driver) {
+  group('Correct null safety mode used', () {
+    var source = '''
+        const soundNullSafety = !(<Null>[] is List<int>);
+        main() {
+          // Breakpoint: bp
+          print('hello world');
+        }
+        ''';
+
+    setUpAll(() async {
+      await driver.initSource(setup, source);
+    });
+
+    tearDownAll(() async {
+      await driver.cleanupTest();
+    });
+
+    test('in original source compilation', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: 'soundNullSafety',
+          expectedResult: setup.soundNullSafety.toString());
+    });
+
+    test('in expression compilation', () async {
+      await driver.check(
+          breakpointId: 'bp',
+          expression: '!(<Null>[] is List<int>)',
+          expectedResult: setup.soundNullSafety.toString());
+    });
+  });
+
   group('Expression compiler scope collection tests', () {
     var source = simpleClassSource;
 
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart
index 422caec..8e8f1bf 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_e2e_suite.dart
@@ -4,8 +4,6 @@
 
 // @dart = 2.9
 
-library dev_compiler.test.expression_compiler;
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' show Directory, File, Platform;
@@ -59,10 +57,8 @@
       p.join(sdkRoot.toFilePath(), 'ddc_outline_sound.dill');
   static final librariesSpecificationUri =
       p.join(p.dirname(p.dirname(getSdkPath())), 'libraries.json');
-  static final String dartUnsoundComment = '// @dart = 2.9';
-  static final String dartSoundComment = '//';
 
-  final String dartLangComment;
+  final bool legacyCode;
   final List<String> errors = [];
   final List<String> diagnosticMessages = [];
   final ModuleFormat moduleFormat;
@@ -84,10 +80,10 @@
   }
 
   SetupCompilerOptions(
-      {this.soundNullSafety = true, this.moduleFormat = ModuleFormat.amd})
-      : options = _getOptions(soundNullSafety),
-        dartLangComment =
-            soundNullSafety ? dartSoundComment : dartUnsoundComment {
+      {this.soundNullSafety = true,
+      this.legacyCode = false,
+      this.moduleFormat = ModuleFormat.amd})
+      : options = _getOptions(soundNullSafety) {
     options.onDiagnostic = (fe.DiagnosticMessage m) {
       diagnosticMessages.addAll(m.plainTextFormatted);
       if (m.severity == fe.Severity.error) {
@@ -280,8 +276,8 @@
       throw StateError('Unable to find SDK summary at path: $summaryPath.');
     }
 
-    // Prepend Dart nullability comment.
-    source = '${setup.dartLangComment}\n\n$source';
+    // Prepend legacy Dart version comment.
+    if (setup.legacyCode) source = '// @dart = 2.11\n\n$source';
     this.setup = setup;
     this.source = source;
     testDir = chromeDir.createTempSync('ddc_eval_test');
@@ -524,7 +520,7 @@
     expect(
         result,
         const TypeMatcher<TestCompilationResult>()
-            .having((_) => '$value', 'result', _matches(expectedResult)));
+            .having((_) => value, 'result', _matches(expectedResult)));
   }
 
   /// Generate simple string representation of a RemoteObject that closely
diff --git a/tools/VERSION b/tools/VERSION
index a6d064a..cc57400 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 163
+PRERELEASE 164
 PRERELEASE_PATCH 0
\ No newline at end of file