Remove implicit downcasts from flutter_tools#31031
Remove implicit downcasts from flutter_tools#31031jonahwilliams wants to merge 6 commits intoflutter:masterfrom
Conversation
| final ProcessResult result = processManager.runSync(<String>[javaExecutable, '-version']); | ||
| if (result.exitCode == 0) { | ||
| final List<String> versionLines = result.stderr.split('\n'); | ||
| final List<String> versionLines = result.stderr.split('\n') as List<String>; |
There was a problem hiding this comment.
All of the File/Process APIs are typed as dynamic :(
| if (name.contains('_snapshot_') || name.endsWith('.so')) { | ||
| final List<int> diff = bsdiff(oldFile.content, newFile.content); | ||
| final int ratio = 100 * diff.length ~/ newFile.content.length; | ||
| final List<int> diff = bsdiff(oldFile.content as List<int>, newFile.content as List<int>); |
There was a problem hiding this comment.
what is oldFile.content statically?
| final List<int> diff = bsdiff(oldFile.content, newFile.content); | ||
| final int ratio = 100 * diff.length ~/ newFile.content.length; | ||
| final List<int> diff = bsdiff(oldFile.content as List<int>, newFile.content as List<int>); | ||
| final int ratio = 100 * diff.length ~/ (newFile.content.length as int); |
There was a problem hiding this comment.
is length not statically an int?
There was a problem hiding this comment.
Like above, all of the file/devfs/process APIs use dynamic
There was a problem hiding this comment.
sure but the most type-safe solution here would be to cast the content, not the length.
| continue; | ||
| } | ||
| final xml.XmlElement xmlElement = node; | ||
| final xml.XmlElement xmlElement = node as xml.XmlElement; |
There was a problem hiding this comment.
replace the !(...is...) with is! and this should just work. Or wrap this in the is instead of bailing early. We shouldn't need both, either way.
There was a problem hiding this comment.
Mentioned below: is! does not promote
There was a problem hiding this comment.
well we should still use is! instead of !(...is...) in any case. :-)
| (_Entry e) => e is _Attribute && e.key.startsWith(name), | ||
| orElse: () => null, | ||
| ); | ||
| ) as _Attribute; |
There was a problem hiding this comment.
you can factor out the e is _Attribute using whereType and that'll get rid of the as
There was a problem hiding this comment.
Done, here and below
| // version that might be binary incompatible with baseline APK. | ||
| final Function contentEquals = const ListEquality<int>().equals; | ||
| if (!contentEquals(f.readAsBytesSync(), af.content)) { | ||
| print(af.content.runtimeType); |
| final Fingerprint typedOther = other; | ||
| return _equalMaps(typedOther._checksums, _checksums) | ||
| && _equalMaps(typedOther._properties, _properties); | ||
| if (other is Fingerprint) { |
There was a problem hiding this comment.
we should compare runtimeType not use is, in an == operator; see style guide
There was a problem hiding this comment.
comparing runtimeType doesn't do a type promotion, so we need to cast regardless. I'd rather have the type safety of the is check, and I think we should allow the tool to diverge from the framework in this regard - especially since there are only a handful of types that implement operator.== here.
There was a problem hiding this comment.
the problem is it'll break when you subclass Fingerprint, and it'll break when someone copies and pastes this code into other code where it isn't as safe.
We're going to have to work out how to make this type safe in any case.
| continue; | ||
| } | ||
| final File file = entity; | ||
| final File file = entity as File; |
There was a problem hiding this comment.
hm, this is similar to the case above. i guess i was wrong about is!. does it let you avoid the as if you wrap instead of bailing? maybe file a bug on the sdk for this, the compiler should be able to determine that entity is a File here.
There was a problem hiding this comment.
The canonical issue is dart-lang/sdk#25565. better type promotion is a requirement of NNBD, and last I heard was a work in progress
| <String>['ver'], runInShell: true); | ||
| if (result.exitCode == 0) | ||
| _name = result.stdout.trim(); | ||
| _name = result.stdout.trim() as String; |
There was a problem hiding this comment.
i'd rather put the as on the stdout here.
| // pointing elsewhere somehow. | ||
| final yaml.YamlMap pubSpecYaml = yaml.loadYaml(fs.file(pubSpecYamlPath).readAsStringSync()); | ||
| final String packageName = pubSpecYaml['name']; | ||
| final yaml.YamlMap pubSpecYaml = yaml.loadYaml(fs.file(pubSpecYamlPath).readAsStringSync()) as yaml.YamlMap; |
| @@ -44,9 +44,9 @@ class EmulatorsCommand extends FlutterCommand { | |||
| } | |||
|
|
|||
| if (argResults.wasParsed('launch')) { | |||
|
To make this easier to review it might make sense to do it in bits, first the ones that involve |
| final ProcessResult result = await processManager.run(<String>[javaBinary, '-version']); | ||
| if (result.exitCode == 0) { | ||
| final List<String> versionLines = result.stderr.split('\n'); | ||
| final List<String> versionLines = result.stderr.split('\n') as List<String>; |
There was a problem hiding this comment.
dang, what was this without the cast?!
There was a problem hiding this comment.
stderr is dynamic unfortunately.
christopherfujino
left a comment
There was a problem hiding this comment.
TL;DR if the print statements were intentional, LGTM.
| // version that might be binary incompatible with baseline APK. | ||
| final Function contentEquals = const ListEquality<int>().equals; | ||
| if (!contentEquals(f.readAsBytesSync(), af.content)) { | ||
| print(af.content.runtimeType); |
There was a problem hiding this comment.
These supposed to be here, and without further context?
|
|
||
| group(FuchsiaIsolateDiscoveryProtocol, () { | ||
| Future<Uri> findUri(List<MockFlutterView> views, String expectedIsolateName) { | ||
| Future<Uri> findUri(List<MockFlutterView> views, String expectedIsolateName) async { |
There was a problem hiding this comment.
Why is this marked async? I thought you only needed async if you used await. Is it because of .thenAnswer?
There was a problem hiding this comment.
This needs to return a Future<Uri> but was currently returning only a Uri. The most straightforward way to fix that is to use an `async method.
There was a problem hiding this comment.
You can also return Future.value(discovertyProtocol.uri)
|
add2app macos strange failure: |
|
Unfortunately add2app-macos is super flaky |
There was a problem hiding this comment.
cool, was just gonna mention this 👍
I'm happy to split this up in a way that can be aided by tools, such as a directory based approach? splitting it up based on subject matter is going to require me verifying every cast multiple times |
|
Closing in favor of smaller refactors |
Remove 750 implicit downcasts from flutter_tools
Description
An implicit downcast is currently allowed in the Dart language, it looks something like this:
This is equivalent to an
ascast - the only difference is the syntax (and at one point the generated code for AOT which the tool does not use).The problem with implicit casts arises when they are used accidentally. The most common example in forgetting to
toListanIterable. This is allowed because we perform an implicit downcast fromIterable<T>toList<T>.Most recently when pushing updates to the devicelab agent, I had to repeat the process 3 times due to implicit cast errors that I had accidentally introduced!
Since the analyzer has introduced the capability to disable implicit casts, I believe we should take advantage of this for the flutter_tool. correctness here is paramount, and while we're working to measure and improve test coverage, I don't believe it is sufficient to catch all errors.
Introducing this change exposed a handful of bugs, my personal favorite:
Furthermore, it identifies several areas of the tool that rely heavily on runtime typing being correct, namely the Context/injection system, Arguments, and the ApplicationPackage class. These should all receive follow up refactors to be made more type safe, so that the explicit casting can be removed.
Related Issues
dart-lang/sdk#31410
Tests
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?