Version 2.18.0-23.0.dev

Merge commit '169365f9530252bdf1131e6bff3fa8c4233d7aba' into 'dev'
diff --git a/pkg/analysis_server/test/edit/bulk_fixes_test.dart b/pkg/analysis_server/test/edit/bulk_fixes_test.dart
index 00c07ca..b728b9b 100644
--- a/pkg/analysis_server/test/edit/bulk_fixes_test.dart
+++ b/pkg/analysis_server/test/edit/bulk_fixes_test.dart
@@ -267,6 +267,7 @@
   }
 
   Future<EditBulkFixesResult> _getBulkFixes() async {
+    // TODO(scheglov) Remove this, we want to see if lines change.
     var request = EditBulkFixesParams([workspaceRoot.path]).toRequest('0');
     var response = await handleSuccessfulRequest(request);
     return EditBulkFixesResult.fromResponse(response);
diff --git a/pkg/analysis_server/test/edit/refactoring_test.dart b/pkg/analysis_server/test/edit/refactoring_test.dart
index 910154e..df8fe1c 100644
--- a/pkg/analysis_server/test/edit/refactoring_test.dart
+++ b/pkg/analysis_server/test/edit/refactoring_test.dart
@@ -10,7 +10,6 @@
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
-import '../analysis_abstract.dart';
 import '../analysis_server_base.dart';
 import '../mocks.dart';
 import '../src/utilities/mock_packages.dart';
@@ -841,16 +840,14 @@
 }
 
 @reflectiveTest
