-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Analyzer won't promote type of "this" #32120
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
Comments
It won't even work if you first assign it to another variable, so I guess it's not specific to "this": class A<T> {
void test() {
final A<T> a = this;
if (a is A<double>) {
final A<double> self = a; // "A value of type 'A<T>' can't be assigned to a variable of type 'A<double>'"
}
}
} |
We only do promotion when you test against a subtype of the static type of the thing being tested. This guarantees you don't "lose" capabilities via promotion. We don't know that If you assign to a variable of type There does seem to be something slightly different about testing against a different instance of the same class. Something we could consider for future improvements. Another possibility (which has some appeal) is to have a separate promoting instance check that always promotes (or perhaps a way to suppress promotion, since you almost always want it). Related: #25260 |
I don't really see what I could be losing here. What can I do with an FWIW, and I think there's another open bug about this, what I really want from type promotion is unions. If I have a variable |
See example below. As I said though, something does feel different here when you're just converting between instances of the same class - you don't lose methods, you just lose assignment.
Intersections actually, but yes. You want the type system to know that class A<T> {
void bar(T y) { ... }
void test(T x) {
if (this is A<double>) {
bar(x); // Error, T is not assignable to double
}
}
} |
if
Ah, right, yes. |
I think the core language design issue here is the following. We do not recommend the following kind of testing on class A {
void foo() {
print("We're doing this every time.");
if (this is B) this.bar();
}
}
class B extends A {
void bar() { print("This is a special thing we only do in B"); }
} The preferred way to do the same thing is to use OO dispatch to obtain the desired control flow rather than an explicit type test on class A {
foo() { print("We're doing this every time."); }
}
class B extends A {
foo() { super(); bar(); }
bar() { print("This is a special thing we only do in B"); }
} We could also use the template method design pattern which will handle situations where we want to inject special behavior in the middle of a method body, or more than once, etc., but the point is that we don't make control flow decisions based on the type of However, in some cases there is no non-trivial superinterface relationship because the type test is concerned with a type argument; so we may test Some languages (e.g., Cecil) support "conditional method implementations": class A<T> {
void test() when T extends double {
// In this scope it is known that `T <: double` and `this: A<T>`.
final A<double> self = this;
...
}
void test() otherwise {
// Here we only know that `T <: Object` and `this: A<T>`.
...
}
} A mechanism like this would allow us to include at least some of those situations where we test type arguments, and it's actually very cool when it is used to say things like "a list can be sorted iff its element type can be sorted" (so But I think that this whole language design area tends to invite designs that are less idiomatically OO, and hence the whole community may end up having higher quality code if we don't go down that path. In summary, it might be cool to support promotion on |
@eernstg I totally agree with what you say above. The specific case I'm dealing with is that we have found that we desperately need a way to chain from an I tried a variety of solutions like having an explicit For now I've added |
I don't have any clever ideas right now, but maybe the |
@InMatrix can talk about this more, but my understanding is that the problem is which object the method is on, not what the method is called (we can't really rename the method anyway, that would be a breaking change). Specifically, what people seem to do is figure out they need an |
There's a bit more context in this bug: flutter/flutter#14482 We'd like to achieve two things by placing the method on the AnimationController object: 1) increasing the discoverability of the method, since the controller is likely to be defined first, and 2) better communicating the controller-controllee relationship between the AnimationController and the resulting Animation. |
OK,
must be the main thing. It does make a difference whether Anyway, I can see from flutter/flutter#14482 that the story is so much longer that I should avoid jumping to conclusions, so the best solution at this point may then well be the class Animation<T> {
void drive(Tween tween) when T extends double {...}
...
}
main() {
Tween tween;
Animation<double> doubleAnimation;
Animation<num> numAnimation;
doubleAnimation.drive(tween); // OK.
numAnimation.drive(tween); // Error: There is no `drive` method on an `Animation<num>`
} |
I see two issues here:
As stated, the workaround for the original problem, on both counts, would be: class A<T> {
void test() {
final A<Object> self = this;
if (self is A<double>) {
... use self when you need double, this when you need T...
}
}
} |
I'm closing this out in favor of language issue dart-lang/language#1397 (which I'm currently considering for possible inclusion into language feature |
Consider this perfectly reasonable code:
The analyzer complains:
...despite the fact that we know for a fact that
this
is anA<double>
where it is assigned to the variable of typeA<double>
.The text was updated successfully, but these errors were encountered: