Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

apply null safety syntax to mobile dart:ui #18933

Merged
merged 9 commits into from
Jun 12, 2020

Conversation

yjbanov
Copy link
Contributor

@yjbanov yjbanov commented Jun 9, 2020

Description

Applies the null safety syntax to the mobile implementation of dart:ui.

Notable changes:

  • Use the migration tool to convert temporary annotations to new NNBD syntax, with some manual assistance.
  • Temporarily disable 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.
  • Enable non-nullable analysis option in analysis_options.yaml and analyze.sh.
  • Add // 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.
  • Remove usages of List constructor in favor of List.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 throws RangeError instead of returning null if current is accessed before iteration starts, or when the iterator runs out of elements. This is so we can make PathMetric non-nullable.
  • Changed window_hooks_integration_test.dart to not depend on package:test. This test is special in the it makes dart:ui files part of itself. It's weird to have fully migrated dart:ui to depend on non-migrated code.

Related Issues

flutter/flutter#53661

@@ -71,7 +72,7 @@ class _RingBuffer<T> {
while (_queue.length > lengthLimit) {
final T item = _queue.removeFirst();
if (_dropItemCallback != null) {
_dropItemCallback(item);
_dropItemCallback!(item);

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat! Done.

@yjbanov yjbanov force-pushed the nnbd-migrate-apply-ui branch from 5b7fe68 to c203f55 Compare June 9, 2020 23:01
@yjbanov yjbanov marked this pull request as ready for review June 10, 2020 00:01
@yjbanov yjbanov requested a review from zanderso June 10, 2020 00:01
@yjbanov yjbanov force-pushed the nnbd-migrate-apply-ui branch from c203f55 to b2a9c70 Compare June 10, 2020 00:02
@auto-assign auto-assign bot requested a review from chinmaygarde June 10, 2020 00:03
@yjbanov yjbanov force-pushed the nnbd-migrate-apply-ui branch from b2a9c70 to 131668d Compare June 10, 2020 17:24
@@ -60,7 +61,7 @@ class _RingBuffer<T> {
}

/// Returns null when empty.
T pop() {
T? pop() {
Copy link
Member

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?

Copy link
Member

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.

Copy link
Contributor Author

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 =
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@lrhn lrhn Jun 11, 2020

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) {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

@zanderso zanderso Jun 11, 2020

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.

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto !.?

Copy link
Contributor Author

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(
Copy link
Member

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.

Copy link
Member

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

Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

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

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... :)

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.

Copy link
Contributor

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.

return null;
return _objects[_kShaderIndex] as Shader;
return objects[_kShaderIndex] as Shader?;
Copy link
Member

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.

Copy link
Member

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

Copy link
Contributor Author

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.

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).

Copy link
Member

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.

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Contributor Author

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.

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected a ! here.

Copy link
Member

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

Copy link
Contributor Author

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?>{};
Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherCountryCode!.isEmpty

Copy link
Contributor Author

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,
Copy link
Member

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.

Copy link
Contributor Author

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);
Copy link
Member

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?

Copy link
Contributor Author

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.

@zanderso
Copy link
Member

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?

@jonahwilliams
Copy link
Member

Will take a look this afternoon

}) {
assert(offset != null, 'Offset argument was null');
assert(offset != null, 'Offset argument was null'); // ignore: unnecessary_null_comparison
Copy link
Member

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 :)

Copy link
Contributor Author

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 asserts it likely indicates an actual bug.

/cc @leafpetersen

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
Copy link
Member

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

Copy link
Contributor Author

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>[];
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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

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?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also consider List.generate.

Copy link
Contributor

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!)

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);
Copy link
Member

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?

Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

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.

Copy link
Contributor

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!);
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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() {
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

});
});
}

/// 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';
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

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;
Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

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

Copy link
Contributor Author

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({
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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}) {
Copy link
Member

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

Copy link
Contributor Author

@yjbanov yjbanov Jun 10, 2020

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.

Copy link
Member

@jonahwilliams jonahwilliams Jun 10, 2020

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>[];
Copy link
Member

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(...)
]

Copy link
Contributor Author

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!(
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

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,
    (_) {},
  );

Copy link
Member

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;
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

@yjbanov
Copy link
Contributor Author

yjbanov commented Jun 10, 2020

@jonahwilliams @zanderso Do my replies make sense?

engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 17, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 18, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 18, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 18, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 18, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 18, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 18, 2020
zljj0818 pushed a commit to zljj0818/flutter that referenced this pull request Jun 22, 2020
* 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)
mingwandroid pushed a commit to mingwandroid/flutter that referenced this pull request Sep 6, 2020
* 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)
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants