Skip to content

Commit 858791d

Browse files
committed
Restore null checks in legacy mode.
Do explicit null checks when sound mode is off and the type is not nullable.
1 parent 16933e5 commit 858791d

19 files changed

+295
-56
lines changed

CHANGELOG.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
## 5.1.0 (unreleased)
44

5-
- Remove explicit runtime null checks and type checks. These are now provided
6-
by the underlying collections as applicable. When used with null safety,
7-
this means you can for example create a `BuiltList<T>` which cannot contain
8-
nulls, or a `BuiltList<T?>` which can contain nulls. Note that legacy code
9-
and mixed mode no longer do runtime null checks.
5+
- Allow collections with nullable types, for example `BuiltList<T?>`.
106
- Allow key/value types to be `dynamic`. This can be useful occasionally, and
117
with Dart 2 and null safety it's much harder to use `dynamic` by accident.
128

lib/src/internal/null_safety.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/// Whether runtime sound mode is enabled.
2+
final bool isSoundMode = <int?>[] is! List<int>;

lib/src/list.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import 'package:built_collection/src/set.dart' show BuiltSet;
99

1010
import 'internal/copy_on_write_list.dart';
1111
import 'internal/hash.dart';
12+
import 'internal/iterables.dart';
13+
import 'internal/null_safety.dart';
1214

1315
part 'list/built_list.dart';
1416
part 'list/list_builder.dart';
@@ -18,7 +20,7 @@ class OverriddenHashcodeBuiltList<T> extends _BuiltList<T> {
1820
final int _overridenHashCode;
1921

2022
OverriddenHashcodeBuiltList(Iterable iterable, this._overridenHashCode)
21-
: super.copy(iterable);
23+
: super.from(iterable);
2224

2325
@override
2426
// ignore: hash_and_equals

lib/src/list/built_list.dart

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,18 @@ abstract class BuiltList<E> implements Iterable<E>, BuiltIterable<E> {
2626
if (iterable is _BuiltList && iterable.hasExactElementType(E)) {
2727
return iterable as BuiltList<E>;
2828
} else {
29-
return _BuiltList<E>.copy(iterable);
29+
return _BuiltList<E>.from(iterable);
3030
}
3131
}
3232

3333
/// Instantiates with elements from an [Iterable<E>].
34+
///
35+
/// `E` must not be `dynamic`.
3436
factory BuiltList.of(Iterable<E> iterable) {
3537
if (iterable is _BuiltList<E> && iterable.hasExactElementType(E)) {
3638
return iterable;
3739
} else {
38-
return _BuiltList<E>.copy(iterable);
40+
return _BuiltList<E>.of(iterable);
3941
}
4042
}
4143

@@ -243,8 +245,26 @@ abstract class BuiltList<E> implements Iterable<E>, BuiltIterable<E> {
243245
class _BuiltList<E> extends BuiltList<E> {
244246
_BuiltList.withSafeList(List<E> list) : super._(list);
245247

246-
_BuiltList.copy([Iterable iterable = const []])
247-
: super._(List<E>.from(iterable, growable: false));
248+
_BuiltList.from([Iterable iterable = const []])
249+
: super._(List<E>.from(iterable, growable: false)) {
250+
_maybeCheckForNull();
251+
}
252+
253+
_BuiltList.of(Iterable<E> iterable)
254+
: super._(List<E>.from(iterable, growable: false)) {
255+
_maybeCheckForNull();
256+
}
257+
258+
bool get _needsNullCheck => !isSoundMode && null is! E;
259+
260+
void _maybeCheckForNull() {
261+
if (!_needsNullCheck) return;
262+
for (var element in _list) {
263+
if (identical(element, null)) {
264+
throw ArgumentError('iterable contained invalid element: null');
265+
}
266+
}
267+
}
248268

249269
bool hasExactElementType(Type type) => E == type;
250270
}
@@ -254,7 +274,7 @@ extension BuiltListExtension<T> on List<T> {
254274
/// Converts to a [BuiltList].
255275
BuiltList<T> build() {
256276
// We know a `List` is not a `BuiltList`, so we have to copy.
257-
return _BuiltList<T>.copy(this);
277+
return _BuiltList<T>.of(this);
258278
}
259279
}
260280

lib/src/list/list_builder.dart

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class ListBuilder<E> {
5252

5353
/// As [List].
5454
void operator []=(int index, E element) {
55+
_maybeCheckElement(element);
5556
_safeList[index] = element;
5657
}
5758

@@ -60,6 +61,7 @@ class ListBuilder<E> {
6061

6162
/// As [List.first].
6263
set first(E value) {
64+
_maybeCheckElement(value);
6365
_safeList.first = value;
6466
}
6567

@@ -68,6 +70,7 @@ class ListBuilder<E> {
6870

6971
/// As [List.last].
7072
set last(E value) {
73+
_maybeCheckElement(value);
7174
_safeList.last = value;
7275
}
7376

@@ -82,12 +85,26 @@ class ListBuilder<E> {
8285

8386
/// As [List.add].
8487
void add(E value) {
88+
_maybeCheckElement(value);
8589
_safeList.add(value);
8690
}
8791

8892
/// As [List.addAll].
8993
void addAll(Iterable<E> iterable) {
90-
_safeList.addAll(iterable);
94+
// Add directly to the underlying `List` then check elements there, for
95+
// performance. Roll back the changes if validation fails.
96+
var safeList = _safeList;
97+
var lengthBefore = safeList.length;
98+
safeList.addAll(iterable);
99+
if (!_needsNullCheck) return;
100+
try {
101+
for (var i = lengthBefore; i != safeList.length; ++i) {
102+
_checkElement(safeList[i]);
103+
}
104+
} catch (_) {
105+
safeList.removeRange(lengthBefore, safeList.length);
106+
rethrow;
107+
}
91108
}
92109

93110
/// As [List.reversed], but updates the builder in place. Returns nothing.
@@ -113,16 +130,33 @@ class ListBuilder<E> {
113130

114131
/// As [List.insert].
115132
void insert(int index, E element) {
133+
_maybeCheckElement(element);
116134
_safeList.insert(index, element);
117135
}
118136

119137
/// As [List.insertAll].
120138
void insertAll(int index, Iterable<E> iterable) {
121-
_safeList.insertAll(index, iterable);
139+
// Add directly to the underlying `List` then check elements there, for
140+
// performance. Roll back the changes if validation fails.
141+
var safeList = _safeList;
142+
var lengthBefore = safeList.length;
143+
safeList.insertAll(index, iterable);
144+
if (!_needsNullCheck) return;
145+
var insertedLength = safeList.length - lengthBefore;
146+
try {
147+
for (var i = index; i != index + insertedLength; ++i) {
148+
_checkElement(safeList[i]);
149+
}
150+
} catch (_) {
151+
safeList.removeRange(index, index + insertedLength);
152+
rethrow;
153+
}
122154
}
123155

124156
/// As [List.setAll].
125157
void setAll(int index, Iterable<E> iterable) {
158+
iterable = evaluateIterable(iterable);
159+
_maybeCheckElements(iterable);
126160
_safeList.setAll(index, iterable);
127161
}
128162

@@ -154,6 +188,8 @@ class ListBuilder<E> {
154188

155189
/// As [List.setRange].
156190
void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
191+
iterable = evaluateIterable(iterable);
192+
_maybeCheckElements(iterable);
157193
_safeList.setRange(start, end, iterable, skipCount);
158194
}
159195

@@ -164,19 +200,24 @@ class ListBuilder<E> {
164200

165201
/// As [List.fillRange], but requires a value.
166202
void fillRange(int start, int end, E fillValue) {
203+
_maybeCheckElement(fillValue);
167204
_safeList.fillRange(start, end, fillValue);
168205
}
169206

170207
/// As [List.replaceRange].
171208
void replaceRange(int start, int end, Iterable<E> iterable) {
209+
iterable = evaluateIterable(iterable);
210+
_maybeCheckElements(iterable);
172211
_safeList.replaceRange(start, end, iterable);
173212
}
174213

175214
// Based on Iterable.
176215

177216
/// As [Iterable.map], but updates the builder in place. Returns nothing.
178217
void map(E Function(E) f) {
179-
_setSafeList(_list.map(f).toList(growable: true));
218+
var result = _list.map(f).toList(growable: true);
219+
_maybeCheckElements(result);
220+
_setSafeList(result);
180221
}
181222

182223
/// As [Iterable.where], but updates the builder in place. Returns nothing.
@@ -186,7 +227,9 @@ class ListBuilder<E> {
186227

187228
/// As [Iterable.expand], but updates the builder in place. Returns nothing.
188229
void expand(Iterable<E> Function(E) f) {
189-
_setSafeList(_list.expand(f).toList(growable: true));
230+
var result = _list.expand(f).toList(growable: true);
231+
_maybeCheckElements(result);
232+
_setSafeList(result);
190233
}
191234

192235
/// As [Iterable.take], but updates the builder in place. Returns nothing.
@@ -231,4 +274,23 @@ class ListBuilder<E> {
231274
}
232275
return _list;
233276
}
277+
278+
bool get _needsNullCheck => !isSoundMode && null is! E;
279+
280+
void _maybeCheckElement(E element) {
281+
if (_needsNullCheck) _checkElement(element);
282+
}
283+
284+
void _checkElement(E element) {
285+
if (identical(element, null)) {
286+
throw ArgumentError('null element');
287+
}
288+
}
289+
290+
void _maybeCheckElements(Iterable<E> elements) {
291+
if (!_needsNullCheck) return;
292+
for (var element in elements) {
293+
_checkElement(element);
294+
}
295+
}
234296
}

lib/src/list_multimap.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'internal/copy_on_write_map.dart';
66

77
import 'internal/hash.dart';
8+
import 'internal/null_safety.dart';
89
import 'list.dart';
910

1011
part 'list_multimap/built_list_multimap.dart';

lib/src/list_multimap/built_list_multimap.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ abstract class BuiltListMultimap<K, V> {
3232
multimap.hasExactKeyAndValueTypes(K, V)) {
3333
return multimap as BuiltListMultimap<K, V>;
3434
} else if (multimap is Map) {
35-
return _BuiltListMultimap<K, V>.copy(multimap.keys, (k) => multimap[k]);
35+
return _BuiltListMultimap<K, V>.copy(
36+
multimap.keys, (k) => multimap[k]);
3637
} else if (multimap is BuiltListMultimap) {
37-
return _BuiltListMultimap<K, V>.copy(multimap.keys, (k) => multimap[k]);
38+
return _BuiltListMultimap<K, V>.copy(
39+
multimap.keys, (k) => multimap[k]);
3840
} else {
39-
return _BuiltListMultimap<K, V>.copy(multimap.keys, (k) => multimap[k]);
41+
return _BuiltListMultimap<K, V>.copy(
42+
multimap.keys, (k) => multimap[k]);
4043
}
4144
}
4245

lib/src/list_multimap/list_multimap_builder.dart

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@ class ListMultimapBuilder<K, V> {
2323

2424
/// Instantiates with elements from a [Map], [ListMultimap] or
2525
/// [BuiltListMultimap].
26-
///
27-
/// Must be called with a generic type parameter.
28-
///
29-
/// Wrong:
30-
/// `new ListMultimapBuilder({1: ['1'], 2: ['2'], 3: ['3']})`.
31-
///
32-
/// Right:
33-
/// `new ListMultimapBuilder<int, String>({1: ['1'], 2: ['2'], 3: ['3']})`,
3426
factory ListMultimapBuilder([multimap = const {}]) {
3527
return ListMultimapBuilder<K, V>._uninitialized()..replace(multimap);
3628
}
@@ -111,6 +103,8 @@ class ListMultimapBuilder<K, V> {
111103
/// As [ListMultimap.add].
112104
void add(K key, V value) {
113105
_makeWriteableCopy();
106+
_checkKey(key);
107+
_checkValue(value);
114108
_getValuesBuilder(key).add(value);
115109
}
116110

@@ -210,4 +204,20 @@ class ListMultimapBuilder<K, V> {
210204
}
211205
}
212206
}
207+
208+
void _checkKey(K key) {
209+
if (isSoundMode) return;
210+
if (null is K) return;
211+
if (identical(key, null)) {
212+
throw ArgumentError('null key');
213+
}
214+
}
215+
216+
void _checkValue(V value) {
217+
if (isSoundMode) return;
218+
if (null is V) return;
219+
if (identical(value, null)) {
220+
throw ArgumentError('null value');
221+
}
222+
}
213223
}

lib/src/map.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'internal/copy_on_write_map.dart';
66
import 'internal/hash.dart';
7+
import 'internal/null_safety.dart';
78

89
part 'map/built_map.dart';
910
part 'map/map_builder.dart';

lib/src/map/built_map.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ abstract class BuiltMap<K, V> {
4444
///
4545
/// `K` and `V` are inferred from `map`.
4646
factory BuiltMap.of(Map<K, V> map) {
47-
return _BuiltMap<K, V>.copy(map.keys, (k) => map[k] as V);
47+
return _BuiltMap<K, V>.copyAndCheckForNull(map.keys, (k) => map[k] as V);
4848
}
4949

5050
/// Creates a [MapBuilder], applies updates to it, and builds.
@@ -180,10 +180,18 @@ class _BuiltMap<K, V> extends BuiltMap<K, V> {
180180
}
181181
}
182182

183-
_BuiltMap.copy(Iterable<K> keys, V Function(K) lookup)
183+
_BuiltMap.copyAndCheckForNull(Iterable<K> keys, V Function(K) lookup)
184184
: super._(null, <K, V>{}) {
185+
var checkKeys = !isSoundMode && null is! K;
186+
var checkValues = !isSoundMode && null is! V;
185187
for (var key in keys) {
188+
if (checkKeys && identical(key, null)) {
189+
throw ArgumentError('map contained invalid key: null');
190+
}
186191
var value = lookup(key);
192+
if (checkValues && value == null) {
193+
throw ArgumentError('map contained invalid value: null');
194+
}
187195
_map[key] = value;
188196
}
189197
}

0 commit comments

Comments
 (0)