-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Migration without exact nullability makes a non-nullable list literal and assigns null #40590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Another example in package:async // without exact nullability. This will crash.
final Map<Stream<T>, StreamSubscription<T>?> _subscriptions = <Stream<T>, StreamSubscription<T>>{};
// with exact nullability. This is the ideal behavior.
final _subscriptions = <Stream<T>, StreamSubscription<T>?>{};
...
Future? add(Stream<T> stream) {
if (_closed) {
throw StateError("Can't add a Stream to a closed StreamGroup.");
}
if (_state == _StreamGroupState.dormant) {
// used nullably here
_subscriptions.putIfAbsent(stream, () => null);
}
... |
There's one example in package:async where exact nullability leads to a worse outcome that's not caused by #40551 StreamController<List<T?>?>? controller;
List<T?>? current;
var dataCount = 0;
/// Called for each data from a subscription in [subscriptions].
void handleData(int index, T data) {
current![index] = data;
dataCount++;
if (dataCount == subscriptions.length) {
var data = current;
current = List(subscriptions.length);
dataCount = 0;
for (var i = 0; i < subscriptions.length; i++) {
if (i != index) subscriptions[i].resume();
}
controller!.add(data);
} else {
subscriptions[index].pause();
}
}
...
// Here:
controller = StreamController<List<T>?>(onPause: () {
for (var i = 0; i < subscriptions.length; i++) {
// This may pause some subscriptions more than once.
// These will not be resumed by onResume below, but must wait for the
// next round.
subscriptions[i].pause();
}
}, onResume: () {
.. Without exact nullability, in In this case, it is possible that #40566 would fix it. It is also possible that the graph could not track this, since it depends upon that very first As it is, with exact nullability we get a worse migration, but it looks like a migration that will still work. |
In package:collection, we have three literals where they disagree at creation vs assignment: - Set<E?> set = SplayTreeSet<E>(comparison);
+ Set<E?> set = SplayTreeSet<E?>(comparison);
for (var i = 0; i < _length; i++) {
set.add(_queue[i]);
}
diff --git a/lib/src/union_set_controller.dart b/lib/src/union_set_controller.dart
index 4f7d6e1..c31d0c8 100644
--- a/lib/src/union_set_controller.dart
+++ b/lib/src/union_set_controller.dart
@@ -27,7 +27,7 @@ class UnionSetController<E> {
UnionSet<E>? _set;
/// The sets whose union is exposed through [set].
- final Set<Set<E>?> _sets = <Set<E>>{};
+ final _sets = <Set<E>?>{};
/// Creates a set of sets that provides a view of the union of those sets.
///
diff --git a/lib/src/wrappers.dart b/lib/src/wrappers.dart
index a95eefc..87b7f13 100644
--- a/lib/src/wrappers.dart
+++ b/lib/src/wrappers.dart
@@ -801,7 +801,7 @@ class MapValueSet<K, V> extends _DelegatingIterableBase<V> implements Set<V> {
@override
void retainAll(Iterable<Object?> elements) {
- Set<V?> valuesToRetain = Set<V>.identity();
+ var valuesToRetain = Set<V?>.identity(); In each case, it looks like it will actually be ok. These are all cases of where the algorithm did the wrong thing and so it is an improvement to not propagate that. However, the algorithm is defensible in each case: List baseList = List<E?>(10); // nullable because of initial capacity
...
Set<E?> set = SplayTreeSet<E>();
...
// baseList won't contain null here, but migrator thinks it may
set.add(baseList[x]); Set<Set<int>?> _sets;
...
foo(Set<int>? other) {
_sets.add(other);
}
...
// in tests
group("foo", () {
// nullable because not initialized
Set<int>? set;
init(() {
set = {};
});
...
test("bar", () {
// won't be null here
UnionSet().foo(set);
});
... Map _baseMap;
...
if (!baseMap.containsKey(k)) continue;
// At runtime, baseMap[k] will not return a nullable value. The tool things it may.
valuesToRetain.add(baseMap[k]); There are two narratives one can draw here:
Personally I'm in camp 2 here |
This assignment won't err at runtime. The type |
Add tests verifying #40590 so that we don't regress it. Change-Id: I74105cc1e6a33b75d7ff67dc07b7cc913040dc8b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/135840 Reviewed-by: Paul Berry <[email protected]> Commit-Queue: Mike Fairhurst <[email protected]>
As of 1c7fe71, the null safety migration tool has been removed from active development and retired. No further work on the tool is planned. If you still need help, or you believe this issue has been closed in error, please feel free to reopen. |
From package:async.
This is with exact nullability. Notice the two lines commented with
// here
Without exact nullability, this produces the same migration except
This is guaranteed to err at runtime. And even if it didn't, it would err when the null is added to
results
if the underlying store is indeedList<Result<T>>
.Notably, however, the null is only added temporarily. It looks like it's added essentially because the order of returned values is not guaranteed; if the second future returns a value, it must be inserted as the second value in the list. The null assignment is there to ensure that the list has two slots at that point, and fundamentally, the first slot may be empty.
The exact-nullable migration is therefore mostly correct, except that
completer.complete
should pass inresults.cast<T>
orList<T>.from(results)
.The migration could be refactored to avoid this, but ultimately, any refactoring will likely be a rewrite of the same pattern -- make a
List<T?>
, fill inT
s into the list, and then return a copy of the list or a view of it that has different static behavior. Whether that pattern is implemented as a linked list, sequence of awaits, new implementation of lists in dart:core, etc., the pattern will essentially be the same.The text was updated successfully, but these errors were encountered: