-
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
Flow analysis unnecessarily demotes for-each loop variables #42653
Comments
@johnniwinther I wasn't sure whether to classify this as area-front-end or area-analyzer since it's in _fe_analyzer_shared. I'm happy to work on this. |
I'm using epics more that area now so it's ok for me that it's area-front-end given it has the (now shared) Flow Analysis epic. |
@stereotype441 is this still planned? |
I'm still hoping to get to this. There are a few related bugs with the treatment of "for each" loop variables that can probably all be fixed at once. But time is running short so I'm not 100% certain I will get to it before the beta release. |
Looking into this more deeply, the problem is in the way type inference interacts with flow analysis. When type inference visits the loop I think what should happen instead is that the promoted type of void f(List<Object> x) {
if (x is List<int>) {
x.first.isEven;
x = [0];
x.first.isEven;
}
} there would have been no error, because we would have used the promoted type @leafpetersen does this seem reasonable to you? |
I think it's already nearly a code smell to use an existing variable as the "counting" variable of a In particular, I think that a local declaration like One obvious question to ask is what we will do about demotion: void forEachWithoutDeclLoopVar(num x) {
if (x is int) {
x.isEven; // Verify that promotion occurred.
for (x in [0]) {
... // Lots of stuff, then 50 lines later:
x = 0.5; // `x` demoted to `num`; does this imply that `[0]` is `<num>[0]`?
}
}
} Another inconvenient corner is promoted type variables: void forEachWithoutDeclLoopVar<X extends num>(X x) {
if (x is int) {
x.isEven; // Justified by promotion to `X & int`.
for (x in [0]) { // Error: `int` not assignable to `X`.
x.isEven; // Error: `num` does not have an `isEven`.
}
}
} I believe the use of the promoted type as the basis for the context for I would actually recommend avoiding this tar pit as much as possible. I don't know about the breakage, but we might be able to say that an existing variable simply doesn't provide a context type for a Keeping the current behavior where the context type is based on the declared type of the existing variable seems slightly inconsistent (which is presumably the reason why this issue was created in the first place), but I still think that it's less confusing than the approach where we use the promoted type. |
Agreed. I kinda wish the language didn't allow this at all.
My preference (and what I've implemented in https://dart-review.googlesource.com/c/sdk/+/162625) is that the type of T id1 = [0];
var id2 = id1.iterator;
while (id2.moveNext()) {
x = id2.current;
{
... // Lots of stuff, then 50 lines later:
x = 0.5; // `x` demoted to `num`; does this imply that `[0]` is `<num>[0]`?
}
} We already have an inference rule for explicit assignments that uses the promoted type as the context, so if the user had written this code directly, the context for
True, but we would have had precisely the same problem if we'd used the declared type of
Do you have a motivating example for this idea? I don't believe it helps your promoted type variables example, because if we say that |
T id1 = [0];
var id2 = id1.iterator;
while (id2.moveNext()) {
x = id2.current;
{
... // Lots of stuff, then 50 lines later:
x = 0.5; // `x` demoted to `num`; does this imply that `[0]` is `<num>[0]`?
}
} Right; the language specification doesn't say anything about the inference step on the iterable, but I don't think it will be hard to come up with a handful of context types (with cases The models are now easy to express: We can use the promoted type of I'm not quite sure which model wins on consistency (we don't have any other mechanism in Dart where we are using exactly the same steps to find a context type), but it's probably not a big issue in practice. (Because developers will be nice and avoid writing code using the case
I think we might want to make the element type of the iterable a type of interest: If the developer uses an iterable of void forEachWithoutDeclLoopVar(num x) {
x = 3.16; // The treatment of `x` doesn't help us getting to `int`.
for (x in <int>[0]) { // But we _will_ assign int values to `x` in the loop.
x.isEven; // So let's promote.
}
} If we take the original example then the effect would simply be that the assignment does not demote the variable, which means that we avoid contradicting the type which was flowing from void forEachWithoutDeclLoopVar(num x) {
if (x is int) {
x.isEven; // Verify that promotion occurred.
for (x in [0]) { // Assume this is inferred as `<int>[0]`.
// Implicit assignment "x = id2.current" preserves promotion.
x.isEven; // OK.
}
}
} The promoted type
That is true. My point was that the promoted type didn't help us, even though it was good enough to allow I still suspect that we can avoid some confusion if we simply say that
The motivation is not a specific example, it's the underlying principle: If we do not provide a context type for the iterable in the case This makes the code more stable, and it may force developers to be a bit more explicit on the type of the iterable (they may need to pass a type argument explicitly), and in this case I actually think that's a plus. |
I don't see a very good motivation for ignoring the promoted type here. In all other cases that I can think of where we use a variable, we use the promoted type of a variable to generate the context, it feels very inconsistent to ignore it here. Given that this is trivial to implement (in fact, already implemented) I'm in favor. |
Using the promoted type is more consistent, and we can lint the use of an existing variable in the first place. |
The following code is currently rejected by flow analysis:
It seems like we should be able to do better than this, because
[0]
should be inferred as<int>[0]
, and therefore the implicit assignment tox
in the for-each loop should not undo the promotion.The text was updated successfully, but these errors were encountered: