-
Notifications
You must be signed in to change notification settings - Fork 207
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
Should the migration to NNBD include eliminating implicit downcasts? #192
Comments
YES PLEASE. |
A key question is, how intrusive would this be to existing code bases. That is, would eliminating implicit casts add large amounts of syntactic noise to existing code bases. Here are a few examples of packages that have done this migration that I've found:
|
This isn't a super clean commit because it also includes a bunch of --no-implicit-dynamic fixes, but here is the commit where I fixed most implicit casts in my game. This file in particular shows casts I needed to handle processing some JSON. Overall, there weren't too many changes for casts and some bugs were discovered. Here is the change in the SDK where I enabled --no-implicit-casts for test.dart. It also includes a bunch of --no-implicit-dynamic changes too. It's sort of a worst case example because, at the time, test.dart heavily used a giant map as its core data structure. This change was in fact intended to find the places where that map was used so I could replace it with an actual typed object. |
I filed an issue for discussing implicit casts during iteration here: #264 . |
Note that it could damage readability if we give up the distinction between arbitrary casts and downcasts: // Assume that we have some unrelated classes `A` and `B`,
// and a class that implements both, `C`.
class A {}
class B {}
class C implements A, B {} Here's some code as we could write it today (when accepting implicit casts): main() {
A a = C();
B b = a as B; // Cast required, this is an "arbitrary" cast.
C c = a; // Downcast, permitted.
} If we just require all casts to be explicit then we'd have the following, which drops the hint that some casts are more risky than others: main() {
// All casts must be explicit.
A a = C();
B b = a as B;
C c = a as C;
} The point is that we could have explicit casts, and still avoid some of the verbosity, and still maintain the distinction between "less surprising" and "more surprising" casts (using main() {
// All casts must be explicit, but downcasts can be `!!`.
A a = C();
B b = a as B;
C c = a!!;
} |
This is accepted. |
cc @natebosch |
Reopening to see if we can take this further. The implementations currently allow implicit casts from My understanding is that the main argument for allowing implicit casts from Can we reconsider the strength of this change? |
I suggested that you should be able to do implicit downcasts from Any dynamic value is inherently unsafe! You can call any method on it without a static type check. If (you think that) you know that a value is a I'd be much happier working towards fewer implicit dynamic types. For example, we could consider changing instantiate-to-bounds of raw types to use So,. I do think that you should consider removing the "no implicit downcast" lint as long as you keep the 'no implicit dynamic" lint. The type Would you recommend that we disallow dynamic invocations as well? Because that would be consistent with disallowing the downcasts. You would always have to cast a |
I agree. We have the opportunity to make them slightly more safe relatively cheaply.
In practice I believe this is a very rare use case for
I strongly agree that we should work on this.
I think they are both problems. We have an established, well received solution to one of these problems. We are intentionally applying part of that popular solution to the core SDK so that everyone can benefit. I don't think we should avoid making things safer than they are today because of a potential solution that has no schedule or plan.
In practice
Yes, if I thought that I could convince anyone I'd definitely recommend removing dynamic invocations, or at least making them stand out like an explicit |
I would be happy to help make this decision easier in any way possible, including doing user studies, meeting with internal teams to gather feedback, running global presubmits, etc - this is probably the I feel frustrated that I think we have tried to make a case we do not like the current dynamic dispatch behavior in Dart 2.0 for a number of years now with seemingly little progress (outside of "well, you can write a lint" or "you can have IntelliJ highlight it"). Please please let us know if there is anything we can do to help facilitate progress here. I know @srawlins is OOO right now, but he had a number of plans around tightening up the language (strict dynamic?) that I worked with him and @leafpetersen on, I do not know if that is still planned and/or available, but I consider that near mandatory right now to use the language properly. |
@matanlurey can you give us a few real-world (i.e. google3) example of where implicit downcasts from |
Sure, I guess I could find some specific examples. The simplest one I can think of is something like: void readMap(Map<String, dynamic> someBlob) {
doAThing(someBlob['campaign']); // No indication this is inserting a runtime check
}
void doAThing(AdsCampaign campaign) {} My main point is this seems to be walking back "strict" checks that I thought were already agreed to and we spent time developing and discussing for a few quarters. If that isn't feasible than I think disabling implicit-downcasts entirely is in the best interest of our users. |
I've seen cases with incorrect defaulting. Something like: set entity(Map<String, dynamic> entity) {
_foo = entity['foo'] ?? 0;
//... more fields
}
Int64 _foo; The author in that case had forgotten to write a test where set entity(Map<String, dynamic> entity) {
_foo = entity['foo'] as Int64 ?? Int64.zero;
//... more fields
}
Int64 _foo; |
If you follow the dart sdk gitter channel there are multiple instances of people being confused about some runtime failure, somebody telling them to disable implicit casts, and them replying with some form of "oh my god why isn't this the default I just fixed X bugs". Most codebases I have migrated to it have also had bugs fixed as a result. We have a lot of concrete evidence that the existing feature as shipped is exactly what users want, and relaxing that for |
Here is another real world (and still existing) bug that would be more visible with explicit casts from dynamic: In this case we surfaced the bug with a different lint. Disabling implicit casts in this package shows a bunch of stuff, including from |
Ok, after further discussion, there's no plan to change this at this point. We will move forward with optional strict checking separately. |
Dart 2.0 assignability allows an assignment from something of type
A
to something of typeB
ifA
is a subtype ofB
or ifB
is a subtype ofA
. With non-nullable types, the implication would be that assignment of a nullable variable to a non-nullable variable would be silently accepted. This seems very contrary to the spirit of the NNBD change, and is inconsistent with what we expect from other languages.One possible solution is to special case non-nullability with respect to assignability. For example, add a side condition that
B
must be nullable ifA
is.An alternative is to take this opportunity to remove implicit downcasts from the language entirely as part of the NNBD opt in. Implicit downcasts seem to be a common source of confusion since they push a surprising amount of type checking to runtime. See background issues below for more discussion.
Topic specific discussion issues:
Background issues:
dart-lang/sdk#33749
dart-lang/sdk#31410
dart-lang/sdk#30402
dart-lang/sdk#32661
dart-lang/sdk#25368
dart-lang/sdk#29718
dart-lang/sdk#30385
dart-lang/sdk#29547
dart-lang/sdk#29548
The text was updated successfully, but these errors were encountered: