-
Notifications
You must be signed in to change notification settings - Fork 6k
apply null safety syntax to mobile dart:ui #18933
Conversation
lib/ui/channel_buffers.dart
Outdated
@@ -71,7 +72,7 @@ class _RingBuffer<T> { | |||
while (_queue.length > lengthLimit) { | |||
final T item = _queue.removeFirst(); | |||
if (_dropItemCallback != null) { | |||
_dropItemCallback(item); | |||
_dropItemCallback!(item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be written as _dropItemCallback?.call(item) instead if desired. Not sure which is clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat! Done.
5b7fe68
to
c203f55
Compare
c203f55
to
b2a9c70
Compare
b2a9c70
to
131668d
Compare
@@ -60,7 +61,7 @@ class _RingBuffer<T> { | |||
} | |||
|
|||
/// Returns null when empty. | |||
T pop() { | |||
T? pop() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if T
is already nullable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC T
is potentially non-nullable, so it could be int?
. The type system will collapse muliplte ??
to ?
, so essentially this forces nullability even a concrete instance of T
is non-nullable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
☝️ What Jonah says. But also, I'm not too worried about what happens inside private classes, as long as there are not breakages or performance regressions. We can revisit specific cases later, when everything uses the same syntax.
@@ -116,8 +115,8 @@ class ChannelBuffers { | |||
static const String kControlChannelName = 'dev.flutter/channel-buffers'; | |||
|
|||
/// A mapping between a channel name and its associated [_RingBuffer]. | |||
final Map<String, _RingBuffer<_StoredMessage>> _messages = | |||
<String, _RingBuffer<_StoredMessage>>{}; | |||
final Map<String, _RingBuffer<_StoredMessage>?> _messages = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this Map
have a String
explicitly mapped to null
? How is that distinguished from the Map
not containing null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maps are tricky in that even if you say the values are non-null operator[]
will still return nullable value. I can try forcing non-null but I also think internal stuff like this can be cleaned up gradually in follow-up PRs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To answer the question: Yes, it can have null
as a value. It's distinguished from not having an entry by the key
being there in keys
or containsKey
, and the null
value being in values
.
Whether you want or need that is up to you.
@@ -620,11 +617,11 @@ class SceneBuilder extends NativeFieldWrapperClass2 { | |||
_debugCheckUsedOnce(parentLayer, 'retained layer'); | |||
parentLayer._debugCheckNotUsedAsOldLayer(); | |||
|
|||
if (parentLayer._debugChildren == null || parentLayer._debugChildren.isEmpty) { | |||
final List<_EngineLayerWrapper>? children = parentLayer._debugChildren; | |||
if (children == null || children.isEmpty) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be children!.isEmpty
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because the children == null ||
promotes the variable to non-null automatically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the analyzer or front-end tell you when you are missing !
s? I would hate to learn that a !
is missing at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both analyzer and CFE will not allow you to call a method on something which can be null except in the following cases[1]:
- The variable has type
dynamic
- The method is one of the Object members (in which case the call succeeds).
So if you are missing a !
you'll get told, modulo the two exceptions above, and the caveat below.
[1] when running a mixture of migrated and unmigrated code, all bets are off, of course.
return; | ||
} | ||
|
||
parentLayer._debugChildren.forEach(recursivelyCheckChildrenUsedOnce); | ||
children.forEach(recursivelyCheckChildrenUsedOnce); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto !.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. The if
check preceding this line promotes the type to non-null.
for (int i = 0; i < length; ++i) { | ||
int offset = i * _kPointerDataFieldCount; | ||
data[i] = PointerData( | ||
data.add(PointerData( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iirc, this method is perf sensitive. So just a heads up to watch the dashboard after this lands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, same statement as above with locales: the loss of the generic List constructor seems like a big step back
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might be able to do something similar enough here with:
final List<PointerData> data = <PointerData>[
for (int i = 0; i < length; i += 1)
PointerData( ... )
]
But then you can't keep a local int offset = i * _kPointerDataFieldCount;
around, so maybe not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran the gallery benchmark on a Moto E5 and the numbers were pretty close. I'll run it a few more times in case there's a difference. I don't see an alternative solution though. Since the contents of the list are non-nullable (and Dart doesn't have zero values for objects) there's no way to preallocate and empty list with a non-zero length.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/cc @leafpetersen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A gallery benchmark won't show a regression here, since driver actions do not go through the dart:ui layer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted. As commented above, you can use a list comprehension as @jonahwilliams suggests. If you need locals, you can abstract the body into a local function, which I would very much expect to be inlined (though I've been disappointed before in my life). If list comprehensions aren't well optimized yet, then we should be leaning on the appropriate people to fix that... :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another possibility is to use List.generate, which really should be well-optimized. I can't think of any reason that putting the initialization code here into a lambda that's immediately passed to List.generate shouldn't 100% end up with same generated code as the original or better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can initialize an empty list if you have a dummy filler value available:
final List<PointerData> data = List.filled(length, const PointerData());
// same code as before.
One other possible pattern to use when you don't have a suitable dummy value is to create the list when you have the first value:
List<PointerData>? data;
for (int i = 0; i < length; ++i) {
int offset = i * _kPointerDataFieldCount;
var pointer = PointerData(
timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)),
change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],
kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],
platformData: packet.getInt64(kStride * offset++, _kFakeHostEndian),
scrollDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
scrollDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian));
assert(offset == (i + 1) * _kPointerDataFieldCount);
(data ??= List.filled(length, pointer)[i] = pointer;
}
return PointerDataPacket(data: data!);
It creates the list when you have an element to fill it with. It does require an extra null
check on each iteration.
Those PointerData
objects are huge. I doubt you'll see the overhead of a growable list in the noise of all those allocations.
lib/ui/painting.dart
Outdated
return null; | ||
return _objects[_kShaderIndex] as Shader; | ||
return objects[_kShaderIndex] as Shader?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be objects![]
? Same question here and below for _objects
related things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this grabs objects into a local on L1351, type inference can actually determine it is non-null with the above check. This doesn't work for fields, since Dart can't/doesn't want to try and prove that. The rest of the getters in this class should use the same pattern to avoid a double null-check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because objects
is null checked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that _objects?[_kshaderIndex] as Shader? will work equivalently if desired. cc @a-siva @alexmarkov this is another pattern that might be a candidate for a peephole optimization if it doesn't already fall out (the cast is unnecessary on the null branch).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flow-sensitiveness of the type system gives me the willies, but if Dart folks say that we're holding things the right way, then that's good enough for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I converted all usages of _objects
to the pattern suggested by @leafpetersen
if (nativeFilter == null) { | ||
if (_objects != null) { | ||
_objects[_kColorFilterIndex] = null; | ||
_objects![_kColorFilterIndex] = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
E.g. confusing that it has to go here but not in other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The analyzer cannot type-promote fields because fields can be overridden. In some future, when we have proper sealed classes, we can remove this limitation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_objects?[_kColorFilterIndex] = null; works instead.
'- The iterator ran out of elements. If so, check that "moveNext" returns true prior to calling "current".' | ||
); | ||
} | ||
return currentMetric; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expected a !
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
null check above is sufficient since currentMetric is a local
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's null-checked.
static Map<CallbackHandle/*!*/, Function/*?*/>/*!*/ _backwardCache = | ||
<CallbackHandle/*!*/, Function/*?*/>{}; | ||
static Map<Function, CallbackHandle?> _forwardCache = | ||
<Function, CallbackHandle?>{}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question for this Map
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This map is different from the channel_buffer.dart
case. Here callback handles are allowed to be null. The C++ code has explicit code path for that:
if (!Dart_IsTearOff(func) || name.empty()) {
Dart_SetReturnValue(args, Dart_Null());
return;
}
&& other.scriptCode == scriptCode // scriptCode cannot be '' | ||
&& (other.countryCode == countryCode // Treat '' as equal to null. | ||
|| other.countryCode != null && other.countryCode.isEmpty && countryCode == null | ||
|| otherCountryCode != null && otherCountryCode.isEmpty && countryCode == null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
otherCountryCode!.isEmpty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I introduced the local variables so nullability can be promoted to non-nullability.
/*required*/ String/*!*/ value, | ||
/*required*/ String/*!*/ increasedValue, | ||
/*required*/ String/*!*/ decreasedValue, | ||
int/*!*/ id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file doesn't look migrated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. web_ui
will be migrated in a follow-up PR. I removed /*required*/
because, turns out, the tool doesn't support it. It only supports @required
from package:meta/meta.dart
. However, it's not necessary here anyway, because these are non-null named arguments without a default value, so they will be automatically made required
.
@@ -115,7 +115,7 @@ void main() { | |||
|
|||
// basic tests on horizontal line | |||
final PathMetrics simpleHorizontalMetrics = simpleHorizontalLine.computeMetrics(); | |||
expect(simpleHorizontalMetrics.iterator.current, isNull); | |||
expect(() => simpleHorizontalMetrics.iterator.current, throwsRangeError); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a breaking change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Theoretically, yes, but I expect in practice it's not. Iterators are typically consumed via for/in
,
forEach
, and while (iter.moveNext()) { iter.current; }
, none of which attempt to access elements outside the valid range.
Even though it is mostly mechanical, I suspect a PR of this size could use another 1 or 2 sets of eyes on it. @jonahwilliams could you take a look? |
Will take a look this afternoon |
}) { | ||
assert(offset != null, 'Offset argument was null'); | ||
assert(offset != null, 'Offset argument was null'); // ignore: unnecessary_null_comparison |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems bad that we need to sprinkle these ignores everywhere. offset is still potentially null when this code is called from a non-opted in library. Can we disable this hint for public APIs? Or if this is an error, can it be removed :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to disable the hint globally because outside assert
s it likely indicates an actual bug.
/cc @leafpetersen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've discussed this a fair bit, and it's being discussed again here dart-lang/language#1018 . I don't think we're going to remove this hint though. For most devs who are unlikely to come back and revisit their code, we feel it's better for them to just migrate once as if they were migrating to a fully sound world, and then forget it. Highly polished and maintained code like this will have to go out its way a bit. You can abstract the offset != null into a helper function, so that you don't need the ignore statements. There was a proposal this morning for us to provide that API in the SDK, and to then deprecate it in Dart 3 as a way of automatically forcing people to revisit these once we're in the sound world.
@@ -14,11 +15,11 @@ abstract class OffsetBase { | |||
/// The first argument sets the horizontal component, and the second the | |||
/// vertical component. | |||
const OffsetBase(this._dx, this._dy) | |||
: assert(_dx != null), | |||
assert(_dy != null); | |||
: assert(_dx != null), // ignore: unnecessary_null_comparison |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here: we shouldn't need to ignore an error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It won't be an error when the app opts into null safety because it will prevent passing null value in the first place. We only need these asserts temporarily, while our libraries can be consumed by both opted in and opted out code.
const int stringsPerLocale = 4; | ||
final int numLocales = locales.length ~/ stringsPerLocale; | ||
window._locales = List<Locale>(numLocales); | ||
final List<Locale> newLocales = <Locale>[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how long locales
will be in practice, but it looks like this code was written in performance sensitive way (by avoiding re-allocating the list on a grow) that we're losing in the rewrite.
It would be really nice if List had a capacity argument which let us correctly size it without touching the length.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I doubt this is performance sensitive. The list of locales is typically populated during app start-up and never changes again.
However, this may matter in other situations. @leafpetersen @lrhn would it be possible to add a List
constructor with a capacity
parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pointer conversion code might be a better example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've discussed it, I can raise it again. For this example, you could consider using a list comprehension. I don't know how well optimized those are yet though. @a-siva do you know if code like:
var l = [
for(int i = 0;i<count;i++) {
i
}];
will get compiled to pre-allocate the list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also consider List.generate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a (relative) lot of computation for creating each element. Unless numLocales
is big, or this code is called in a tight loop, I doubt the list growing is going to be visible.
The memory overhead of a growable list over a fixed-length list is real, but again, unless this gets called very often, it's probably not a problem.
I'd go for either:
String? nullIfEmpty(String s) => s.isNotEmpty ? s : null;
var newLocales = <Locale>[for (var i = 0; i < locales.length; i += stringsPerLocale)
Locale.fromSubtags(
languageCode: locales[i],
countryCode: _nullIfEmpty(locales[i + 1]),
scriptCode: _nullifEmpty(locales[i + 2]),
)];
or List.generate
if you really, really want a fixed-length list.
Either would be more readable than repeated addition.
(It would be nice to have let
in list comprehensions, though!)
lib/ui/hooks.dart
Outdated
final Map<String, dynamic> data = json.decode(jsonData) as Map<String, dynamic>; | ||
if (data.isEmpty) { | ||
return; | ||
} | ||
_updateTextScaleFactor((data['textScaleFactor'] as num).toDouble()); | ||
_updateTextScaleFactor((data['textScaleFactor'] as num?)!.toDouble()); | ||
_updateAlwaysUse24HourFormat(data['alwaysUse24HourFormat'] as bool); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't these types nullable too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
even if the API requires them to be non-nullable, we're just parsing JSON here so it could contain anything. In fact, the first cast to as Map<String, dynamic>
is also suspect
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think all should be non-null. That ?)!
was the migration tool being confused by the .toDouble()
call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it is JSON data, it could be anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's two ways to go with JSON data.
Either you validate it thoroughly, with proper error messages on all invalid data, or you assume it's correct and crash with a type error when it's not. Adding nullability to the mix doesn't really change this.
So far this code has been of the "crash if wrong" kind.
I find the if (data.isEmpty)
to be a little unsettling. It suggests that you can omit either all entries or none, and I can't see why omitting all settings is reasonable (why call updateUserSettings
with no changes?).
I can see reasons why updating only some settings would make sense, so I'd have expected, e.g.,
var textScaleFactor = data['textScaleFactor'];
if (textScaleFactor != null) {
_updateTextScaleFactor((textScaleFactor as num).toDouble());
}
And then it's not that far to do:
var textScaleFactor = data['textScaleFactor'];
if (textScaleFactor is num) {
_updateTextScaleFactor(textScaleFactor.toDouble());
}
if (name == ChannelBuffers.kControlChannelName) { | ||
try { | ||
channelBuffers.handleMessage(data); | ||
channelBuffers.handleMessage(data!); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The !
seems questionable. What maintains the invariant here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chatted with @gaaclarke about this one. The invariant is maintained by the protocol for kControlChannelName
. Other channels allow nulls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either we !
here or we do a check for null and throw an exception in handleMessage
. The result is the same no? This way is less code and the semantics are a bit more clear if ever there is another client to handleMessage
.
@@ -68,7 +69,7 @@ void _setupHooks() { // ignore: unused_element | |||
/// ``` | |||
/// | |||
/// This function is only effective in debug and dynamic modes, and will throw in AOT mode. | |||
List<int/*!*/>/*!*/ saveCompilationTrace() { | |||
List<int> saveCompilationTrace() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this actually still a part of the public API? IIRC it is completely unused (and unusable) by the framework so we should probably file a bug to remove it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: flutter/flutter#59205
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/cc @rmacnak-google
}); | ||
}); | ||
} | ||
|
||
/// Returns an error message on failure, null on success. | ||
String/*?*/ _toByteData(int format, _Callback<Uint8List/*?*/> callback) native 'Image_toByteData'; | ||
String? _toByteData(int format, _Callback<Uint8List?> callback) native 'Image_toByteData'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC the contract of this method is that it either returns the buffer through the callback, or returns a String. Why do we need ?
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It returns null on success. Non-null string means error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean the callback data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e. why _Callback<Uint8List?>
instead of _Callback<Uint8List>
list[0] = a; | ||
if (b != null) | ||
list[1] = b; | ||
list[0] = a; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't actually null check or assert on some of the values received in makeList, so this could lead to new runtime exceptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Done.
@@ -4361,6 +4380,10 @@ class Shadow { | |||
int shadowOffset = 0; | |||
for (int shadowIndex = 0; shadowIndex < shadows.length; ++shadowIndex) { | |||
final Shadow shadow = shadows[shadowIndex]; | |||
// TODO(yjbanov): remove the null check when the framework is migrated. While the list |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add an issue for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Filed a more general one: flutter/flutter#59207.
@@ -689,35 +690,36 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { | |||
/// node starts at `elevation` above the parent and ends at `elevation` + | |||
/// `thickness` above the parent. | |||
void updateNode({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is right. While the framework unconditionally passes all of these arguments, I think several of them can actually be null.
For historical evidence, see the null check on scrollChildren
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how the framework passes scrollChildren
though:
scrollChildren: data.scrollChildCount ?? 0,
So I'm not sure why we have this here.
However, I imagine that as we start migrating the framework, we will find out that we got some of these wrong. I think the best way to address it to so fix them one by one in small standalone PRs. There's no way we can get everything right in one mega-PR like this. It's OK to have some best guesses because for a while we'll be running with the framework opted out of nullability, which basically means these annotations here will have no effect. A first approximation should be good enough, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a reasonable approach to me.
void updateCustomAction({/*required*/ int/*!*/ id, String/*?*/ label, String/*?*/ hint, int/*!*/ overrideId = -1}) { | ||
assert(id != null); | ||
assert(overrideId != null); | ||
void updateCustomAction({required int id, String? label, String? hint, int overrideId = -1}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should consider fixing this API to make label and hint non-nullable - IIRC it won't actually work without non-null values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very tempting, but I prefer to limit this PR to syntactic migration as much as possible. We can follow up with smaller PRs fixing individual issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you file an issue 😄
lib/ui/text.dart
Outdated
final List<LineMetrics> metrics = List<LineMetrics>(count); | ||
for (int index = 0; index < metrics.length; index += 1) { | ||
metrics[index] = LineMetrics( | ||
final List<LineMetrics> metrics = <LineMetrics>[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can potentially return a large amount of data,
This concerns about switching to add. There are no local calculations so it should be possible to use a collection element
< LineMetrics>[
for (int index = 0; index < count; index += 1)
LineMetrics(...)
]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
lib/ui/text.dart
Outdated
@@ -2268,11 +2273,11 @@ final ByteData _fontChangeMessage = utf8.encoder.convert( | |||
|
|||
FutureOr<void> _sendFontChangeMessage() async { | |||
if (window.onPlatformMessage != null) | |||
window.onPlatformMessage( | |||
window.onPlatformMessage!( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would tear-off the function, then null check. As written this is a double null check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something tells me double null check is orders of magnitude faster than a tear-off. But I can convert to a tear-off if you prefer that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait, that's not a method but a field. Nothing to tear-off. I converted to this:
window.onPlatformMessage?.call(
'flutter/system',
_fontChangeMessage,
(_) {},
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yay, best of both worlds
_initialLifecycleStateAccessed = true; | ||
return _initialLifecycleState; | ||
} | ||
/*late*/ String/*!*/ _initialLifecycleState; | ||
late String _initialLifecycleState; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we actually guarantee this is non-null on startup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I traced it to https://github.com/dart-lang/sdk/blob/0fab083e1c2b7747da090d937d7dcb8fb9170995/runtime/vm/dart_api_impl.cc#L2811, which doesn't allow nulls. The question is though, is this guaranteed to be populated during application start-up. Checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On iOS it is populated via https://developer.apple.com/documentation/uikit/uiviewcontroller/1621423-viewdidappear. On Android, FlutterView.onStart
sends "AppLifecycleState.inactive". On the web this has a fixed value of "AppLifecycleState.resumed".
I think we're good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
@jonahwilliams @zanderso Do my replies make sense? |
* 141ee78 Roll Dart SDK from 2b917f5b6a0e to 0fab083e1c2b (15 revisions) (flutter/engine#18974) * f5ab179 Call Shell::NotifyLowMemoryWarning on Android Trim and LowMemory events (flutter/engine#18979) * 4dabac9 Roll Fuchsia Linux SDK from 8AiUM... to ThzHh... (flutter/engine#18976) * 50cae02 Reland: Add RAII wrapper for EGLSurface (flutter/engine#18977) * db7f226 Include the memory header as unique_ptr is used in ASCII Trie. (flutter/engine#18978) * b19a17d Implement an EGL resource context for the Linux shell. (flutter/engine#18918) * ca26b75 Make Linux shell plugin constructor descriptions consistent (flutter/engine#18940) * efd1452 Roll Skia from 9c401e7e1ace to f69e40841eb9 (11 revisions) (flutter/engine#18983) * e5845af Put JNI functions under an interface (flutter/engine#18903) * 1ddc6e1 Call destructor and fix check (flutter/engine#18985) * 7f71f1d Roll Fuchsia Mac SDK from oWhyp... to gAD3P... (flutter/engine#18982) * 74291b7 Roll Dart SDK from 0fab083e1c2b to e62b89c56d01 (10 revisions) (flutter/engine#18987) * 2739bbf [web] Provide a hook to disable location strategy (flutter/engine#18969) * 336d000 Roll Skia from f69e40841eb9 to 553496b66f12 (2 revisions) (flutter/engine#18992) * b82769b Roll Skia from 553496b66f12 to 0ad37b87549b (2 revisions) (flutter/engine#18993) * f5ca58b Roll Dart SDK from e62b89c56d01 to b0d2b97d2cd7 (4 revisions) (flutter/engine#18994) * 2a82a08 [dart] Account for compiler api change (flutter/engine#19002) * 369e0a9 Add ui_benchmarks (flutter/engine#18945) * a0a92d6 Roll Skia from 0ad37b87549b to de175abede4d (1 revision) (flutter/engine#18995) * cea5a9c Roll Dart SDK from b0d2b97d2cd7 to f043f9e5f6ea (6 revisions) (flutter/engine#18997) * d417772 Roll Fuchsia Mac SDK from gAD3P... to Wj0yo... (flutter/engine#19001) * 4538622 Roll Skia from de175abede4d to 32d5cfa1f35e (15 revisions) (flutter/engine#19005) * 71fce02 Fix shift-tab not working by adding more GTK->GLFW key mappings. (flutter/engine#18988) * 5ddc122 Fix inverted check in creating resource surface (flutter/engine#18989) * 87d8888 Show warning if method response errors occur and error not handled. (flutter/engine#18946) * 5171fbd Roll Skia from 32d5cfa1f35e to 21bbfc6c2dfe (5 revisions) (flutter/engine#19006) * 4bd6aea Always send key events, even if they're used for text input. (flutter/engine#18991) * 9f898e9 Don't process key events when the text input is not requested (flutter/engine#18990) * 48d7091 Roll buildroot for Windows warning update (flutter/engine#19000) * 5f37029 Roll Dart SDK from f043f9e5f6ea to f0ea02bc51f8 (22 revisions) (flutter/engine#19010) * ac809b4 onBeginFrame JNI (flutter/engine#18866) * e1c622b Make SKSL caching test work on Fuchsia on arm64 (flutter/engine#18572) * 2651beb Exit before pushing a trace event when layer tree holder is empty (flutter/engine#19008) * acf048b Remove log added for local testing (flutter/engine#19012) * 1f3aa23 Roll Dart SDK from f0ea02bc51f8 to 0b64f5488965 (9 revisions) (flutter/engine#19013) * 8dcc95d Roll Fuchsia Mac SDK from Wj0yo... to gR0Zc... (flutter/engine#19015) * f6455fa Roll Dart SDK from 0b64f5488965 to 50836c171e91 (4 revisions) (flutter/engine#19017) * b2fea9d Roll Skia from 21bbfc6c2dfe to 30212b7941d6 (6 revisions) (flutter/engine#19009) * 0065646 Roll Skia from 30212b7941d6 to 3d6bf04366f6 (17 revisions) (flutter/engine#19020) * 0a852d8 Revert "Call Shell::NotifyLowMemoryWarning on Android Trim and LowMemory events (flutter#18979)" (flutter/engine#19023) * 194acdf apply null safety syntax to mobile dart:ui (flutter/engine#18933) * b0a0e0e Roll Skia from 3d6bf04366f6 to 637838d20abd (2 revisions) (flutter/engine#19021) * be499ab Roll Fuchsia Mac SDK from gR0Zc... to H-uAk... (flutter/engine#19022) * 8c24c41 Roll Skia from 637838d20abd to ac16760df463 (1 revision) (flutter/engine#19025) * 7cb7003 onEndFrame JNI (flutter/engine#18867) * e3fdb23 [fuchsia] Add ability to configure separate data and asset dirs (flutter/engine#18858) * 983b6e1 Call Shell::NotifyLowMemory when backgrounded/memory pressure occurs on Android (flutter/engine#19026) * f7d241f Wire up channel for restoration data (flutter/engine#18042) * e84d497 Fix hit testing logic in fuchsia a11y (flutter/engine#19029) * 801559a Revert to last-known-good-rev of Dart SDK (flutter/engine#19031)
* 141ee78 Roll Dart SDK from 2b917f5b6a0e to 0fab083e1c2b (15 revisions) (flutter/engine#18974) * f5ab179 Call Shell::NotifyLowMemoryWarning on Android Trim and LowMemory events (flutter/engine#18979) * 4dabac9 Roll Fuchsia Linux SDK from 8AiUM... to ThzHh... (flutter/engine#18976) * 50cae02 Reland: Add RAII wrapper for EGLSurface (flutter/engine#18977) * db7f226 Include the memory header as unique_ptr is used in ASCII Trie. (flutter/engine#18978) * b19a17d Implement an EGL resource context for the Linux shell. (flutter/engine#18918) * ca26b75 Make Linux shell plugin constructor descriptions consistent (flutter/engine#18940) * efd1452 Roll Skia from 9c401e7e1ace to f69e40841eb9 (11 revisions) (flutter/engine#18983) * e5845af Put JNI functions under an interface (flutter/engine#18903) * 1ddc6e1 Call destructor and fix check (flutter/engine#18985) * 7f71f1d Roll Fuchsia Mac SDK from oWhyp... to gAD3P... (flutter/engine#18982) * 74291b7 Roll Dart SDK from 0fab083e1c2b to e62b89c56d01 (10 revisions) (flutter/engine#18987) * 2739bbf [web] Provide a hook to disable location strategy (flutter/engine#18969) * 336d000 Roll Skia from f69e40841eb9 to 553496b66f12 (2 revisions) (flutter/engine#18992) * b82769b Roll Skia from 553496b66f12 to 0ad37b87549b (2 revisions) (flutter/engine#18993) * f5ca58b Roll Dart SDK from e62b89c56d01 to b0d2b97d2cd7 (4 revisions) (flutter/engine#18994) * 2a82a08 [dart] Account for compiler api change (flutter/engine#19002) * 369e0a9 Add ui_benchmarks (flutter/engine#18945) * a0a92d6 Roll Skia from 0ad37b87549b to de175abede4d (1 revision) (flutter/engine#18995) * cea5a9c Roll Dart SDK from b0d2b97d2cd7 to f043f9e5f6ea (6 revisions) (flutter/engine#18997) * d417772 Roll Fuchsia Mac SDK from gAD3P... to Wj0yo... (flutter/engine#19001) * 4538622 Roll Skia from de175abede4d to 32d5cfa1f35e (15 revisions) (flutter/engine#19005) * 71fce02 Fix shift-tab not working by adding more GTK->GLFW key mappings. (flutter/engine#18988) * 5ddc122 Fix inverted check in creating resource surface (flutter/engine#18989) * 87d8888 Show warning if method response errors occur and error not handled. (flutter/engine#18946) * 5171fbd Roll Skia from 32d5cfa1f35e to 21bbfc6c2dfe (5 revisions) (flutter/engine#19006) * 4bd6aea Always send key events, even if they're used for text input. (flutter/engine#18991) * 9f898e9 Don't process key events when the text input is not requested (flutter/engine#18990) * 48d7091 Roll buildroot for Windows warning update (flutter/engine#19000) * 5f37029 Roll Dart SDK from f043f9e5f6ea to f0ea02bc51f8 (22 revisions) (flutter/engine#19010) * ac809b4 onBeginFrame JNI (flutter/engine#18866) * e1c622b Make SKSL caching test work on Fuchsia on arm64 (flutter/engine#18572) * 2651beb Exit before pushing a trace event when layer tree holder is empty (flutter/engine#19008) * acf048b Remove log added for local testing (flutter/engine#19012) * 1f3aa23 Roll Dart SDK from f0ea02bc51f8 to 0b64f5488965 (9 revisions) (flutter/engine#19013) * 8dcc95d Roll Fuchsia Mac SDK from Wj0yo... to gR0Zc... (flutter/engine#19015) * f6455fa Roll Dart SDK from 0b64f5488965 to 50836c171e91 (4 revisions) (flutter/engine#19017) * b2fea9d Roll Skia from 21bbfc6c2dfe to 30212b7941d6 (6 revisions) (flutter/engine#19009) * 0065646 Roll Skia from 30212b7941d6 to 3d6bf04366f6 (17 revisions) (flutter/engine#19020) * 0a852d8 Revert "Call Shell::NotifyLowMemoryWarning on Android Trim and LowMemory events (flutter#18979)" (flutter/engine#19023) * 194acdf apply null safety syntax to mobile dart:ui (flutter/engine#18933) * b0a0e0e Roll Skia from 3d6bf04366f6 to 637838d20abd (2 revisions) (flutter/engine#19021) * be499ab Roll Fuchsia Mac SDK from gR0Zc... to H-uAk... (flutter/engine#19022) * 8c24c41 Roll Skia from 637838d20abd to ac16760df463 (1 revision) (flutter/engine#19025) * 7cb7003 onEndFrame JNI (flutter/engine#18867) * e3fdb23 [fuchsia] Add ability to configure separate data and asset dirs (flutter/engine#18858) * 983b6e1 Call Shell::NotifyLowMemory when backgrounded/memory pressure occurs on Android (flutter/engine#19026) * f7d241f Wire up channel for restoration data (flutter/engine#18042) * e84d497 Fix hit testing logic in fuchsia a11y (flutter/engine#19029) * 801559a Revert to last-known-good-rev of Dart SDK (flutter/engine#19031)
Description
Applies the null safety syntax to the mobile implementation of
dart:ui
.Notable changes:
api_conform_test.dart
. The parser crashes on NNBD code. We are using deprecate analyzer API, so we'll need to migrate to something supported.analysis_options.yaml
andanalyze.sh
.// ignore: unnecessary_null_comparison
where we null-check arguments, because until everyone opts-in the type annotations themselves won't have an effect in opted out code.List
constructor in favor ofList.filled
for nullables, and plain[]
for non-nullables.TextStyle._fontFamily
seems to have never been null, so I removed the null checking code paths.PathMetricIterator
now throwsRangeError
instead of returningnull
ifcurrent
is accessed before iteration starts, or when the iterator runs out of elements. This is so we can makePathMetric
non-nullable.window_hooks_integration_test.dart
to not depend onpackage:test
. This test is special in the it makesdart:ui
filespart of
itself. It's weird to have fully migrateddart:ui
to depend on non-migrated code.Related Issues
flutter/flutter#53661