-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[pigeon] Move test support logic out of the default Dart output. #246
Conversation
flutter/plugins#3281 applies these changes to video_player. |
dd8a506
to
0bcb6a8
Compare
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 going to pick this up tomorrow, I got to it late and it's taking longer than I expected. Generally looking good I want to run it a few times while I think about it. I think the encode
and decode
methods was a good compromise while I'm looking at it.
final String nullTag = opt.isNullSafe ? '?' : ''; | ||
final String nullable = opt.isNullSafe ? '?' : ''; | ||
final String notNullable = opt.isNullSafe ? '!' : ''; | ||
bool first = true; |
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 find these variable names confusing. nullTag
described it as a part of the language, nullable
doesn't describe it as a character that has semantic meaning. I checked the Dart documentation to see what they call the !
operator and unfortunately I couldn't find a name. In other languages it's called unwrapping.
How about nullableTag
and unwrapOperator
or unwrapper
?
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 i didn't realise "null tag" was the official name. Happy to change these to whatever you want.
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, ?=nulltag and !=unwrapOperator
} else if (replyMap['error'] != null) { | ||
\tfinal Map<dynamic, dynamic> error = replyMap['${Keys.error}']; | ||
\tfinal Map<Object$nullable, Object$nullable> error = replyMap['${Keys.error}'] as Map<Object$nullable, Object$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.
Why did you change this? Because dynamic doesn't denote null-safety? Why was there a dynamic and an Object before NNBD? Are some things not Objects?
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.
dynamic
hides bugs, because it disables all type checking. By using Object?
we can see where we're casting and we can make sure the methods (etc) we're using on those objects don't have typos.
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 your questions, dynamic
is essentially the same as Object?
, except that the language never checks what you call on objects that are marked dynamic
, it's like you're in an untyped language. Everything is an Object?
.)
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.
Okay, finished up the review. The only big issues I noticed is that there is a null check that has been removed from the decode method. That makes me nervous, do you think that is correct and that it is adequately tested already? Also there is an added case where channels can fail silently and bypass the provided API.
packages/pigeon/lib/pigeon_lib.dart
Outdated
/// calls from your real [HostApi] class in Dart instead of the host | ||
/// platform code, as is typical. | ||
/// | ||
/// When using this, you must specify the `--out-test-dart` argument |
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 need to use snake case, not kebob case 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.
Fixed.
from: context.fromUri( | ||
path.toUri(path.dirname(path.absolute(options.dartTestOut))), | ||
), | ||
); |
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 do the same path juggling here twice, it could be more clear to pull some of that into a helper function.
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.
If you mean the code two lines above this, it's subtly different in ways that makes it hard to usefully factor out. :-(
(I spent some time trying to find a way to factor it out cleanly and it ended up just being simpler to do it this way.)
That said if you have any suggestions on how to factor it out let me know.
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.
Couldn't you just do this?
String posixify(String path) {
final path.Context context = path.Context(style: path.Style.posix);
return context.fromUri(path.toUri(path.absolute(path));
}
posixify(options.dartOut);
posixify(path.dirname(options.dartTestOut);
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 theory being that path.dirname(path.absolute(x)) == path.absolute(path.dirname(x))
? Seems reasonable, let me give it a try.
final Map<Object, Object> pigeonMap = message as Map<Object, Object>; | ||
return SearchReply() |
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.
_fromMap
used to accept null input, now decode doesn't. I see there appears to be one place where decode is called without a null check to the argument. Are you sure this is safe to remove?
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 must have misread the code; it looked like it used to crash when the message was null, but I see in the output for video_player that there's a null check.
I'm happy to make it accept null again.
(Is there ever a valid reason to send 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.
Oh I remember why I made it not accept null -- it's not so much that the input is non-nullable, so much as that the output is non-nullable. With a null input I don't know what to return. I could return null but then I need to null check around every call site anyway, and it seemed cleaner to have the null check before the call than after the 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.
Yea, that makes sense. I just checked the output with nullsafety and it looks like this:
static SearchReply _fromMap(Map<dynamic, dynamic> pigeonMap) {
final SearchReply result = SearchReply();
result.result = pigeonMap['result'];
result.error = pigeonMap['error'];
return result;
}
Looks like I already got rid of the null check, this generated code just hasn't been updated in a while.
channel.setMessageHandler(null); | ||
} else { | ||
channel.setMessageHandler((Object message) async { | ||
if (message == 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.
We shouldn't bypass calling into the api. Either we support null here and pass it through to the api, or we don't and we assert. Silently bypassing the API is just going to create hard to debug situations.
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 then FlutterSearchApi.search has to take a nullable argument. Do we want 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.
I think this is the main open question here. (bold so I can find this in the github page)
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.
Ok sounds like from the previous comment that you're good leaving this as-is.
|
||
final Map<dynamic, dynamic> replyMap = await channel.send(requestMap); | ||
final Map<Object, Object> replyMap = | ||
await channel.send(encoded) as Map<Object, Object>; |
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.
Are these casts incurring any runtime cost?
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.
My understanding is that casts are cheaper than dynamic dispatch on dynamic
these days, but I could be wrong.
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 put it another way: there were casts before, this just made them visible.)
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.
(Specifically, before, we were assigning a dynamic
(the return value of await channel.send(encoded)
) to a variable of type Map<dynamic, dynamic>
, which implies a cast. Now we are assigning an Object
to a variable of type Map<Object, Object>
with an explicit cast. I wouldn't be surprised if the assembly was actually byte for byte identical, though I haven't checked.)
final SearchReply output = api.search(input); | ||
return <dynamic, dynamic>{'result': output._toMap()}; | ||
}); | ||
return SearchReply.decode(replyMap['result']); |
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.
Here's the call whose behavior would change as a result of removing the null check in decode.
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.
Ah, yes. In null-safe mode this has a !
in the new generated code. I can check it for a result
key and throw if it's missing, if you like. Or do something else. Your 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.
I think this is good. I got a confused by the skip in checking in generated code. Thanks ian.
This will allow us to move the mock logic out of the core framework.
Comments applied, tests pass! I'ma land it! |
Deep location example: Migrate to null safety
This will allow us to move the mock logic out of the core framework.
I also got distracted and changed some of the output to be cleaner while I was at it.