-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
promote locals without an upcast first #33932
Comments
Actually, it seems to fail more broadly. Consider: class Selectable<T> {
void doThing() {}
}
class SelectionModel<T> {
}
class Controller<T> {
SelectionModel<T> model;
void doThing() {
final model$ = model;
if (model$ is Selectable) {
model$.doThing();
}
}
} |
This works though: class Selectable<T> {
void doThing() {}
}
class SelectionModel<T> {
}
class Controller<T> {
SelectionModel<T> model;
void doThing() {
final Object model$ = model;
if (model$ is Selectable) {
model$.doThing();
}
}
} |
this was considered this a bunch of times for strong mode/Dart 2, but we couldn't do it without a notion of interface types. There's probably another issue filed about this, though I can't find it at the moment. In the meantime, moving to area-language. It would be neat to see an improvement here in a future version of Dart. (or some alternate way of expressing this, like pattern matching) edit: to add more explanation, Dart 1/2 can only promote when the promoted type is a subtype of the original type. This is because otherwise, you'd lose access to some APIs. In the original example, if |
Thanks! That makes sense. That being said if someone writes: Model x;
if (x is SomethingElse) {
// OK to "lose" capabilities of Model.
} ... seems fine with me. |
Losing information is most likely going to be very annoying in practice. It might seem fine to you currently, because the examples you are looking at has that property, but in general it's not practical to forget that Unless we get (effectively) intersection types, I don't see a way to promote a variable of one type to an unrelated type. |
Could we do some actual investigation of this before claiming it? For example, we have a huge swath of both Flutter (/cc @Hixie) and internal code (/cc @davidmorgan) to look at, and we could see if indeed you are correct, or if the user always expects the new type, and is OK with losing capabilities. In the short term though, can we do something about the usability issue? Many users hit this and are very confused. A couple ideas:
class A {}
class B {}
void example(A a) {
// HINT: Variable "a" will not be promoted to type "B". Perform an upcast first.
if (a is B) {
}
} ... and if analyzer could offer an auto-fix, it would write, for you: class A {}
class B {}
void example(A a) {
final Object aUpcast = a;
if (aUpcast is B) {
// aUpcast is now a type of B
}
} If we deem this too disruptive, then maybe we could detect the following pattern: class A {}
class B {}
void example(A a) {
// HINT: Variable "a" will not be promoted to type "B". Perform an upcast first.
if (a is B) {
// This.
(a as B);
// Or this.
B b = a;
}
}
class A {}
class B {}
void example(A a) {
// I am not a language designer. But something that allows `is` to an unrelated type.
if (a is~ B) {
}
} |
it would be pretty easy to do something like: change Analyzer behavior to always promote, and then analyze all the code we have. Even if that run came back pretty good, it would still be a breaking change to the language. (Also, mutating the variable's type to an unrelated type would be pretty weird from a language design perspective.) Adding a hint would be fairly straightforward. Promotion is also disabled in other cases, such as if you mutate the variable in the block, or if you closed over it anywhere. So the hint could address that as well. But if you turn off implicit casts, it makes it more obvious when promotion failed. So perhaps pushing on implicit casts is the right way to go. |
…usion around upcasts (dart-lang/sdk#33932), is easily verbose. These helpers will also help deprecate `SelectionWithComposition`, because we can centralize these checks and delete part of the check as soon as I delete that class. (I am worried these checks are actually quite slow, since the result of them are not cached, and these likely occur in `*ngFor`, but I don't think I can solve that problem in this CL [or any one CL]). PiperOrigin-RevId: 205538946
The language team has considered a different mechanism that might be helpful here (and it might be helpful in other ways as well). Consider the original example (slightly modified): class Model<T> {}
class Interface<T> {
void hasMethod() {}
}
class Service {
Model model;
void tryThis() {
final model = this.model;
if (model is Interface interface) {
interface.hasMethod();
}
}
} There may be lots of things to sort out for this mechanism, but the basic idea is that |
Love that suggestion @eernstg! Huge 👍 from me. |
I have encountered this moment and it seems tremendously annoying. Does this case really needs to be followed by the spec rule of promotion? I mean are there none of solutions with minimum losses due to extra types promoted for variable scope? |
Naming
final model
tofinal modelX
works as expected.The text was updated successfully, but these errors were encountered: