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)];