Use flattenedToList/flattenedToSet instead of generic expand().

It is faster.

[expand: 102]
[flattened: 131]
[flattened2: 37]

import 'package:collection/collection.dart';

main() {
  const outerLength = 100;
  const innerLength = 10;
  final listOfLists = List.generate(outerLength, (i) {
    return List.generate(innerLength, (j) => i * innerLength + j);
  });

  for (var i = 0; i < 10; i++) {
    f(listOfLists);
  }
}

void f(List<List<int>> listOfLists) {
  const repeatCount = 10000;

  {
    final timer = Stopwatch()..start();
    for (var i = 0; i < repeatCount; i++) {
      listOfLists.expand((e) => e).toList();
    }
    print('[expand: ${timer.elapsedMilliseconds}]');
  }

  {
    final timer = Stopwatch()..start();
    for (var i = 0; i < repeatCount; i++) {
      listOfLists.flattened.toList();
    }
    print('[flattened: ${timer.elapsedMilliseconds}]');
  }

  {
    final timer = Stopwatch()..start();
    for (var i = 0; i < repeatCount; i++) {
      listOfLists.flattened2.toList();
    }
    print('[flattened2: ${timer.elapsedMilliseconds}]');
  }
}

extension ListListExtensions<T> on List<List<T>> {
  Iterable<T> get flattened2 {
    return [
      for (final elements in this) ...elements,
    ];
  }
}

Change-Id: I0bf9dc0c8735fe62aab69cbce276254e2db110f9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345202
Reviewed-by: Jacob Richman <jacobr@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/code_actions/plugins.dart b/pkg/analysis_server/lib/src/lsp/handlers/code_actions/plugins.dart
index 3049602..ca3f9fd 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/code_actions/plugins.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/code_actions/plugins.dart
@@ -8,6 +8,7 @@
 import 'package:analysis_server/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart';
 import 'package:analysis_server/src/lsp/mapping.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
 import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
 import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' as plugin;
@@ -60,8 +61,7 @@
         .map((response) => plugin.EditGetFixesResult.fromResponse(response))
         .expand((response) => response.fixes)
         .map(_convertFixes)
-        .expand((fix) => fix)
-        .toList();
+        .flattenedToList;
   }
 
   @override
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart
index e2d78b4..e2f0acd 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart
@@ -13,6 +13,7 @@
 import 'package:analysis_server/src/lsp/semantic_tokens/encoder.dart';
 import 'package:analysis_server/src/lsp/semantic_tokens/legend.dart';
 import 'package:analyzer/source/source_range.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
 
 typedef StaticOptions