-class GetAvailableRefactoringsTest extends AbstractAnalysisTest {
+class GetAvailableRefactoringsTest extends PubPackageAnalysisServerTest {
   late List<RefactoringKind> kinds;
 
   void addFlutterPackage() {
     var flutterLib = MockPackages.instance.addFlutter(resourceProvider);
-    newPackageConfigJsonFile(
-      projectPath,
-      (PackageConfigFileBuilder()
-            ..add(name: 'flutter', rootPath: flutterLib.parent.path))
-          .toContent(toUriStr: toUriStr),
+    writeTestPackageConfig(
+      config: PackageConfigFileBuilder()
+        ..add(name: 'flutter', rootPath: flutterLib.parent.path),
     );
   }
 
@@ -877,8 +874,9 @@
   /// Returns the list of available refactorings for the given [offset] and
   /// [length].
   Future getRefactorings(int offset, int length) async {
-    var request = EditGetAvailableRefactoringsParams(testFile, offset, length)
-        .toRequest('0');
+    var request =
+        EditGetAvailableRefactoringsParams(testFile.path, offset, length)
+            .toRequest('0');
     serverChannel.sendRequest(request);
     var response = await serverChannel.waitForResponse(request);
     var result = EditGetAvailableRefactoringsResult.fromResponse(response);
@@ -899,9 +897,7 @@
   @override
   Future<void> setUp() async {
     super.setUp();
-    await createProject();
-    handler = EditDomainHandler(server);
-    server.handlers = [handler];
+    await setRoots(included: [workspaceRootPath], excluded: []);
   }
 
   Future test_convertMethodToGetter_hasElement() {
@@ -954,10 +950,11 @@
   Future<void> test_invalidFilePathFormat_notAbsolute() async {
     var request =
         EditGetAvailableRefactoringsParams('test.dart', 0, 0).toRequest('0');
-    var response = await waitResponse(request);
-    expect(
+    var response = await handleRequest(request);
+    assertResponseFailure(
       response,
-      isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
+      requestId: '0',
+      errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT,
     );
   }
 
@@ -965,10 +962,11 @@
     var request = EditGetAvailableRefactoringsParams(
             convertPath('/foo/../bar/test.dart'), 0, 0)
         .toRequest('0');
-    var response = await waitResponse(request);
-    expect(
+    var response = await handleRequest(request);
+    assertResponseFailure(
       response,
-      isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
+      requestId: '0',
+      errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT,
     );
   }
 
diff --git a/pkg/analysis_server/test/src/domain_abstract_test.dart b/pkg/analysis_server/test/src/domain_abstract_test.dart
index 33c09fa..12e9cf5 100644
--- a/pkg/analysis_server/test/src/domain_abstract_test.dart
+++ b/pkg/analysis_server/test/src/domain_abstract_test.dart
@@ -13,7 +13,7 @@
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
-import '../analysis_abstract.dart';
+import '../analysis_server_base.dart';
 import 'plugin/plugin_manager_test.dart';
 
 void main() {
@@ -23,7 +23,7 @@
 }
 
 @reflectiveTest
-class AbstractRequestHandlerTest extends AbstractAnalysisTest {
+class AbstractRequestHandlerTest extends PubPackageAnalysisServerTest {
   Future<void> test_waitForResponses_empty_noTimeout() async {
     AbstractRequestHandler handler = TestAbstractRequestHandler(server);
     var futures = <PluginInfo, Future<plugin.Response>>{};
diff --git a/pkg/analysis_server/test/src/domains/completion/available_suggestions_base.dart b/pkg/analysis_server/test/src/domains/completion/available_suggestions_base.dart
index 9fee7ee..5f057a3 100644
--- a/pkg/analysis_server/test/src/domains/completion/available_suggestions_base.dart
+++ b/pkg/analysis_server/test/src/domains/completion/available_suggestions_base.dart
@@ -5,21 +5,20 @@
 import 'dart:convert';
 
 import 'package:analysis_server/protocol/protocol_constants.dart';
-import 'package:analysis_server/src/domain_completion.dart';
 import 'package:analysis_server/src/protocol_server.dart';
-import 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
+import 'package:analyzer/file_system/file_system.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
-import '../../../analysis_abstract.dart';
+import '../../../analysis_server_base.dart';
 import '../../../constants.dart';
 
 @reflectiveTest
-class AvailableSuggestionsBase extends AbstractAnalysisTest {
+class AvailableSuggestionsBase extends PubPackageAnalysisServerTest {
   final Map<int, AvailableSuggestionSet> idToSetMap = {};
   final Map<String, AvailableSuggestionSet> uriToSetMap = {};
   final Map<String, CompletionResultsParams> idToSuggestions = {};
-  final Map<String, ExistingImports> fileToExistingImports = {};
+  final Map<File, ExistingImports> fileToExistingImports = {};
 
   void assertJsonText(Object object, String expected) {
     expected = expected.trimRight();
@@ -59,7 +58,7 @@
       var params = CompletionExistingImportsParams.fromNotification(
         notification,
       );
-      fileToExistingImports[params.file] = params.imports;
+      fileToExistingImports[getFile(params.file)] = params.imports;
     } else if (notification.event == SERVER_NOTIFICATION_ERROR) {
       fail('${notification.toJson()}');
     }
@@ -75,19 +74,16 @@
   @override
   Future<void> setUp() async {
     super.setUp();
-    projectPath = convertPath('/home');
-    testFile = convertPath('/home/test/lib/test.dart');
 
-    newPubspecYamlFile('/home/test', '');
-    newPackageConfigJsonFile(
-      '/home/test',
-      (PackageConfigFileBuilder()..add(name: 'test', rootPath: '/home/test'))
-          .toContent(toUriStr: toUriStr),
+    newPubspecYamlFile(testPackageRootPath, '');
+    writeTestPackageConfig();
+    await setRoots(included: [workspaceRootPath], excluded: []);
+
+    await handleSuccessfulRequest(
+      CompletionSetSubscriptionsParams([
+        CompletionService.AVAILABLE_SUGGESTION_SETS,
+      ]).toRequest('0'),
     );
-
-    await createProject();
-    handler = server.handlers.whereType<CompletionDomainHandler>().single;
-    _setCompletionSubscriptions([CompletionService.AVAILABLE_SUGGESTION_SETS]);
   }
 
   Future<CompletionResultsParams> waitForGetSuggestions(String id) async {
@@ -119,10 +115,4 @@
       await Future.delayed(const Duration(milliseconds: 1));
     }
   }
-
-  void _setCompletionSubscriptions(List<CompletionService> subscriptions) {
-    handleSuccessfulRequest(
-      CompletionSetSubscriptionsParams(subscriptions).toRequest('0'),
-    );
-  }
 }
diff --git a/pkg/analysis_server/test/src/domains/completion/get_suggestion_details_test.dart b/pkg/analysis_server/test/src/domains/completion/get_suggestion_details_test.dart
index 75ae254..1f6eae4 100644
--- a/pkg/analysis_server/test/src/domains/completion/get_suggestion_details_test.dart
+++ b/pkg/analysis_server/test/src/domains/completion/get_suggestion_details_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/protocol_server.dart';
+import 'package:analyzer/file_system/file_system.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -31,7 +32,7 @@
       _buildRequest(
         id: set.id,
         label: 'MyEnum.aaa',
-        offset: testCode.indexOf('} // ref'),
+        offset: testFileContent.indexOf('} // ref'),
       ),
     );
 
@@ -55,7 +56,7 @@
       _buildRequest(
         id: mathSet.id,
         label: 'sin',
-        offset: testCode.indexOf('} // ref'),
+        offset: testFileContent.indexOf('} // ref'),
       ),
     );
 
@@ -75,7 +76,7 @@
       _buildRequest(
         id: mathSet.id,
         label: 'sin',
-        offset: testCode.indexOf('} // ref'),
+        offset: testFileContent.indexOf('} // ref'),
       ),
     );
 
@@ -86,10 +87,14 @@
   Future<void> test_invalid_library() async {
     addTestFile('');
 
-    var response = await waitResponse(
+    var response = await handleRequest(
       _buildRequest(id: -1, label: 'foo', offset: 0),
     );
-    expect(response.error!.code, RequestErrorCode.INVALID_PARAMETER);
+    assertResponseFailure(
+      response,
+      requestId: '0',
+      errorCode: RequestErrorCode.INVALID_PARAMETER,
+    );
   }
 
   Future<void> test_newImport() async {
@@ -102,7 +107,7 @@
       _buildRequest(
         id: mathSet.id,
         label: 'sin',
-        offset: testCode.indexOf('} // ref'),
+        offset: testFileContent.indexOf('} // ref'),
       ),
     );
 
@@ -131,7 +136,7 @@
       _buildRequest(
         id: mathSet.id,
         label: 'sin',
-        offset: testCode.indexOf('} // ref'),
+        offset: testFileContent.indexOf('} // ref'),
       ),
     );
 
@@ -165,7 +170,7 @@
       _buildRequest(
         id: mathSet.id,
         label: 'sin',
-        offset: testCode.indexOf('} // ref'),
+        offset: testFileContent.indexOf('} // ref'),
       ),
     );
 
@@ -197,7 +202,7 @@
       _buildRequest(
         id: mathSet.id,
         label: 'sin',
-        offset: testCode.indexOf('} // ref'),
+        offset: testFileContent.indexOf('} // ref'),
       ),
     );
 
@@ -218,7 +223,7 @@
 
 main() {} // ref
 ''';
-    var partPath = newFile2('/home/test/lib/a.dart', partCode).path;
+    var partFile = newFile2('/home/test/lib/a.dart', partCode);
     addTestFile(r'''
 part 'a.dart';
 ''');
@@ -226,7 +231,7 @@
     var mathSet = await waitForSetWithUri('dart:math');
     var result = await _getSuggestionDetails(
       _buildRequest(
-        file: partPath,
+        file: partFile,
         id: mathSet.id,
         label: 'sin',
         offset: partCode.indexOf('} // ref'),
@@ -250,20 +255,20 @@
     expect(fileEdits, hasLength(1));
 
     var fileEdit = fileEdits[0];
-    expect(fileEdit.file, testFile);
+    expect(fileEdit.file, testFile.path);
 
     var edits = fileEdit.edits;
-    expect(SourceEdit.applySequence(testCode, edits), expected);
+    expect(SourceEdit.applySequence(testFileContent, edits), expected);
   }
 
   Request _buildRequest({
-    String? file,
+    File? file,
     required int id,
     required String label,
     required int offset,
   }) {
     return CompletionGetSuggestionDetailsParams(
-      file ?? testFile,
+      (file ?? testFile).path,
       id,
       label,
       offset,
@@ -272,7 +277,7 @@
 
   Future<CompletionGetSuggestionDetailsResult> _getSuggestionDetails(
       Request request) async {
-    var response = await waitResponse(request);
+    var response = await handleSuccessfulRequest(request);
     return CompletionGetSuggestionDetailsResult.fromResponse(response);
   }
 }
diff --git a/pkg/analysis_server/test/src/domains/completion/get_suggestions_available_test.dart b/pkg/analysis_server/test/src/domains/completion/get_suggestions_available_test.dart
index 8a1b392..725ebab 100644
--- a/pkg/analysis_server/test/src/domains/completion/get_suggestions_available_test.dart
+++ b/pkg/analysis_server/test/src/domains/completion/get_suggestions_available_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/protocol_server.dart';
+import 'package:analyzer/file_system/file_system.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -110,7 +111,7 @@
   }
 
   Future<void> test_defaultArgumentListString() async {
-    newFile2('/home/test/lib/a.dart', r'''
+    newFile2('$testPackageLibPath/a.dart', r'''
 void fff(int aaa, int bbb) {}
 
 void ggg({int aaa, @required int bbb, @required int ccc}) {}
@@ -128,13 +129,13 @@
   }
 
   Future<void> test_displayUri_file() async {
-    var aPath = '/home/test/test/a.dart';
+    var aPath = '$testPackageRootPath/test/a.dart';
     newFile2(aPath, 'class A {}');
 
     var aSet = await waitForSetWithUri(toUriStr(aPath));
 
-    var testPath = newFile2('/home/test/test/sub/test.dart', '').path;
-    var results = await _getSuggestions(testPath, 0);
+    var file = newFile2('$testPackageRootPath/test/sub/test.dart', '');
+    var results = await _getSuggestions(file, 0);
 
     expect(
       results.includedSuggestionSets!.singleWhere((set) {
@@ -145,13 +146,12 @@
   }
 
   Future<void> test_displayUri_package() async {
-    var aPath = '/home/test/lib/a.dart';
-    newFile2(aPath, 'class A {}');
+    newFile2('$testPackageLibPath/a.dart', 'class A {}');
 
     var aSet = await waitForSetWithUri('package:test/a.dart');
-    var testPath = newFile2('/home/test/lib/test.dart', '').path;
+    var file = newFile2('$testPackageLibPath/test.dart', '');
 
-    var results = await _getSuggestions(testPath, 0);
+    var results = await _getSuggestions(file, 0);
     expect(
       results.includedSuggestionSets!.singleWhere((set) {
         return set.id == aSet.id;
@@ -167,7 +167,7 @@
 
     var results = await _getSuggestions(
       testFile,
-      testCode.indexOf('{} // ref'),
+      testFileContent.indexOf('{} // ref'),
     );
 
     expect(
@@ -192,7 +192,7 @@
 
     var results = await _getSuggestions(
       testFile,
-      testCode.indexOf('); // ref'),
+      testFileContent.indexOf('); // ref'),
     );
 
     expect(
@@ -217,13 +217,12 @@
   }
 
   Future<void> test_inHtml() async {
-    newFile2('/home/test/lib/a.dart', 'class A {}');
+    newFile2('$testPackageLibPath/a.dart', 'class A {}');
 
-    var path = convertPath('/home/test/doc/a.html');
-    newFile2(path, '<html></html>');
+    var file = newFile2('$testPackageRoot/doc/a.html', '<html></html>');
 
-    await waitResponse(
-      CompletionGetSuggestionsParams(path, 0).toRequest('0'),
+    await handleSuccessfulRequest(
+      CompletionGetSuggestionsParams(file.path, 0).toRequest('0'),
     );
   }
 
@@ -238,7 +237,7 @@
 
     var results = await _getSuggestions(
       testFile,
-      testCode.indexOf('); // ref'),
+      testFileContent.indexOf('); // ref'),
     );
 
     var includedTags = results.includedSuggestionRelevanceTags!;
@@ -272,7 +271,7 @@
 
     var results = await _getSuggestions(
       testFile,
-      testCode.indexOf(' // ref'),
+      testFileContent.indexOf(' // ref'),
     );
 
     assertJsonText(results.includedSuggestionRelevanceTags!, r'''
@@ -336,7 +335,7 @@
 
     var results = await _getSuggestions(
       testFile,
-      testCode.indexOf('); // ref'),
+      testFileContent.indexOf('); // ref'),
     );
 
     assertJsonText(results.includedSuggestionRelevanceTags!, r'''
@@ -400,7 +399,7 @@
 
     var results = await _getSuggestions(
       testFile,
-      testCode.indexOf('); // ref'),
+      testFileContent.indexOf('); // ref'),
     );
 
     assertJsonText(results.includedSuggestionRelevanceTags!, r'''
@@ -471,7 +470,7 @@
 
     var results = await _getSuggestions(
       testFile,
-      testCode.indexOf(' // ref'),
+      testFileContent.indexOf(' // ref'),
     );
 
     assertJsonText(results.includedSuggestionRelevanceTags!, r'''
@@ -531,7 +530,7 @@
 
     var results = await _getSuggestions(
       testFile,
-      testCode.indexOf(' // ref'),
+      testFileContent.indexOf(' // ref'),
     );
 
     assertJsonText(results.includedSuggestionRelevanceTags!, r'''
@@ -601,7 +600,7 @@
 
     var results = await _getSuggestions(
       testFile,
-      testCode.indexOf(']; // ref'),
+      testFileContent.indexOf(']; // ref'),
     );
 
     assertJsonText(results.includedSuggestionRelevanceTags!, r'''
@@ -617,12 +616,12 @@
 
 abstract class GetSuggestionsBase extends AvailableSuggestionsBase {
   Future<CompletionResultsParams> _getSuggestions(
-    String path,
+    File file,
     int offset,
   ) async {
     var response = CompletionGetSuggestionsResult.fromResponse(
-      await waitResponse(
-        CompletionGetSuggestionsParams(path, offset).toRequest('0'),
+      await handleSuccessfulRequest(
+        CompletionGetSuggestionsParams(file.path, offset).toRequest('0'),
       ),
     );
     return await waitForGetSuggestions(response.id);
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index ed88f0c..683238e 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -1405,7 +1405,8 @@
           _provider.setOverlay(filePath,
               content: overlayContents,
               modificationStamp: overlayModificationStamp++);
-          context.driver.changeFile(filePath);
+          context.changeFile(filePath);
+          await context.applyPendingFileChanges();
           resolvedUnitResult = await context.currentSession
               .getResolvedUnit(filePath) as ResolvedUnitResult;
         }
diff --git a/pkg/analyzer/lib/src/summary2/macro_application.dart b/pkg/analyzer/lib/src/summary2/macro_application.dart
index 5ef25c0..2e9a109 100644
--- a/pkg/analyzer/lib/src/summary2/macro_application.dart
+++ b/pkg/analyzer/lib/src/summary2/macro_application.dart
@@ -79,27 +79,7 @@
   }
 
   macro.ClassDeclaration getClassDeclaration(ClassDeclaration node) {
-    return _classDeclarations[node] ??= _createClassDeclaration(node);
-  }
-
-  macro.ClassDeclaration _createClassDeclaration(ClassDeclaration node) {
-    return macro.ClassDeclarationImpl(
-      id: macro.RemoteInstance.uniqueId,
-      identifier: _IdentifierImpl(
-        id: macro.RemoteInstance.uniqueId,
-        name: node.name.name,
-      ),
-      // TODO(scheglov): Support typeParameters
-      typeParameters: [],
-      // TODO(scheglov): Support interfaces
-      interfaces: [],
-      isAbstract: node.abstractKeyword != null,
-      isExternal: false,
-      // TODO(scheglov): Support mixins
-      mixins: [],
-      // TODO(scheglov): Support superclass
-      superclass: null,
-    );
+    return _classDeclarations[node] ??= _buildClassDeclaration(node);
   }
 
   macro.TypeAnnotation _inferOmittedType(
@@ -127,6 +107,79 @@
     );
     return await macroInstance.executeTypesPhase();
   }
+
+  static macro.ClassDeclarationImpl _buildClassDeclaration(
+    ClassDeclaration node,
+  ) {
+    return macro.ClassDeclarationImpl(
+      id: macro.RemoteInstance.uniqueId,
+      identifier: _buildIdentifier(node.name),
+      typeParameters: _buildTypeParameters(node.typeParameters),
+      interfaces: _buildTypeAnnotations(node.implementsClause?.interfaces),
+      isAbstract: node.abstractKeyword != null,
+      isExternal: false,
+      mixins: _buildTypeAnnotations(node.withClause?.mixinTypes),
+      superclass: node.extendsClause?.superclass.mapOrNull(
+        _buildTypeAnnotation,
+      ),
+    );
+  }
+
+  static macro.IdentifierImpl _buildIdentifier(Identifier node) {
+    final String name;
+    if (node is SimpleIdentifier) {
+      name = node.name;
+    } else {
+      name = (node as PrefixedIdentifier).identifier.name;
+    }
+    return _IdentifierImpl(
+      id: macro.RemoteInstance.uniqueId,
+      name: name,
+    );
+  }
+
+  static macro.TypeAnnotationImpl _buildTypeAnnotation(TypeAnnotation node) {
+    if (node is NamedType) {
+      return macro.NamedTypeAnnotationImpl(
+        id: macro.RemoteInstance.uniqueId,
+        identifier: _buildIdentifier(node.name),
+        isNullable: node.question != null,
+        typeArguments: _buildTypeAnnotations(node.typeArguments?.arguments),
+      );
+    } else {
+      throw UnimplementedError('(${node.runtimeType}) $node');
+    }
+  }
+
+  static List<macro.TypeAnnotationImpl> _buildTypeAnnotations(
+    List<TypeAnnotation>? elements,
+  ) {
+    if (elements != null) {
+      return elements.map(_buildTypeAnnotation).toList();
+    } else {
+      return const [];
+    }
+  }
+
+  static macro.TypeParameterDeclarationImpl _buildTypeParameter(
+    TypeParameter node,
+  ) {
+    return macro.TypeParameterDeclarationImpl(
+      id: macro.RemoteInstance.uniqueId,
+      identifier: _buildIdentifier(node.name),
+      bound: node.bound?.mapOrNull(_buildTypeAnnotation),
+    );
+  }
+
+  static List<macro.TypeParameterDeclarationImpl> _buildTypeParameters(
+    TypeParameterList? typeParameterList,
+  ) {
+    if (typeParameterList != null) {
+      return typeParameterList.typeParameters.map(_buildTypeParameter).toList();
+    } else {
+      return const [];
+    }
+  }
 }
 
 class _FakeIdentifierResolver extends macro.IdentifierResolver {
@@ -146,3 +199,10 @@
   bool get isNotEmpty =>
       libraryAugmentations.isNotEmpty || classAugmentations.isNotEmpty;
 }
+
+extension _IfNotNull<T> on T? {
+  R? mapOrNull<R>(R Function(T) mapper) {
+    final self = this;
+    return self != null ? mapper(self) : null;
+  }
+}
diff --git a/pkg/analyzer/test/src/summary/element_text.dart b/pkg/analyzer/test/src/summary/element_text.dart
index 5790c59..cfe45a8 100644
--- a/pkg/analyzer/test/src/summary/element_text.dart
+++ b/pkg/analyzer/test/src/summary/element_text.dart
@@ -109,7 +109,7 @@
   // Print the actual text to simplify copy/paste into the expectation.
   // if (actualText != expected) {
   //   print('-------- Actual --------');
-  //   print(actualText + '------------------------');
+  //   print('$actualText------------------------');
   // }
 
   expect(actualText, expected);
diff --git a/pkg/analyzer/test/src/summary/macro/declaration_text.dart b/pkg/analyzer/test/src/summary/macro/declaration_text.dart
new file mode 100644
index 0000000..066c9b6
--- /dev/null
+++ b/pkg/analyzer/test/src/summary/macro/declaration_text.dart
@@ -0,0 +1,179 @@
+// Copyright (c) 2022, 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 'dart:async';
+
+import 'package:_fe_analyzer_shared/src/macros/api.dart';
+
+/*macro*/ class DeclarationTextMacro implements ClassTypesMacro {
+  const DeclarationTextMacro();
+
+  @override
+  FutureOr<void> buildTypesForClass(declaration, builder) {
+    final printer = _DeclarationPrinter();
+    printer.writeClassDeclaration(declaration);
+    final text = printer._sink.toString();
+
+    builder.declareType(
+      'x',
+      DeclarationCode.fromString(
+        'const x = r"""$text""";',
+      ),
+    );
+  }
+}
+
+class _DeclarationPrinter {
+  final StringBuffer _sink = StringBuffer();
+  String _indent = '';
+
+  void writeClassDeclaration(ClassDeclaration e) {
+    _writeIf(e.isAbstract, 'abstract ');
+    _writeIf(e.isExternal, 'external ');
+
+    _writeln('class ${e.identifier.name}');
+
+    _withIndent(() {
+      var superclass = e.superclass;
+      if (superclass != null) {
+        _writeTypeAnnotation('superclass', superclass);
+      }
+
+      _writeTypeParameters(e.typeParameters);
+      _writeTypeAnnotations('mixins', e.mixins);
+      _writeTypeAnnotations('interfaces', e.interfaces);
+    });
+  }
+
+  void _withIndent(void Function() f) {
+    var savedIndent = _indent;
+    _indent = '$savedIndent  ';
+    f();
+    _indent = savedIndent;
+  }
+
+  void _writeElements<T>(
+    String name,
+    Iterable<T> elements,
+    void Function(T) f,
+  ) {
+    if (elements.isNotEmpty) {
+      _writelnWithIndent(name);
+      _withIndent(() {
+        for (var element in elements) {
+          f(element);
+        }
+      });
+    }
+  }
+
+  void _writeIf(bool flag, String str) {
+    if (flag) {
+      _sink.write(str);
+    }
+  }
+
+  void _writeln(String line) {
+    _sink.writeln(line);
+  }
+
+  void _writelnWithIndent(String line) {
+    _sink.write(_indent);
+    _sink.writeln(line);
+  }
+
+  void _writeTypeAnnotation(String name, TypeAnnotation? type) {
+    _sink.write(_indent);
+    _sink.write('$name: ');
+
+    if (type != null) {
+      _writeln(_typeStr(type));
+    } else {
+      _writeln('null');
+    }
+  }
+
+  void _writeTypeAnnotationLine(TypeAnnotation type) {
+    var typeStr = _typeStr(type);
+    _writelnWithIndent(typeStr);
+  }
+
+  void _writeTypeAnnotations(String name, Iterable<TypeAnnotation> elements) {
+    _writeElements(name, elements, _writeTypeAnnotationLine);
+  }
+
+  void _writeTypeParameter(TypeParameterDeclaration e) {
+    _writelnWithIndent(e.identifier.name);
+
+    _withIndent(() {
+      var bound = e.bound;
+      if (bound != null) {
+        _writeTypeAnnotation('bound', bound);
+      }
+    });
+  }
+
+  void _writeTypeParameters(Iterable<TypeParameterDeclaration> elements) {
+    _writeElements('typeParameters', elements, _writeTypeParameter);
+  }
+
+  static String _typeStr(TypeAnnotation type) {
+    final buffer = StringBuffer();
+    _TypeStringBuilder(buffer).write(type);
+    return buffer.toString();
+  }
+}
+
+class _TypeStringBuilder {
+  final StringSink _sink;
+
+  _TypeStringBuilder(this._sink);
+
+  void write(TypeAnnotation type) {
+    if (type is NamedTypeAnnotation) {
+      _sink.write(type.identifier.name);
+      _sink.writeList(
+        elements: type.typeArguments,
+        write: write,
+        separator: ', ',
+        open: '<',
+        close: '>',
+      );
+    } else {
+      throw UnimplementedError('(${type.runtimeType}) $type');
+    }
+    if (type.isNullable) {
+      _sink.write('?');
+    }
+  }
+}
+
+extension on StringSink {
+  void writeList<T>({
+    required Iterable<T> elements,
+    required void Function(T element) write,
+    required String separator,
+    String? open,
+    String? close,
+  }) {
+    elements = elements.toList();
+    if (elements.isNotEmpty) {
+      if (open != null) {
+        this.write(open);
+      }
+      var isFirst = true;
+      for (var element in elements) {
+        if (isFirst) {
+          isFirst = false;
+        } else {
+          this.write(separator);
+        }
+        write(element);
+      }
+      if (close != null) {
+        this.write(close);
+      }
+    }
+  }
+}
diff --git a/pkg/analyzer/test/src/summary/macro_test.dart b/pkg/analyzer/test/src/summary/macro_test.dart
index c46dce0..c32eac5 100644
--- a/pkg/analyzer/test/src/summary/macro_test.dart
+++ b/pkg/analyzer/test/src/summary/macro_test.dart
@@ -4,6 +4,8 @@
 
 import 'package:_fe_analyzer_shared/src/macros/executor/multi_executor.dart'
     as macro;
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/test_utilities/package_config_file_builder.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -46,6 +48,14 @@
   /// The path for external packages.
   String get packagesRootPath => '/packages';
 
+  /// Return the code for `DeclarationTextMacro`.
+  String get _declarationTextCode {
+    var code = MacrosEnvironment.instance.packageAnalyzerFolder
+        .getChildAssumingFile('test/src/summary/macro/declaration_text.dart')
+        .readAsStringSync();
+    return code.replaceAll('/*macro*/', 'macro');
+  }
+
   Future<void> setUp() async {
     writeTestPackageConfig(
       PackageConfigFileBuilder(),
@@ -87,7 +97,9 @@
       {'package:test/a.dart'}
     ]);
 
-    checkElementText(library, r'''
+    checkElementText(
+        library,
+        r'''
 library
   imports
     package:test/a.dart
@@ -113,10 +125,83 @@
         class MyClass @6
           constructors
             synthetic @-1
+  exportScope
+    A: package:test/test.dart;A
+    MyClass: package:test/test.dart;package:test/_macro_types.dart;MyClass
+''',
+        withExportScope: true);
+  }
+
+  test_introspect_types_ClassDeclaration_interfaces() async {
+    await _assertTypesPhaseIntrospectionText(r'''
+class A implements B, C<int, String> {}
+''', r'''
+class A
+  interfaces
+    B
+    C<int, String>
 ''');
   }
 
-  test_class_macro() async {
+  test_introspect_types_ClassDeclaration_isAbstract() async {
+    await _assertTypesPhaseIntrospectionText(r'''
+abstract class A {}
+''', r'''
+abstract class A
+''');
+  }
+
+  test_introspect_types_ClassDeclaration_mixins() async {
+    await _assertTypesPhaseIntrospectionText(r'''
+class A with B, C<int, String> {}
+''', r'''
+class A
+  mixins
+    B
+    C<int, String>
+''');
+  }
+
+  test_introspect_types_ClassDeclaration_superclass() async {
+    await _assertTypesPhaseIntrospectionText(r'''
+class A extends B {}
+''', r'''
+class A
+  superclass: B
+''');
+  }
+
+  test_introspect_types_ClassDeclaration_superclass_nullable() async {
+    await _assertTypesPhaseIntrospectionText(r'''
+class A extends B<int?> {}
+''', r'''
+class A
+  superclass: B<int?>
+''');
+  }
+
+  test_introspect_types_ClassDeclaration_superclass_typeArguments() async {
+    await _assertTypesPhaseIntrospectionText(r'''
+class A extends B<String, List<int>> {}
+''', r'''
+class A
+  superclass: B<String, List<int>>
+''');
+  }
+
+  test_introspect_types_ClassDeclaration_typeParameters() async {
+    await _assertTypesPhaseIntrospectionText(r'''
+class A<T, U extends List<T>> {}
+''', r'''
+class A
+  typeParameters
+    T
+    U
+      bound: List<T>
+''');
+  }
+
+  test_macroFlag_class() async {
     var library = await buildLibrary(r'''
 macro class A {}
 ''');
@@ -130,7 +215,7 @@
 ''');
   }
 
-  test_classAlias_macro() async {
+  test_macroFlag_classAlias() async {
     var library = await buildLibrary(r'''
 mixin M {}
 macro class A = Object with M;
@@ -186,4 +271,46 @@
       ),
     );
   }
+
+  /// Assert that the textual dump of the introspection information for
+  /// the first declaration in [declarationCode] is the same as [expected].
+  Future<void> _assertTypesPhaseIntrospectionText(
+      String declarationCode, String expected) async {
+    var actual = await _getDeclarationText(declarationCode);
+    if (actual != expected) {
+      print(actual);
+    }
+    expect(actual, expected);
+  }
+
+  /// The [declarationCode] is expected to start with a declaration. It may
+  /// include other declaration below, for example to reference them in
+  /// the first declaration.
+  ///
+  /// Use `DeclarationTextMacro` to generate a library that produces exactly
+  /// one part, with exactly one top-level constant `x`, with a string
+  /// literal initializer. We expect that the value of this literal is
+  /// the textual dump of the introspection information for the first
+  /// declaration.
+  Future<String> _getDeclarationText(String declarationCode) async {
+    newFile2(
+      '$testPackageLibPath/declaration_text.dart',
+      _declarationTextCode,
+    );
+
+    var library = await buildLibrary('''
+import 'declaration_text.dart';
+
+@DeclarationTextMacro()
+$declarationCode
+''', preBuildSequence: [
+      {'package:test/declaration_text.dart'}
+    ]);
+
+    var x = library.parts.single.topLevelVariables.single;
+    expect(x.name, 'x');
+    x as ConstTopLevelVariableElementImpl;
+    var x_literal = x.constantInitializer as SimpleStringLiteral;
+    return x_literal.value;
+  }
 }
diff --git a/pkg/analyzer/test/src/summary/repository_macro_kernel_builder.dart b/pkg/analyzer/test/src/summary/repository_macro_kernel_builder.dart
index 8af654a..e178097 100644
--- a/pkg/analyzer/test/src/summary/repository_macro_kernel_builder.dart
+++ b/pkg/analyzer/test/src/summary/repository_macro_kernel_builder.dart
@@ -96,6 +96,7 @@
 
   final _resourceProvider = MemoryResourceProvider(context: package_path.posix);
   late final Uint8List platformDillBytes;
+  late final Folder packageAnalyzerFolder;
 
   MacrosEnvironment._() {
     var physical = PhysicalResourceProvider.INSTANCE;
@@ -107,6 +108,8 @@
         .copyTo(
           packageSharedFolder.getChildAssumingFolder('lib/src'),
         );
+    packageAnalyzerFolder =
+        physical.getFolder(packageRoot).getChildAssumingFolder('analyzer');
 
     platformDillBytes = physical
         .getFile(io.Platform.resolvedExecutable)
diff --git a/pkg/analyzer_utilities/analysis_options.yaml b/pkg/analyzer_utilities/analysis_options.yaml
index f848bc6..9639f05 100644
--- a/pkg/analyzer_utilities/analysis_options.yaml
+++ b/pkg/analyzer_utilities/analysis_options.yaml
@@ -1,4 +1,4 @@
-include: package:pedantic/analysis_options.1.9.0.yaml
+include: package:lints/recommended.yaml
 
 analyzer:
   strong-mode:
diff --git a/pkg/analyzer_utilities/lib/check/check_target.dart b/pkg/analyzer_utilities/lib/check/check_target.dart
index d22a1ac..c7e33f5 100644
--- a/pkg/analyzer_utilities/lib/check/check_target.dart
+++ b/pkg/analyzer_utilities/lib/check/check_target.dart
@@ -17,7 +17,7 @@
   String get _indent => '  ' * (_depth + 1);
 
   Never fail(String message) {
-    test_package.fail(_describe() + '\n' + _indent + message);
+    test_package.fail('${_describe()}\n$_indent$message');
   }
 
   /// Chains to the given [value]; if a subsequent check fails, [describe]
@@ -27,7 +27,7 @@
     String Function(U value) describe,
   ) {
     return CheckTarget(value, _depth + 1, () {
-      return _describe() + '\n' + _indent + describe(value);
+      return '${_describe()}\n$_indent${describe(value)}';
     });
   }
 
diff --git a/pkg/analyzer_utilities/lib/tools.dart b/pkg/analyzer_utilities/lib/tools.dart
index 00724c7..aae7a2a 100644
--- a/pkg/analyzer_utilities/lib/tools.dart
+++ b/pkg/analyzer_utilities/lib/tools.dart
@@ -549,7 +549,7 @@
     var lines = text.split('\n');
     if (lines.last.isEmpty) {
       lines.removeLast();
-      buffer.add(dom.Text(lines.join('\n$indent') + '\n'));
+      buffer.add(dom.Text('${lines.join('\n$indent')}\n'));
       indentNeeded = true;
     } else {
       buffer.add(dom.Text(lines.join('\n$indent')));
diff --git a/pkg/analyzer_utilities/pubspec.yaml b/pkg/analyzer_utilities/pubspec.yaml
index d0aac18..2eac4b1 100644
--- a/pkg/analyzer_utilities/pubspec.yaml
+++ b/pkg/analyzer_utilities/pubspec.yaml
@@ -13,3 +13,6 @@
     path: ../meta
   path: any
   test: any
+
+dev_dependencies:
+  lints: any
diff --git a/pkg/analyzer_utilities/test/check/check_test.dart b/pkg/analyzer_utilities/test/check/check_test.dart
index b88501c..9e5ee83 100644
--- a/pkg/analyzer_utilities/test/check/check_test.dart
+++ b/pkg/analyzer_utilities/test/check/check_test.dart
@@ -269,6 +269,7 @@
       });
     });
     group('nullability', () {
+      // ignore: unnecessary_nullable_for_final_variable_declarations
       const int? notNullable = 0;
       const int? nullable = null;
       test('isNotNull', () {
diff --git a/pkg/compiler/lib/src/elements/types.dart b/pkg/compiler/lib/src/elements/types.dart
index 6f5106a..dbcb79d 100644
--- a/pkg/compiler/lib/src/elements/types.dart
+++ b/pkg/compiler/lib/src/elements/types.dart
@@ -30,7 +30,7 @@
   List<DartType> _readDartTypes(
       List<FunctionTypeVariable> functionTypeVariables) {
     int count = readInt();
-    List<DartType> types = List<DartType>.filled(count, null);
+    List<DartType> types = List<DartType>.filled(count, const NeverType._());
     for (int index = 0; index < count; index++) {
       types[index] = DartType.readFromDataSource(this, functionTypeVariables);
     }
@@ -51,7 +51,14 @@
 abstract class DartType {
   const DartType();
 
-  factory DartType.readFromDataSource(DataSourceReader source,
+  static DartType readFromDataSource(DataSourceReader source,
+      List<FunctionTypeVariable> functionTypeVariables) {
+    DartType type = readFromDataSourceOrNull(source, functionTypeVariables);
+    if (type == null) throw StateError('Unexpected null DartType');
+    return type;
+  }
+
+  static DartType readFromDataSourceOrNull(DataSourceReader source,
       List<FunctionTypeVariable> functionTypeVariables) {
     DartTypeKind kind = source.readEnum(DartTypeKind.values);
     switch (kind) {
diff --git a/pkg/compiler/lib/src/js_backend/backend_usage.dart b/pkg/compiler/lib/src/js_backend/backend_usage.dart
index 245b922..7e08bd6 100644
--- a/pkg/compiler/lib/src/js_backend/backend_usage.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_usage.dart
@@ -369,7 +369,7 @@
     Set<RuntimeTypeUse> runtimeTypeUses = source.readList(() {
       RuntimeTypeUseKind kind = source.readEnum(RuntimeTypeUseKind.values);
       DartType receiverType = source.readDartType();
-      DartType argumentType = source.readDartType(allowNull: true);
+      DartType /*?*/ argumentType = source.readDartTypeOrNull();
       return RuntimeTypeUse(kind, receiverType, argumentType);
     }).toSet();
     bool needToInitializeIsolateAffinityTag = source.readBool();
@@ -407,7 +407,7 @@
     sink.writeList(runtimeTypeUses, (RuntimeTypeUse runtimeTypeUse) {
       sink.writeEnum(runtimeTypeUse.kind);
       sink.writeDartType(runtimeTypeUse.receiverType);
-      sink.writeDartType(runtimeTypeUse.argumentType, allowNull: true);
+      sink.writeDartTypeOrNull(runtimeTypeUse.argumentType);
     });
     sink.writeBool(needToInitializeIsolateAffinityTag);
     sink.writeBool(needToInitializeDispatchProperty);
diff --git a/pkg/compiler/lib/src/js_model/closure.dart b/pkg/compiler/lib/src/js_model/closure.dart
index d8e2b26..cb36eb7 100644
--- a/pkg/compiler/lib/src/js_model/closure.dart
+++ b/pkg/compiler/lib/src/js_model/closure.dart
@@ -1227,7 +1227,8 @@
     source.begin(tag);
     ClosureMemberDefinition definition =
         MemberDefinition.readFromDataSource(source);
-    InterfaceType memberThisType = source.readDartType(allowNull: true);
+    InterfaceType /*?*/ memberThisType =
+        source.readDartTypeOrNull() as InterfaceType /*?*/;
     FunctionType functionType = source.readDartType();
     ir.FunctionNode functionNode = source.readTreeNode();
     ClassTypeVariableAccess classTypeVariableAccess =
@@ -1242,7 +1243,7 @@
     sink.writeEnum(JMemberDataKind.closureFunction);
     sink.begin(tag);
     definition.writeToDataSink(sink);
-    sink.writeDartType(memberThisType, allowNull: true);
+    sink.writeDartTypeOrNull(memberThisType);
     sink.writeDartType(functionType);
     sink.writeTreeNode(functionNode);
     sink.writeEnum(classTypeVariableAccess);
@@ -1280,7 +1281,8 @@
   factory ClosureFieldData.readFromDataSource(DataSourceReader source) {
     source.begin(tag);
     MemberDefinition definition = MemberDefinition.readFromDataSource(source);
-    InterfaceType memberThisType = source.readDartType(allowNull: true);
+    InterfaceType /*?*/ memberThisType =
+        source.readDartTypeOrNull() as InterfaceType /*?*/;
     source.end(tag);
     return ClosureFieldData(definition, memberThisType);
   }
@@ -1290,7 +1292,7 @@
     sink.writeEnum(JMemberDataKind.closureField);
     sink.begin(tag);
     definition.writeToDataSink(sink);
-    sink.writeDartType(memberThisType, allowNull: true);
+    sink.writeDartTypeOrNull(memberThisType);
     sink.end(tag);
   }
 
diff --git a/pkg/compiler/lib/src/js_model/env.dart b/pkg/compiler/lib/src/js_model/env.dart
index 258eb0f..c987921 100644
--- a/pkg/compiler/lib/src/js_model/env.dart
+++ b/pkg/compiler/lib/src/js_model/env.dart
@@ -753,7 +753,8 @@
   factory SignatureFunctionData.readFromDataSource(DataSourceReader source) {
     source.begin(tag);
     MemberDefinition definition = MemberDefinition.readFromDataSource(source);
-    InterfaceType memberThisType = source.readDartType(allowNull: true);
+    InterfaceType /*?*/ memberThisType =
+        source.readDartTypeOrNull() as InterfaceType /*?*/;
     List<ir.TypeParameter> typeParameters = source.readTypeParameterNodes();
     ClassTypeVariableAccess classTypeVariableAccess =
         source.readEnum(ClassTypeVariableAccess.values);
@@ -767,7 +768,7 @@
     sink.writeEnum(JMemberDataKind.signature);
     sink.begin(tag);
     definition.writeToDataSink(sink);
-    sink.writeDartType(memberThisType, allowNull: true);
+    sink.writeDartTypeOrNull(memberThisType);
     sink.writeTypeParameterNodes(typeParameters);
     sink.writeEnum(classTypeVariableAccess);
     sink.end(tag);
diff --git a/pkg/compiler/lib/src/js_model/type_recipe.dart b/pkg/compiler/lib/src/js_model/type_recipe.dart
index 3a84def..6b3025e 100644
--- a/pkg/compiler/lib/src/js_model/type_recipe.dart
+++ b/pkg/compiler/lib/src/js_model/type_recipe.dart
@@ -282,9 +282,9 @@
 
   static FullTypeEnvironmentRecipe _readFromDataSource(
       DataSourceReader source) {
-    InterfaceType classType =
-        source.readDartType(allowNull: true) as InterfaceType;
-    List<DartType> types = source.readDartTypes(emptyAsNull: true) ?? const [];
+    InterfaceType /*?*/ classType =
+        source.readDartTypeOrNull() as InterfaceType /*?*/;
+    List<DartType> types = source.readDartTypes();
     return FullTypeEnvironmentRecipe(classType: classType, types: types);
   }
 
@@ -293,8 +293,8 @@
 
   @override
   void _writeToDataSink(DataSinkWriter sink) {
-    sink.writeDartType(classType, allowNull: true);
-    sink.writeDartTypes(types, allowNull: false);
+    sink.writeDartTypeOrNull(classType);
+    sink.writeDartTypes(types);
   }
 
   @override
diff --git a/pkg/compiler/lib/src/serialization/sink.dart b/pkg/compiler/lib/src/serialization/sink.dart
index 83a5af9..e7dfec8 100644
--- a/pkg/compiler/lib/src/serialization/sink.dart
+++ b/pkg/compiler/lib/src/serialization/sink.dart
@@ -589,40 +589,43 @@
     }
   }
 
-  /// Writes the type [value] to this data sink. If [allowNull] is `true`,
-  /// [value] is allowed to be `null`.
-  void writeDartType(DartType value, {bool allowNull = false}) {
+  /// Writes the type [value] to this data sink.
+  void writeDartType(DartType value) {
     _writeDataKind(DataKind.dartType);
-    _writeDartType(value, [], allowNull: allowNull);
+    value.writeToDataSink(this, []);
   }
 
-  void _writeDartType(
-      DartType value, List<FunctionTypeVariable> functionTypeVariables,
-      {bool allowNull = false}) {
+  /// Writes the optional type [value] to this data sink.
+  void writeDartTypeOrNull(DartType /*?*/ value) {
+    _writeDataKind(DataKind.dartType);
     if (value == null) {
-      if (!allowNull) {
-        throw UnsupportedError("Missing DartType is not allowed.");
-      }
       writeEnum(DartTypeKind.none);
     } else {
-      value.writeToDataSink(this, functionTypeVariables);
+      value.writeToDataSink(this, []);
     }
   }
 
-  /// Writes the type [values] to this data sink. If [allowNull] is `true`,
-  /// [values] is allowed to be `null`.
+  /// Writes the types [values] to this data sink. If [values] is null, write a
+  /// zero-length iterable.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSourceReader.readDartTypesOrNull].
+  void writeDartTypesOrNull(Iterable<DartType> /*?*/ values) {
+    if (values == null) {
+      writeInt(0);
+    } else {
+      writeDartTypes(values);
+    }
+  }
+
+  /// Writes the types [values] to this data sink.
   ///
   /// This is a convenience method to be used together with
   /// [DataSourceReader.readDartTypes].
-  void writeDartTypes(Iterable<DartType> values, {bool allowNull = false}) {
-    if (values == null) {
-      assert(allowNull);
-      writeInt(0);
-    } else {
-      writeInt(values.length);
-      for (DartType value in values) {
-        writeDartType(value);
-      }
+  void writeDartTypes(Iterable<DartType> values) {
+    writeInt(values.length);
+    for (DartType value in values) {
+      writeDartType(value);
     }
   }
 
diff --git a/pkg/compiler/lib/src/serialization/source.dart b/pkg/compiler/lib/src/serialization/source.dart
index ee1b7e0..ed758d94 100644
--- a/pkg/compiler/lib/src/serialization/source.dart
+++ b/pkg/compiler/lib/src/serialization/source.dart
@@ -633,23 +633,35 @@
     return list;
   }
 
-  /// Reads a type from this data source. If [allowNull], the returned type is
-  /// allowed to be `null`.
-  DartType readDartType({bool allowNull = false}) {
+  /// Reads a type from this data source.
+  DartType /*!*/ readDartType() {
     _checkDataKind(DataKind.dartType);
-    DartType type = DartType.readFromDataSource(this, []);
-    assert(type != null || allowNull);
-    return type;
+    return DartType.readFromDataSource(this, []);
   }
 
-  /// Reads a list of types from this data source. If [emptyAsNull] is `true`,
-  /// `null` is returned instead of an empty list.
+  /// Reads a nullable type from this data source.
+  DartType /*?*/ readDartTypeOrNull() {
+    _checkDataKind(DataKind.dartType);
+    return DartType.readFromDataSourceOrNull(this, []);
+  }
+
+  /// Reads a list of types from this data source.
   ///
   /// This is a convenience method to be used together with
   /// [DataSinkWriter.writeDartTypes].
-  List<DartType> readDartTypes({bool emptyAsNull = false}) {
+  List<DartType> readDartTypes() {
+    // Share the list when empty.
+    return readDartTypesOrNull() ?? const [];
+  }
+
+  /// Reads a list of types from this data source. Returns `null` instead of an
+  /// empty list.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSinkWriter.writeDartTypes].
+  List<DartType> /*?*/ readDartTypesOrNull() {
     int count = readInt();
-    if (count == 0 && emptyAsNull) return null;
+    if (count == 0) return null;
     List<DartType> list = List<DartType>.filled(count, null);
     for (int i = 0; i < count; i++) {
       list[i] = readDartType();
diff --git a/pkg/compiler/lib/src/universe/use.dart b/pkg/compiler/lib/src/universe/use.dart
index 2d7159a..9ddd60d 100644
--- a/pkg/compiler/lib/src/universe/use.dart
+++ b/pkg/compiler/lib/src/universe/use.dart
@@ -69,7 +69,7 @@
     if (hasConstraint) {
       receiverConstraint = source.readAbstractValue();
     }
-    List<DartType> typeArguments = source.readDartTypes(emptyAsNull: true);
+    List<DartType> typeArguments = source.readDartTypesOrNull();
     source.end(tag);
     return DynamicUse(selector, receiverConstraint, typeArguments);
   }
@@ -86,7 +86,7 @@
             "Unsupported receiver constraint: ${receiverConstraint}");
       }
     }
-    sink.writeDartTypes(_typeArguments, allowNull: true);
+    sink.writeDartTypesOrNull(_typeArguments);
     sink.end(tag);
   }
 
@@ -221,12 +221,13 @@
     source.begin(tag);
     MemberEntity element = source.readMember();
     StaticUseKind kind = source.readEnum(StaticUseKind.values);
-    InterfaceType type = source.readDartType(allowNull: true);
+    InterfaceType /*?*/ type =
+        source.readDartTypeOrNull() as InterfaceType /*?*/;
     CallStructure callStructure =
         source.readValueOrNull(() => CallStructure.readFromDataSource(source));
     ImportEntity deferredImport = source.readImportOrNull();
     ConstantValue constant = source.readConstantOrNull();
-    List<DartType> typeArguments = source.readDartTypes(emptyAsNull: true);
+    List<DartType> typeArguments = source.readDartTypesOrNull();
     source.end(tag);
     return StaticUse.internal(element, kind,
         type: type,
@@ -241,12 +242,12 @@
     assert(element is MemberEntity, "Unsupported entity: $element");
     sink.writeMember(element);
     sink.writeEnum(kind);
-    sink.writeDartType(type, allowNull: true);
+    sink.writeDartTypeOrNull(type);
     sink.writeValueOrNull(
         callStructure, (CallStructure c) => c.writeToDataSink(sink));
     sink.writeImportOrNull(deferredImport);
     sink.writeConstantOrNull(constant);
-    sink.writeDartTypes(typeArguments, allowNull: true);
+    sink.writeDartTypesOrNull(typeArguments);
     sink.end(tag);
   }
 
diff --git a/pkg/dds/analysis_options.yaml b/pkg/dds/analysis_options.yaml
index 6b1919a..8f7435b 100644
--- a/pkg/dds/analysis_options.yaml
+++ b/pkg/dds/analysis_options.yaml
@@ -1,4 +1,4 @@
-include: package:pedantic/analysis_options.1.8.0.yaml
+include: package:lints/core.yaml
 
 linter:
   rules:
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
index 1c8be40..eecc025 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
@@ -8,7 +8,6 @@
 import 'dart:math' as math;
 
 import 'package:path/path.dart' as path;
-import 'package:pedantic/pedantic.dart';
 import 'package:vm_service/vm_service.dart' as vm;
 
 import '../logging.dart';
diff --git a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
index 0a2b63c..468ea09 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
@@ -7,7 +7,6 @@
 import 'dart:io';
 import 'dart:math' as math;
 
-import 'package:pedantic/pedantic.dart';
 import 'package:vm_service/vm_service.dart' as vm;
 
 import '../logging.dart';
diff --git a/pkg/dds/lib/src/dap/adapters/mixins.dart b/pkg/dds/lib/src/dap/adapters/mixins.dart
index 53de6fc..8ecf1eb 100644
--- a/pkg/dds/lib/src/dap/adapters/mixins.dart
+++ b/pkg/dds/lib/src/dap/adapters/mixins.dart
@@ -7,7 +7,6 @@
 import 'dart:io';
 
 import 'package:path/path.dart' as path;
-import 'package:pedantic/pedantic.dart';
 
 import '../logging.dart';
 import '../protocol_common.dart';
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index 8de53ff..a52fd13 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -11,7 +11,6 @@
 
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
 import 'package:meta/meta.dart';
-import 'package:pedantic/pedantic.dart';
 import 'package:shelf/shelf.dart';
 import 'package:shelf/shelf_io.dart' as io;
 import 'package:shelf_proxy/shelf_proxy.dart';
diff --git a/pkg/dds/lib/src/utils/mutex.dart b/pkg/dds/lib/src/utils/mutex.dart
index 9aff468..dcbbf59 100644
--- a/pkg/dds/lib/src/utils/mutex.dart
+++ b/pkg/dds/lib/src/utils/mutex.dart
@@ -36,7 +36,7 @@
       // Reinitialize if this is the only weakly guarded scope.
       _outstandingReadersCompleter = Completer<void>();
     }
-    final result;
+    final T result;
     try {
       await _acquireLock(strong: false);
       result = await criticalSection();
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 1430222..bfb5a961 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -8,7 +8,7 @@
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
 
 environment:
-  sdk: '>=2.13.0 <3.0.0'
+  sdk: '>=2.14.0 <3.0.0'
 
 dependencies:
   args: ^2.0.0
@@ -21,7 +21,6 @@
   json_rpc_2: ^3.0.0
   meta: ^1.1.8
   path: ^1.8.0
-  pedantic: ^1.7.0
   shelf: ^1.0.0
   shelf_proxy: ^1.0.0
   shelf_static: ^1.0.0
@@ -34,5 +33,6 @@
 
 dev_dependencies:
   http: ^0.13.0
+  lints: any
   test: ^1.0.0
   webdriver: ^3.0.0
diff --git a/pkg/dds/test/dap/integration/debug_eval_test.dart b/pkg/dds/test/dap/integration/debug_eval_test.dart
index 9d6ff1b..a21b5cb 100644
--- a/pkg/dds/test/dap/integration/debug_eval_test.dart
+++ b/pkg/dds/test/dap/integration/debug_eval_test.dart
@@ -19,7 +19,7 @@
   group('debug mode evaluation', () {
     test('evaluates expressions with simple results', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   var a = 1;
   var b = 2;
@@ -37,7 +37,7 @@
 
     test('evaluates expressions with complex results', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(simpleBreakpointProgram);
+      final testFile = dap.createTestFile(simpleBreakpointProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -60,7 +60,7 @@
 
     test('evaluates expressions ending with semicolons', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   var a = 1;
   var b = 2;
@@ -77,7 +77,7 @@
         'evaluates complex expressions expressions with evaluateToStringInDebugViews=true',
         () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(simpleBreakpointProgram);
+      final testFile = dap.createTestFile(simpleBreakpointProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(
@@ -99,7 +99,7 @@
         'evaluates $threadExceptionExpression to the threads exception (simple type)',
         () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = dap.createTestFile(r'''
 void main(List<String> args) {
   throw 'my error';
 }''');
@@ -118,7 +118,7 @@
         'evaluates $threadExceptionExpression to the threads exception (complex type)',
         () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = dap.createTestFile(r'''
 void main(List<String> args) {
   throw Exception('my error');
 }''');
@@ -137,7 +137,7 @@
         'evaluates $threadExceptionExpression.x.y to x.y on the threads exception',
         () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = dap.createTestFile(r'''
 void main(List<String> args) {
   throw Exception('12345');
 }
@@ -154,7 +154,7 @@
 
     test('can evaluate expressions in non-top frames', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   var a = 999;
   foo();
@@ -175,7 +175,7 @@
 
     test('returns the full message for evaluation errors', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(simpleBreakpointProgram);
+      final testFile = dap.createTestFile(simpleBreakpointProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -198,7 +198,7 @@
 
     test('returns short errors for evaluation in "watch" context', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(simpleBreakpointProgram);
+      final testFile = dap.createTestFile(simpleBreakpointProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
diff --git a/pkg/dds/test/dap/integration/debug_services_test.dart b/pkg/dds/test/dap/integration/debug_services_test.dart
index 98f698e..99dd7f6 100644
--- a/pkg/dds/test/dap/integration/debug_services_test.dart
+++ b/pkg/dds/test/dap/integration/debug_services_test.dart
@@ -19,7 +19,7 @@
   group('debug mode', () {
     test('reports the VM Service URI to the client', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(simpleBreakpointProgram);
+      final testFile = dap.createTestFile(simpleBreakpointProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       await client.hitBreakpoint(testFile, breakpointLine);
@@ -31,7 +31,7 @@
 
     test('exposes VM services to the client', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(simpleBreakpointProgram);
+      final testFile = dap.createTestFile(simpleBreakpointProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       // Capture our test service registration.
@@ -68,7 +68,7 @@
 
     test('exposes VM service extensions to the client', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(serviceExtensionProgram);
+      final testFile = dap.createTestFile(serviceExtensionProgram);
 
       // Capture our test service registration.
       final serviceExtensionAddedFuture = client.serviceExtensionAddedEvents
diff --git a/pkg/dds/test/dap/integration/debug_stack_test.dart b/pkg/dds/test/dap/integration/debug_stack_test.dart
index 72f2cb5..e14458f 100644
--- a/pkg/dds/test/dap/integration/debug_stack_test.dart
+++ b/pkg/dds/test/dap/integration/debug_stack_test.dart
@@ -18,7 +18,7 @@
   group('debug mode stack trace', () {
     test('includes expected names and async boundaries', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(simpleAsyncProgram);
+      final testFile = dap.createTestFile(simpleAsyncProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -60,7 +60,7 @@
 
     test('only sets canRestart where VM can rewind', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(simpleAsyncProgram);
+      final testFile = dap.createTestFile(simpleAsyncProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -91,7 +91,7 @@
 
     test('deemphasizes SDK frames when debugSdk=false', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(sdkStackFrameProgram);
+      final testFile = dap.createTestFile(sdkStackFrameProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(
@@ -123,7 +123,7 @@
 
     test('does not deemphasize SDK frames when debugSdk=true', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(sdkStackFrameProgram);
+      final testFile = dap.createTestFile(sdkStackFrameProgram);
       final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(
diff --git a/pkg/dds/test/dap/integration/debug_test.dart b/pkg/dds/test/dap/integration/debug_test.dart
index 974692f..9044657 100644
--- a/pkg/dds/test/dap/integration/debug_test.dart
+++ b/pkg/dds/test/dap/integration/debug_test.dart
@@ -114,7 +114,7 @@
       ], eagerError: true);
 
       // Resume and expect termination.
-      await await Future.wait([
+      await Future.wait([
         dap.client.event('terminated'),
         dap.client.continue_((await stop).threadId!),
       ], eagerError: true);
diff --git a/pkg/dds/test/dap/integration/debug_variables_test.dart b/pkg/dds/test/dap/integration/debug_variables_test.dart
index 86f4412..e78ecd0 100644
--- a/pkg/dds/test/dap/integration/debug_variables_test.dart
+++ b/pkg/dds/test/dap/integration/debug_variables_test.dart
@@ -18,7 +18,7 @@
   group('debug mode variables', () {
     test('provides variable list for frames', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = 1;
   foo();
@@ -58,7 +58,7 @@
 
     test('provides simple exception types for frames', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = dap.createTestFile(r'''
 void main(List<String> args) {
   throw 'my error';
 }
@@ -80,7 +80,7 @@
 
     test('provides complex exception types for frames', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = dap.createTestFile(r'''
 void main(List<String> args) {
   throw ArgumentError.notNull('args');
 }
@@ -104,7 +104,7 @@
 
     test('includes simple variable fields', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = DateTime(2000, 1, 1);
   print('Hello!'); $breakpointMarker
@@ -126,7 +126,7 @@
     test('includes variable getters when evaluateGettersInDebugViews=true',
         () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = DateTime(2000, 1, 1);
   print('Hello!'); $breakpointMarker
@@ -172,7 +172,7 @@
 
     test('renders a simple list', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = ["first", "second", "third"];
   print('Hello!'); $breakpointMarker
@@ -195,7 +195,7 @@
 
     test('renders a simple list subset', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = ["first", "second", "third"];
   print('Hello!'); $breakpointMarker
@@ -218,7 +218,7 @@
 
     test('renders a simple map with keys/values', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = {
     'zero': 0,
@@ -261,7 +261,7 @@
 
     test('renders a simple map subset', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = {
     'zero': 0,
@@ -291,7 +291,7 @@
 
     test('renders a complex map with keys/values', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = {
     DateTime(2000, 1, 1): Exception("my error")
@@ -349,7 +349,7 @@
 
     test('calls toString() on custom classes', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 class Foo {
   toString() => 'Bar!';
 }
@@ -386,7 +386,7 @@
       //
       //     myVariable: Foo (Instance of Foo)
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 class Foo {}
 
 void main() {
@@ -416,7 +416,7 @@
 
     test('handles errors in getters', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile('''
+      final testFile = dap.createTestFile('''
 class Foo {
   String get doesNotThrow => "success";
   String get throws => throw Exception('err');
diff --git a/pkg/dds/test/dap/integration/test_server.dart b/pkg/dds/test/dap/integration/test_server.dart
index ccb4c1d..519f5e1 100644
--- a/pkg/dds/test/dap/integration/test_server.dart
+++ b/pkg/dds/test/dap/integration/test_server.dart
@@ -10,7 +10,6 @@
 import 'package:dds/src/dap/logging.dart';
 import 'package:dds/src/dap/server.dart';
 import 'package:path/path.dart' as path;
-import 'package:pedantic/pedantic.dart';
 
 /// Enable to run from local source (useful in development).
 const runFromSource = false;
@@ -94,7 +93,7 @@
   @override
   Future<void> stop() async {
     _isShuttingDown = true;
-    await _process.kill();
+    _process.kill();
     await _process.exitCode;
   }
 
diff --git a/pkg/dds/test/handles_client_disconnect_state_error_test.dart b/pkg/dds/test/handles_client_disconnect_state_error_test.dart
index c9d35fa..9359389 100644
--- a/pkg/dds/test/handles_client_disconnect_state_error_test.dart
+++ b/pkg/dds/test/handles_client_disconnect_state_error_test.dart
@@ -8,7 +8,6 @@
 import 'package:dds/src/dds_impl.dart';
 import 'package:dds/src/rpc_error_codes.dart';
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
-import 'package:pedantic/pedantic.dart';
 import 'package:test/test.dart';
 import 'package:web_socket_channel/web_socket_channel.dart';
 
@@ -49,7 +48,7 @@
   test('StateError handled by _StreamManager.clientDisconnect', () async {
     final dds = await DartDevelopmentService.startDartDevelopmentService(
         Uri(scheme: 'http'));
-    final ws = await WebSocketChannel.connect(dds.uri!.replace(scheme: 'ws'));
+    final ws = WebSocketChannel.connect(dds.uri!.replace(scheme: 'ws'));
 
     // Create a VM service client that connects to DDS.
     final client = json_rpc.Client(ws.cast<String>());
@@ -76,7 +75,7 @@
       () async {
     final dds = await DartDevelopmentService.startDartDevelopmentService(
         Uri(scheme: 'http'));
-    final ws = await WebSocketChannel.connect(dds.uri!.replace(scheme: 'ws'));
+    final ws = WebSocketChannel.connect(dds.uri!.replace(scheme: 'ws'));
 
     // Create a VM service client that connects to DDS.
     final client = json_rpc.Client(ws.cast<String>());
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index 462413c..68b49e4 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -234,8 +234,8 @@
   P(use_field_guards, bool, true, "Use field guards and track field types")    \
   C(use_osr, false, true, bool, true, "Use OSR")                               \
   P(use_slow_path, bool, false, "Whether to avoid inlined fast paths.")        \
-  P(verbose_gc, bool, false, "Enables verbose GC.")                            \
-  P(verbose_gc_hdr, int, 40, "Print verbose GC header interval.")              \
+  R(verbose_gc, false, bool, false, "Enables verbose GC.")                     \
+  R(verbose_gc_hdr, 40, int, 40, "Print verbose GC header interval.")          \
   R(verify_after_gc, false, bool, false,                                       \
     "Enables heap verification after GC.")                                     \
   R(verify_before_gc, false, bool, false,                                      \
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index a57bcfc..481bafe 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -522,8 +522,7 @@
                                     ? VMTag::kGCIdleTagId
                                     : VMTag::kGCOldSpaceTagId);
     TIMELINE_FUNCTION_GC_DURATION(thread, "CollectOldGeneration");
-    old_space_.CollectGarbage(thread, /*compact=*/type == GCType::kMarkCompact,
-                              /*finalize=*/true);
+    old_space_.CollectGarbage(type == GCType::kMarkCompact, true /* finish */);
     RecordAfterGC(type);
     PrintStats();
 #if defined(SUPPORT_TIMELINE)
@@ -624,7 +623,7 @@
                                   ? VMTag::kGCIdleTagId
                                   : VMTag::kGCOldSpaceTagId);
   TIMELINE_FUNCTION_GC_DURATION(thread, "StartConcurrentMarking");
-  old_space_.CollectGarbage(thread, /*compact=*/false, /*finalize=*/false);
+  old_space_.CollectGarbage(/*compact=*/false, /*finalize=*/false);
   RecordAfterGC(GCType::kStartConcurrentMark);
   PrintStats();
 #if defined(SUPPORT_TIMELINE)
@@ -1020,7 +1019,8 @@
   stats_.before_.micros_ = OS::GetCurrentMonotonicMicros();
   stats_.before_.new_ = new_space_.GetCurrentUsage();
   stats_.before_.old_ = old_space_.GetCurrentUsage();
-  stats_.before_.store_buffer_ = isolate_group_->store_buffer()->Size();
+  for (int i = 0; i < GCStats::kTimeEntries; i++)
+    stats_.times_[i] = 0;
 }
 
 static double AvgCollectionPeriod(int64_t run_time, intptr_t collections) {
@@ -1043,7 +1043,6 @@
   }
   stats_.after_.new_ = new_space_.GetCurrentUsage();
   stats_.after_.old_ = old_space_.GetCurrentUsage();
-  stats_.after_.store_buffer_ = isolate_group_->store_buffer()->Size();
 #ifndef PRODUCT
   // For now we'll emit the same GC events on all isolates.
   if (Service::gc_stream.enabled()) {
@@ -1115,20 +1114,24 @@
 }
 
 void Heap::PrintStats() {
+#if !defined(PRODUCT)
   if (!FLAG_verbose_gc) return;
 
   if ((FLAG_verbose_gc_hdr != 0) &&
       (((stats_.num_ - 1) % FLAG_verbose_gc_hdr) == 0)) {
     OS::PrintErr(
-        "[              |                          |     |       |      | new "
-        "gen     | new gen     | new gen | old gen       | old gen       | old "
-        "gen     |  store  | delta used   ]\n"
-        "[ GC isolate   | space (reason)           | GC# | start | time | used "
-        "(MB)   | capacity MB | external| used (MB)     | capacity (MB) | "
-        "external MB |  buffer | new  | old   ]\n"
+        "[              |                          |     |       |      "
+        "| new gen     | new gen     | new gen "
+        "| old gen       | old gen       | old gen     "
+        "| sweep | safe- | roots/| stbuf/| tospc/| weaks/  ]\n"
+        "[ GC isolate   | space (reason)           | GC# | start | time "
+        "| used (MB)   | capacity MB | external"
+        "| used (MB)     | capacity (MB) | external MB "
+        "| thread| point |marking| reset | sweep |swplrge  ]\n"
         "[              |                          |     |  (s)  | (ms) "
-        "|before| after|before| after| b4 |aftr| before| after | before| after "
-        "|before| after| b4 |aftr| (MB) | (MB)  ]\n");
+        "|before| after|before| after| b4 |aftr"
+        "| before| after | before| after |before| after"
+        "| (ms)  | (ms)  | (ms)  | (ms)  | (ms)  | (ms)    ]\n");
   }
 
   // clang-format off
@@ -1143,8 +1146,7 @@
     "%6.1f, %6.1f, "   // old gen: in use before/after
     "%6.1f, %6.1f, "   // old gen: capacity before/after
     "%5.1f, %5.1f, "   // old gen: external before/after
-    "%3" Pd ", %3" Pd ", "   // store buffer: before/after
-    "%5.1f, %6.1f, "   // delta used: new gen/old gen
+    "%6.2f, %6.2f, %6.2f, %6.2f, %6.2f, %6.2f, "  // times
     "]\n",  // End with a comma to make it easier to import in spreadsheets.
     isolate_group()->source()->name,
     GCTypeToString(stats_.type_),
@@ -1165,13 +1167,14 @@
     WordsToMB(stats_.after_.old_.capacity_in_words),
     WordsToMB(stats_.before_.old_.external_in_words),
     WordsToMB(stats_.after_.old_.external_in_words),
-    stats_.before_.store_buffer_,
-    stats_.after_.store_buffer_,
-    WordsToMB(stats_.after_.new_.used_in_words -
-              stats_.before_.new_.used_in_words),
-    WordsToMB(stats_.after_.old_.used_in_words -
-              stats_.before_.old_.used_in_words));
+    MicrosecondsToMilliseconds(stats_.times_[0]),
+    MicrosecondsToMilliseconds(stats_.times_[1]),
+    MicrosecondsToMilliseconds(stats_.times_[2]),
+    MicrosecondsToMilliseconds(stats_.times_[3]),
+    MicrosecondsToMilliseconds(stats_.times_[4]),
+    MicrosecondsToMilliseconds(stats_.times_[5]));
   // clang-format on
+#endif  // !defined(PRODUCT)
 }
 
 void Heap::PrintStatsToTimeline(TimelineEventScope* event, GCReason reason) {
diff --git a/runtime/vm/heap/heap.h b/runtime/vm/heap/heap.h
index 112c59f..179122d 100644
--- a/runtime/vm/heap/heap.h
+++ b/runtime/vm/heap/heap.h
@@ -256,6 +256,12 @@
   void ForwardWeakEntries(ObjectPtr before_object, ObjectPtr after_object);
   void ForwardWeakTables(ObjectPointerVisitor* visitor);
 
+  // Stats collection.
+  void RecordTime(int id, int64_t micros) {
+    ASSERT((id >= 0) && (id < GCStats::kTimeEntries));
+    stats_.times_[id] = micros;
+  }
+
   void UpdateGlobalMaxUsed();
 
   static bool IsAllocatableInNewSpace(intptr_t size) {
@@ -308,14 +314,16 @@
       int64_t micros_;
       SpaceUsage new_;
       SpaceUsage old_;
-      intptr_t store_buffer_;
 
      private:
       DISALLOW_COPY_AND_ASSIGN(Data);
     };
 
+    enum { kTimeEntries = 6 };
+
     Data before_;
     Data after_;
+    int64_t times_[kTimeEntries];
 
    private:
     DISALLOW_COPY_AND_ASSIGN(GCStats);
diff --git a/runtime/vm/heap/pages.cc b/runtime/vm/heap/pages.cc
index f1c3ed4..6c7c909 100644
--- a/runtime/vm/heap/pages.cc
+++ b/runtime/vm/heap/pages.cc
@@ -1100,7 +1100,7 @@
   }
 }
 
-void PageSpace::CollectGarbage(Thread* thread, bool compact, bool finalize) {
+void PageSpace::CollectGarbage(bool compact, bool finalize) {
   ASSERT(GrowthControlState());
 
   if (!finalize) {
@@ -1112,11 +1112,16 @@
 #endif
   }
 
+  Thread* thread = Thread::Current();
+  const int64_t pre_safe_point = OS::GetCurrentMonotonicMicros();
   GcSafepointOperationScope safepoint_scope(thread);
 
+  const int64_t pre_wait_for_sweepers = OS::GetCurrentMonotonicMicros();
   // Wait for pending tasks to complete and then account for the driver task.
+  Phase waited_for;
   {
     MonitorLocker locker(tasks_lock());
+    waited_for = phase();
     if (!finalize &&
         (phase() == kMarking || phase() == kAwaitingFinalization)) {
       // Concurrent mark is already running.
@@ -1131,11 +1136,26 @@
     set_tasks(1);
   }
 
+  if (FLAG_verbose_gc) {
+    const int64_t wait =
+        OS::GetCurrentMonotonicMicros() - pre_wait_for_sweepers;
+    if (waited_for == kMarking) {
+      THR_Print("Waited %" Pd64 " us for concurrent marking to finish.\n",
+                wait);
+    } else if (waited_for == kSweepingRegular || waited_for == kSweepingLarge) {
+      THR_Print("Waited %" Pd64 " us for concurrent sweeping to finish.\n",
+                wait);
+    }
+  }
+
   // Ensure that all threads for this isolate are at a safepoint (either
   // stopped or in native code). We have guards around Newgen GC and oldgen GC
   // to ensure that if two threads are racing to collect at the same time the
   // loser skips collection and goes straight to allocation.
-  CollectGarbageHelper(thread, compact, finalize);
+  {
+    CollectGarbageHelper(compact, finalize, pre_wait_for_sweepers,
+                         pre_safe_point);
+  }
 
   // Done, reset the task count.
   {
@@ -1145,9 +1165,11 @@
   }
 }
 
-void PageSpace::CollectGarbageHelper(Thread* thread,
-                                     bool compact,
-                                     bool finalize) {
+void PageSpace::CollectGarbageHelper(bool compact,
+                                     bool finalize,
+                                     int64_t pre_wait_for_sweepers,
+                                     int64_t pre_safe_point) {
+  Thread* thread = Thread::Current();
   ASSERT(thread->IsAtSafepoint());
   auto isolate_group = heap_->isolate_group();
   ASSERT(isolate_group == IsolateGroup::Current());
@@ -1160,7 +1182,7 @@
       [&](Isolate* isolate) { isolate->field_table()->FreeOldTables(); },
       /*at_safepoint=*/true);
 
-  NoSafepointScope no_safepoints(thread);
+  NoSafepointScope no_safepoints;
 
   if (FLAG_print_free_list_before_gc) {
     for (intptr_t i = 0; i < num_freelists_; i++) {
@@ -1202,6 +1224,8 @@
   delete marker_;
   marker_ = NULL;
 
+  int64_t mid1 = OS::GetCurrentMonotonicMicros();
+
   // Abandon the remainder of the bump allocation block.
   AbandonBumpAllocation();
   // Reset the freelists and setup sweeping.
@@ -1209,15 +1233,19 @@
     freelists_[i].Reset();
   }
 
-  if (FLAG_verify_before_gc) {
-    OS::PrintErr("Verifying before sweeping...");
-    heap_->VerifyGC(kAllowMarked);
-    OS::PrintErr(" done.\n");
-  }
+  int64_t mid2 = OS::GetCurrentMonotonicMicros();
+  int64_t mid3 = 0;
 
   {
+    if (FLAG_verify_before_gc) {
+      OS::PrintErr("Verifying before sweeping...");
+      heap_->VerifyGC(kAllowMarked);
+      OS::PrintErr(" done.\n");
+    }
+
     // Executable pages are always swept immediately to simplify
     // code protection.
+
     TIMELINE_FUNCTION_GC_DURATION(thread, "SweepExecutable");
     GCSweeper sweeper;
     OldPage* prev_page = NULL;
@@ -1235,6 +1263,8 @@
       // Advance to the next page.
       page = next_page;
     }
+
+    mid3 = OS::GetCurrentMonotonicMicros();
   }
 
   bool has_reservation = MarkReservation();
@@ -1285,6 +1315,13 @@
   page_space_controller_.EvaluateGarbageCollection(
       usage_before, GetCurrentUsage(), start, end);
 
+  heap_->RecordTime(kConcurrentSweep, pre_safe_point - pre_wait_for_sweepers);
+  heap_->RecordTime(kSafePoint, start - pre_safe_point);
+  heap_->RecordTime(kMarkObjects, mid1 - start);
+  heap_->RecordTime(kResetFreeLists, mid2 - mid1);
+  heap_->RecordTime(kSweepPages, mid3 - mid2);
+  heap_->RecordTime(kSweepLargePages, end - mid3);
+
   if (FLAG_print_free_list_after_gc) {
     for (intptr_t i = 0; i < num_freelists_; i++) {
       OS::PrintErr("After GC: Freelist %" Pd "\n", i);
@@ -1711,8 +1748,8 @@
   if (FLAG_log_growth || FLAG_verbose_gc) {
     THR_Print("%s: threshold=%" Pd "kB, idle_threshold=%" Pd "kB, reason=%s\n",
               heap_->isolate_group()->source()->name,
-              RoundWordsToKB(hard_gc_threshold_in_words_),
-              RoundWordsToKB(idle_gc_threshold_in_words_), reason);
+              hard_gc_threshold_in_words_ / KBInWords,
+              idle_gc_threshold_in_words_ / KBInWords, reason);
   }
 }
 
diff --git a/runtime/vm/heap/pages.h b/runtime/vm/heap/pages.h
index fca17a2..5a3c5c0 100644
--- a/runtime/vm/heap/pages.h
+++ b/runtime/vm/heap/pages.h
@@ -408,7 +408,7 @@
                        OldPage::PageType type) const;
 
   // Collect the garbage in the page space using mark-sweep or mark-compact.
-  void CollectGarbage(Thread* thread, bool compact, bool finalize);
+  void CollectGarbage(bool compact, bool finalize);
 
   void AddRegionsToObjectSet(ObjectSet* set) const;
 
@@ -573,7 +573,10 @@
   void FreeLargePage(OldPage* page, OldPage* previous_page);
   void FreePages(OldPage* pages);
 
-  void CollectGarbageHelper(Thread* thread, bool compact, bool finalize);
+  void CollectGarbageHelper(bool compact,
+                            bool finalize,
+                            int64_t pre_wait_for_sweepers,
+                            int64_t pre_safe_point);
   void SweepLarge();
   void Sweep(bool exclusive);
   void ConcurrentSweep(IsolateGroup* isolate_group);
diff --git a/runtime/vm/heap/pointer_block.cc b/runtime/vm/heap/pointer_block.cc
index 672bdc0..daedab8 100644
--- a/runtime/vm/heap/pointer_block.cc
+++ b/runtime/vm/heap/pointer_block.cc
@@ -228,11 +228,6 @@
   return (full_.length() + partial_.length()) > kMaxNonEmpty;
 }
 
-intptr_t StoreBuffer::Size() {
-  MonitorLocker ml(&monitor_);
-  return full_.length() + partial_.length();
-}
-
 void StoreBuffer::VisitObjectPointers(ObjectPointerVisitor* visitor) {
   for (Block* block = full_.Peek(); block != NULL; block = block->next()) {
     block->VisitObjectPointers(visitor);
diff --git a/runtime/vm/heap/pointer_block.h b/runtime/vm/heap/pointer_block.h
index 1b6fd5a..fb10fa6 100644
--- a/runtime/vm/heap/pointer_block.h
+++ b/runtime/vm/heap/pointer_block.h
@@ -268,7 +268,6 @@
   // Check whether non-empty blocks have exceeded kMaxNonEmpty (but takes no
   // action).
   bool Overflowed();
-  intptr_t Size();
 
   void VisitObjectPointers(ObjectPointerVisitor* visitor);
 };
diff --git a/runtime/vm/heap/scavenger.cc b/runtime/vm/heap/scavenger.cc
index c94c042..d2f7de4 100644
--- a/runtime/vm/heap/scavenger.cc
+++ b/runtime/vm/heap/scavenger.cc
@@ -1742,6 +1742,9 @@
   Thread* thread = Thread::Current();
   GcSafepointOperationScope safepoint_scope(thread);
 
+  int64_t safe_point = OS::GetCurrentMonotonicMicros();
+  heap_->RecordTime(kSafePoint, safe_point - start);
+
   // Scavenging is not reentrant. Make sure that is the case.
   ASSERT(!scavenging_);
   scavenging_ = true;
diff --git a/runtime/vm/heap/scavenger.h b/runtime/vm/heap/scavenger.h
index bfd8fc5..5a93aec 100644
--- a/runtime/vm/heap/scavenger.h
+++ b/runtime/vm/heap/scavenger.h
@@ -180,13 +180,6 @@
   bool Contains(uword addr) const;
   void WriteProtect(bool read_only);
 
-  intptr_t used_in_words() const {
-    intptr_t size = 0;
-    for (const NewPage* p = head_; p != nullptr; p = p->next()) {
-      size += (p->object_end() - p->object_start());
-    }
-    return size >> kWordSizeLog2;
-  }
   intptr_t capacity_in_words() const { return capacity_in_words_; }
   intptr_t max_capacity_in_words() const { return max_capacity_in_words_; }
 
@@ -291,7 +284,7 @@
 
   int64_t UsedInWords() const {
     MutexLocker ml(&space_lock_);
-    return to_->used_in_words();
+    return to_->capacity_in_words();
   }
   int64_t CapacityInWords() const { return to_->max_capacity_in_words(); }
   int64_t ExternalInWords() const { return external_size_ >> kWordSizeLog2; }
diff --git a/tools/VERSION b/tools/VERSION
index e3b18d0..32f8ad6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 22
+PRERELEASE 23
 PRERELEASE_PATCH 0
\ No newline at end of file