Skip to content

Commit 28c84f8

Browse files
committed
Merge remote-tracking branch 'upstream/main' into length_ignore_ansi
# Conflicts: # pkgs/args/CHANGELOG.md
2 parents 17748b7 + 1ce6453 commit 28c84f8

10 files changed

Lines changed: 273 additions & 77 deletions

File tree

.github/workflows/lints_version_bump.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
check-major-version:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v4
13+
- uses: actions/checkout@v6
1414

1515
- name: Verify Major first in major version is X.0.0 or X.0.0-wip
1616
if: "!contains(github.event.pull_request.labels.*.name, 'skip-breaking-check')"

.github/workflows/no-response.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
runs-on: ubuntu-latest
2020
if: ${{ github.repository_owner == 'dart-lang' }}
2121
steps:
22-
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d
22+
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f
2323
with:
2424
# Don't automatically mark inactive issues+PRs as stale.
2525
days-before-stale: -1

pkgs/args/CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
(Fixes #103).
1515
* Remove sorting of the subcommands in usage output. Ordering will depend on the
1616
order that `addSubCommand` is called.
17-
18-
## 2.7.0
17+
1918

2019
* Remove sorting of the `allowedHelp` argument in usage output. Ordering will
2120
depend on key order for the passed `Map`.

pkgs/collection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Optimize equality and hash code for maps by using `update` and a `values`
1010
iterator to avoid extra lookups.
1111
- Add `PriorityQueue.of` constructor and optimize adding many elements.
12+
- Add `descending` argument to `sortedBy`.
1213
- Address diagnostics from `strict_top_level_inference`.
1314
- Run `dart format` with the new style.
1415
- Replace `quickSort` implementation with a more performant and robust

pkgs/collection/lib/src/iterable_extensions.dart

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import 'dart:math' show Random;
77
import '../collection.dart' show DelegatingIterable;
88
import 'algorithms.dart';
99
import 'functions.dart' as functions;
10-
import 'utils.dart';
1110

1211
/// Extensions that apply to all iterables.
1312
///
@@ -165,10 +164,17 @@ extension IterableExtension<T> on Iterable<T> {
165164
/// Creates a sorted list of the elements of the iterable.
166165
///
167166
/// The elements are ordered by the natural ordering of the
168-
/// property [keyOf] of the element.
169-
List<T> sortedBy<K extends Comparable<K>>(K Function(T element) keyOf) {
167+
/// [keyOf] property when [descending] is `false` (the default) or reverse
168+
/// ordering when [descending] is `true`.
169+
List<T> sortedBy<K extends Comparable<K>>(
170+
K Function(T element) keyOf, {
171+
bool descending = false,
172+
}) {
173+
final compare = descending
174+
? (K a, K b) => b.compareTo(a)
175+
: (K a, K b) => a.compareTo(b);
170176
var elements = [...this];
171-
mergeSortBy<T, K>(elements, keyOf, compareComparable);
177+
mergeSortBy<T, K>(elements, keyOf, compare);
172178
return elements;
173179
}
174180

pkgs/collection/test/extensions_test.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ void main() {
7070
test('multiple', () {
7171
expect(iterable(<int>[3, 20, 100]).sortedBy(toString), [100, 20, 3]);
7272
});
73+
test('multiple descending: false', () {
74+
expect(
75+
iterable(<int>[3, 20, 100]).sortedBy(toString, descending: false),
76+
[100, 20, 3]);
77+
});
78+
test('multiple descending: true', () {
79+
expect(
80+
iterable(<int>[3, 20, 100]).sortedBy(toString, descending: true),
81+
[3, 20, 100]);
82+
});
7383
});
7484
group('.sortedByCompare', () {
7585
test('empty', () {

pkgs/path/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
in/as the last segment when splitting.
99
- Run `dart format` with the new style.
1010
- Centralize join logic in `Context` and eliminate redundant validation in `absolute()`.
11+
- Avoid list allocation in `join()` and `absolute()` argument validation for improved performance.
1112

1213
## 1.9.1
1314

pkgs/path/benchmark/benchmark.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ void main(List<String> args) {
5858
}
5959

6060
benchmark('absolute', context.absolute);
61+
runBenchmark('${style.name}-join', 100000, () {
62+
for (var file in files) {
63+
context.join(file, 'subdir', 'file.txt');
64+
}
65+
});
66+
runBenchmark('${style.name}-join-many', 100000, () {
67+
for (var file in files) {
68+
context.join(file, 'a', 'b', 'c', 'd', 'e', 'f');
69+
}
70+
});
6171
benchmark('basename', context.basename);
6272
benchmark('basenameWithoutExtension', context.basenameWithoutExtension);
6373
benchmark('dirname', context.dirname);

pkgs/path/lib/src/context.dart

Lines changed: 211 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ class Context {
114114
return part1;
115115
}
116116

117-
return _join('absolute', [
117+
final args = _validateArgs(
118+
'absolute',
118119
current,
119120
part1,
120121
part2,
@@ -131,7 +132,8 @@ class Context {
131132
part13,
132133
part14,
133134
part15,
134-
]);
135+
);
136+
return joinAll(args);
135137
}
136138

137139
/// Gets the part of [path] after the last separator on the context's
@@ -279,30 +281,27 @@ class Context {
279281
String? part14,
280282
String? part15,
281283
String? part16,
282-
]) =>
283-
_join('join', [
284-
part1,
285-
part2,
286-
part3,
287-
part4,
288-
part5,
289-
part6,
290-
part7,
291-
part8,
292-
part9,
293-
part10,
294-
part11,
295-
part12,
296-
part13,
297-
part14,
298-
part15,
299-
part16,
300-
]);
301-
302-
/// Joins the given path parts into a single path.
303-
String _join(String method, List<String?> parts) {
304-
_validateArgList(method, parts);
305-
return joinAll(parts.whereType<String>());
284+
]) {
285+
final args = _validateArgs(
286+
'join',
287+
part1,
288+
part2,
289+
part3,
290+
part4,
291+
part5,
292+
part6,
293+
part7,
294+
part8,
295+
part9,
296+
part10,
297+
part11,
298+
part12,
299+
part13,
300+
part14,
301+
part15,
302+
part16,
303+
);
304+
return joinAll(args);
306305
}
307306

308307
/// Joins the given path parts into a single path. Example:
@@ -1165,28 +1164,194 @@ Uri _parseUri(Object uri) {
11651164

11661165
/// Validates that there are no non-null arguments following a null one and
11671166
/// throws an appropriate [ArgumentError] on failure.
1168-
void _validateArgList(String method, List<String?> args) {
1169-
for (var i = 1; i < args.length; i++) {
1170-
// Ignore nulls hanging off the end.
1171-
if (args[i] == null || args[i - 1] != null) continue;
1172-
1173-
int numArgs;
1174-
for (numArgs = args.length; numArgs >= 1; numArgs--) {
1175-
if (args[numArgs - 1] != null) break;
1176-
}
1167+
///
1168+
/// This function operates on individual arguments rather than a list for better
1169+
/// performance by avoiding list allocation.
1170+
///
1171+
/// Returns a list of only the non-null arguments.
1172+
List<String> _validateArgs(
1173+
String method,
1174+
String p0,
1175+
String? p1,
1176+
String? p2,
1177+
String? p3,
1178+
String? p4,
1179+
String? p5,
1180+
String? p6,
1181+
String? p7,
1182+
String? p8,
1183+
String? p9,
1184+
String? p10,
1185+
String? p11,
1186+
String? p12,
1187+
String? p13,
1188+
String? p14,
1189+
String? p15,
1190+
) {
1191+
// Helper to create the list of args for error messages.
1192+
List<String?> allArgs() => [
1193+
p0,
1194+
p1,
1195+
p2,
1196+
p3,
1197+
p4,
1198+
p5,
1199+
p6,
1200+
p7,
1201+
p8,
1202+
p9,
1203+
p10,
1204+
p11,
1205+
p12,
1206+
p13,
1207+
p14,
1208+
p15,
1209+
];
1210+
1211+
// p0 is always non-null (enforced by caller), so we start checking from p1.
1212+
// Once we see a null, all subsequent arguments must also be null.
1213+
var seenNull = false;
1214+
final args = <String>[p0];
1215+
1216+
if (p1 == null) {
1217+
seenNull = true;
1218+
} else {
1219+
args.add(p1);
1220+
}
11771221

1178-
// Show the arguments.
1179-
final message = StringBuffer();
1180-
message.write('$method(');
1181-
message.write(
1182-
args
1183-
.take(numArgs)
1184-
.map((arg) => arg == null ? 'null' : '"$arg"')
1185-
.join(', '),
1186-
);
1187-
message.write('): part ${i - 1} was null, but part $i was not.');
1188-
throw ArgumentError(message.toString());
1222+
if (p2 == null) {
1223+
seenNull = true;
1224+
} else if (seenNull) {
1225+
_throwArgError(method, 2, allArgs());
1226+
} else {
1227+
args.add(p2);
1228+
}
1229+
1230+
if (p3 == null) {
1231+
seenNull = true;
1232+
} else if (seenNull) {
1233+
_throwArgError(method, 3, allArgs());
1234+
} else {
1235+
args.add(p3);
11891236
}
1237+
1238+
if (p4 == null) {
1239+
seenNull = true;
1240+
} else if (seenNull) {
1241+
_throwArgError(method, 4, allArgs());
1242+
} else {
1243+
args.add(p4);
1244+
}
1245+
1246+
if (p5 == null) {
1247+
seenNull = true;
1248+
} else if (seenNull) {
1249+
_throwArgError(method, 5, allArgs());
1250+
} else {
1251+
args.add(p5);
1252+
}
1253+
1254+
if (p6 == null) {
1255+
seenNull = true;
1256+
} else if (seenNull) {
1257+
_throwArgError(method, 6, allArgs());
1258+
} else {
1259+
args.add(p6);
1260+
}
1261+
1262+
if (p7 == null) {
1263+
seenNull = true;
1264+
} else if (seenNull) {
1265+
_throwArgError(method, 7, allArgs());
1266+
} else {
1267+
args.add(p7);
1268+
}
1269+
1270+
if (p8 == null) {
1271+
seenNull = true;
1272+
} else if (seenNull) {
1273+
_throwArgError(method, 8, allArgs());
1274+
} else {
1275+
args.add(p8);
1276+
}
1277+
1278+
if (p9 == null) {
1279+
seenNull = true;
1280+
} else if (seenNull) {
1281+
_throwArgError(method, 9, allArgs());
1282+
} else {
1283+
args.add(p9);
1284+
}
1285+
1286+
if (p10 == null) {
1287+
seenNull = true;
1288+
} else if (seenNull) {
1289+
_throwArgError(method, 10, allArgs());
1290+
} else {
1291+
args.add(p10);
1292+
}
1293+
1294+
if (p11 == null) {
1295+
seenNull = true;
1296+
} else if (seenNull) {
1297+
_throwArgError(method, 11, allArgs());
1298+
} else {
1299+
args.add(p11);
1300+
}
1301+
1302+
if (p12 == null) {
1303+
seenNull = true;
1304+
} else if (seenNull) {
1305+
_throwArgError(method, 12, allArgs());
1306+
} else {
1307+
args.add(p12);
1308+
}
1309+
1310+
if (p13 == null) {
1311+
seenNull = true;
1312+
} else if (seenNull) {
1313+
_throwArgError(method, 13, allArgs());
1314+
} else {
1315+
args.add(p13);
1316+
}
1317+
1318+
if (p14 == null) {
1319+
seenNull = true;
1320+
} else if (seenNull) {
1321+
_throwArgError(method, 14, allArgs());
1322+
} else {
1323+
args.add(p14);
1324+
}
1325+
1326+
if (p15 == null) {
1327+
// No more arguments to check after p15, so this is valid.
1328+
} else if (seenNull) {
1329+
_throwArgError(method, 15, allArgs());
1330+
} else {
1331+
args.add(p15);
1332+
}
1333+
1334+
return args;
1335+
}
1336+
1337+
/// Throws an [ArgumentError] for invalid argument ordering.
1338+
///
1339+
/// [nonNullIndex] is the index of the non-null argument that follows a null.
1340+
Never _throwArgError(String method, int nonNullIndex, List<String?> args) {
1341+
int numArgs;
1342+
for (numArgs = args.length; numArgs >= 1; numArgs--) {
1343+
if (args[numArgs - 1] != null) break;
1344+
}
1345+
1346+
final message = StringBuffer();
1347+
message.write('$method(');
1348+
message.write(
1349+
args.take(numArgs).map((arg) => arg == null ? 'null' : '"$arg"').join(', '),
1350+
);
1351+
message.write(
1352+
'): part ${nonNullIndex - 1} was null, but part $nonNullIndex was not.',
1353+
);
1354+
throw ArgumentError(message.toString());
11901355
}
11911356

11921357
/// An enum of possible return values for [Context._pathDirection].

0 commit comments

Comments
 (0)