@@ -65,8 +66,7 @@
 
       return toSourceRangeNullable(lineInfo, range).mapResult((range) async {
         final serverTokens = await getServerResult(path, range);
-        final pluginHighlightRegions =
-            getPluginResults(path).expand((results) => results).toList();
+        final pluginHighlightRegions = getPluginResults(path).flattenedToList;
 
         if (token.isCancellationRequested) {
           return cancelled();
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index c6ff9e9..7467034 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -27,6 +27,7 @@
 import 'package:analyzer/src/dart/analysis/search.dart' as server
     show DeclarationKind;
 import 'package:analyzer/src/error/codes.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
 import 'package:collection/collection.dart';
 import 'package:path/path.dart' as path;
@@ -1165,8 +1166,7 @@
       .map((occurrence) => occurrence.offsets.map((offset) =>
           lsp.DocumentHighlight(
               range: toRange(lineInfo, offset, occurrence.length))))
-      .expand((occurrences) => occurrences)
-      .toSet()
+      .flattenedToSet
       .toList();
 }
 
diff --git a/pkg/analysis_server/lib/src/lsp/semantic_tokens/legend.dart b/pkg/analysis_server/lib/src/lsp/semantic_tokens/legend.dart
index a79a488..366015c 100644
--- a/pkg/analysis_server/lib/src/lsp/semantic_tokens/legend.dart
+++ b/pkg/analysis_server/lib/src/lsp/semantic_tokens/legend.dart
@@ -7,6 +7,7 @@
 import 'package:analysis_server/lsp_protocol/protocol.dart';
 import 'package:analysis_server/src/lsp/constants.dart';
 import 'package:analysis_server/src/lsp/semantic_tokens/mapping.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:meta/meta.dart';
 
 final semanticTokenLegend = SemanticTokenLegendLookup();
@@ -33,8 +34,8 @@
     _usedTokenTypes = Set.of(highlightRegionTokenTypes.values
             .followedBy(CustomSemanticTokenTypes.values))
         .toList();
-    _usedTokenModifiers = Set.of(highlightRegionTokenModifiers.values
-            .expand((v) => v)
+    _usedTokenModifiers = Set.of(highlightRegionTokenModifiers
+            .values.flattenedToList
             .followedBy(CustomSemanticTokenModifiers.values))
         .toList();
 
diff --git a/pkg/analysis_server/lib/src/operation/operation_analysis.dart b/pkg/analysis_server/lib/src/operation/operation_analysis.dart
index c81de49..10cffda 100644
--- a/pkg/analysis_server/lib/src/operation/operation_analysis.dart
+++ b/pkg/analysis_server/lib/src/operation/operation_analysis.dart
@@ -14,6 +14,7 @@
 import 'package:analyzer/exception/exception.dart';
 import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 
 Future<void> scheduleImplementedNotification(
     LegacyAnalysisServer server, Iterable<String> files) async {
@@ -42,8 +43,7 @@
   _sendNotification(server, () {
     var analyzedFiles = server.driverMap.values
         .map((driver) => driver.knownFiles)
-        .expand((files) => files)
-        .toSet();
+        .flattenedToSet;
 
     // Exclude *.yaml files because IDEA Dart plugin attempts to index
     // all the files in folders which contain analyzed files.
diff --git a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
index d910bcb..8dfa0f3 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix/data_driven/transform_set_parser.dart
@@ -22,6 +22,7 @@
 import 'package:analysis_server/src/services/refactoring/framework/formal_parameter.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/util/yaml.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:analyzer/src/utilities/extensions/string.dart';
 import 'package:collection/collection.dart';
 import 'package:yaml/yaml.dart';
@@ -350,9 +351,8 @@
     var firstEntry = entries.firstOrNull;
     if (firstEntry == null) {
       if (required) {
-        var validKeysList = translators.keys
-            .expand((keys) => keys)
-            .quotedAndCommaSeparatedWithOr;
+        var validKeysList =
+            translators.keys.flattenedToList.quotedAndCommaSeparatedWithOr;
         _reportError(TransformSetErrorCode.missingOneOfMultipleKeys, errorNode,
             [validKeysList]);
       }
diff --git a/pkg/analysis_server/lib/src/utilities/import_analyzer.dart b/pkg/analysis_server/lib/src/utilities/import_analyzer.dart
index 3e4e479..90ecb64 100644
--- a/pkg/analysis_server/lib/src/utilities/import_analyzer.dart
+++ b/pkg/analysis_server/lib/src/utilities/import_analyzer.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/source/source_range.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 
 /// A utility class used to analyze a library from which some set of
 /// declarations are being moved in order to compute the set of changes needed
@@ -298,8 +299,7 @@
     // Extensions can be used without a prefix, so we can use any import that
     // brings in the extension.
     if (import == null && prefix == null && element is ExtensionElement) {
-      import = _importsByPrefix.values
-          .expand((imports) => imports)
+      import = _importsByPrefix.values.flattenedToList
           .where((import) =>
               // Because we don't know what prefix we're looking for (any is
               // allowed), use the imports own prefix when checking for the
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index b65a669..d863465 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -51,6 +51,7 @@
 import 'package:analyzer/src/summary2/package_bundle_format.dart';
 import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer/src/util/performance/operation_performance.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:analyzer/src/utilities/uri_cache.dart';
 
 /// This class computes [AnalysisResult]s for Dart files.
@@ -2558,7 +2559,7 @@
       // Discover files in package/lib folders.
       var packageMap = driver._sourceFactory.packageMap;
       if (packageMap != null) {
-        folderIterator = packageMap.values.expand((f) => f).iterator;
+        folderIterator = packageMap.values.flattenedToList.iterator;
       } else {
         folderIterator = <Folder>[].iterator;
       }
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
index d74630d..c1113f8 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_graph.dart
@@ -149,7 +149,7 @@
                   .whereType<LibraryExportWithFile>()
                   .map((export) => export.exportedLibrary),
             ])
-        .expand((libraries) => libraries)
+        .flattenedToList
         .whereNotNull()
         .toSet();
 
diff --git a/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart b/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart
index a767bb3..8801ec5 100644
--- a/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart
+++ b/pkg/analyzer/lib/src/dart/element/generic_inferrer.dart
@@ -27,6 +27,7 @@
 import 'package:analyzer/src/dart/element/type_system.dart';
 import 'package:analyzer/src/error/codes.dart'
     show CompileTimeErrorCode, WarningCode;
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:meta/meta.dart';
 
 /// Tracks upper and lower type bounds for a set of type parameters.
@@ -491,7 +492,7 @@
         .values
         .where((l) =>
             l.every((c) => c.isSatisfiedBy(_typeSystem, inferred)) == expected)
-        .expand((i) => i);
+        .flattenedToList;
 
     String unsatisfied = _formatConstraints(isSatisfied(false));
     String satisfied = _formatConstraints(isSatisfied(true));
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart
index fb28e84..d4c6a34 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -68,6 +68,7 @@
 import 'package:analyzer/src/fasta/error_converter.dart';
 import 'package:analyzer/src/generated/utilities_dart.dart';
 import 'package:analyzer/src/summary2/ast_binary_tokens.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:collection/collection.dart';
 import 'package:meta/meta.dart';
 import 'package:pub_semver/pub_semver.dart';
@@ -3071,7 +3072,7 @@
     debugEvent("SwitchBlock");
 
     var membersList = popTypedList2<List<SwitchMemberImpl>>(caseCount);
-    var members = membersList.expand((members) => members).toList();
+    var members = membersList.flattenedToList;
 
     Set<String> labels = <String>{};
     for (var member in members) {
diff --git a/pkg/analyzer/lib/src/summary2/library_builder.dart b/pkg/analyzer/lib/src/summary2/library_builder.dart
index c7559b4..61d5563 100644
--- a/pkg/analyzer/lib/src/summary2/library_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/library_builder.dart
@@ -460,7 +460,7 @@
     }
 
     final augmentationCode = macroApplier.buildAugmentationLibraryCode(
-      _macroResults.expand((e) => e).toList(),
+      _macroResults.flattenedToList,
     );
     if (augmentationCode == null) {
       return;
diff --git a/pkg/analyzer/lib/src/utilities/extensions/collection.dart b/pkg/analyzer/lib/src/utilities/extensions/collection.dart
index 14362d6..0ba8e0c 100644
--- a/pkg/analyzer/lib/src/utilities/extensions/collection.dart
+++ b/pkg/analyzer/lib/src/utilities/extensions/collection.dart
@@ -19,6 +19,26 @@
   }
 }
 
+extension IterableIterableExtension<T> on Iterable<Iterable<T>> {
+  /// Elements of each iterable in this iterable.
+  ///
+  /// At the moment of writing, this method is `2.75` times faster than
+  /// `expand((e) => e)`, and `3.5` faster than `flattened` from
+  /// `package:collection`.
+  List<T> get flattenedToList {
+    return [
+      for (final elements in this) ...elements,
+    ];
+  }
+
+  /// Elements of each iterable in this iterable.
+  Set<T> get flattenedToSet {
+    return {
+      for (final elements in this) ...elements,
+    };
+  }
+}
+
 extension IterableMapEntryExtension<K, V> on Iterable<MapEntry<K, V>> {
   Map<K, V> get mapFromEntries => Map.fromEntries(this);
 }
diff --git a/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart b/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart
index 35953aa..d6f7cc4 100644
--- a/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart
+++ b/pkg/analyzer/test/src/dart/element/inheritance_manager3_test.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:analyzer/src/dart/element/extensions.dart';
 import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
+import 'package:analyzer/src/utilities/extensions/collection.dart';
 import 'package:collection/collection.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -3022,7 +3023,7 @@
     String name,
     Map<Name, List<ExecutableElement>> map,
   ) {
-    final isEmpty = map.values.expand((elements) => elements).where((element) {
+    final isEmpty = map.values.flattenedToList.where((element) {
       if (_configuration.withObjectMembers) return true;
       return !element.isObjectMember;
     }).isEmpty;
diff --git a/pkg/analyzer/test/src/utilities/extensions/collection_test.dart b/pkg/analyzer/test/src/utilities/extensions/collection_test.dart
index b8b8520..9d824a3 100644
--- a/pkg/analyzer/test/src/utilities/extensions/collection_test.dart
+++ b/pkg/analyzer/test/src/utilities/extensions/collection_test.dart
@@ -9,6 +9,7 @@
 main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(IterableExtensionTest);
+    defineReflectiveTests(IterableIterableExtensionTest);
     defineReflectiveTests(IterableMapEntryExtensionTest);
     defineReflectiveTests(ListExtensionTest);
   });
@@ -22,6 +23,31 @@
 }
 
 @reflectiveTest
+class IterableIterableExtensionTest {
+  test_flattenedToList() {
+    expect(
+      [
+        [0],
+        [1, 2],
+        [3, 3]
+      ].flattenedToList,
+      [0, 1, 2, 3, 3],
+    );
+  }
+
+  test_flattenedToSet() {
+    expect(
+      [
+        [0, 0],
+        [1, 2, 1],
+        [3, 3]
+      ].flattenedToSet,
+      {0, 1, 2, 3},
+    );
+  }
+}
+
+@reflectiveTest
 class IterableMapEntryExtensionTest {
   test_mapFromEntries() {
     final entries = [MapEntry('foo', 0), MapEntry('bar', 1)];