-
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
common developer bad coding style issue of nullable list iteration using for-loop #59567
Comments
You need to consider the context type of various expressions. In particular, the context type for the iterable of a for-in statement is obtained from the declaration of the iteration variable (or from the existing iteration variable, if it is a for-in statement that doesn't declare this variable, as in You can explore this context type using examples like these ones: X whatever<X>() {
print('X is $X');
return <Never>[] as X;
}
void main() {
for (int _ in whatever()) {} // 'X is Iterable<int>'.
for (String _ in whatever()) {} // 'X is Iterable<String>'.
for (var _ in whatever()) {} // 'X is Iterable<Object?>'.
} This illustrates that if the type of the iteration variable is In 'Case 1', In 'Case 2', you're providing the type argument in 'Case 3' is like case 1 (the left-hand operand of And so on. The point is that the In general, if It is somewhat confusing because inference of One part is still surprising, though: @stereotype441, can you explain why |
The lints shouldn't play a big role here: They're concerned with having or omitting type annotations on local variable declarations, and the interesting question for this code is which type the variable gets when there is no type annotation (so it's not relevant to add a type annotation, or remove it). The lines marked |
By the way, you might want to use something like this in your analyzer:
language:
strict-raw-types: true
strict-inference: true
strict-casts: true
linter:
rules:
- avoid_dynamic_calls |
Thanks @eernstg for answering my question. Wait to see any changes in dart 3.6/3.7 release, since i just come across this seem dart core team already had a glance on this matter: Sadly, |
Right, it is always a difficult trade off when a language is using implicit criteria to make semantic choices. It's convenient, concise, perhaps even readable in the cases where the implicit effect is as expected and desired. But in the case where it does something unexpected and/or unwanted, it may not be obvious why this "wrong" thing happens, or how the code could be modified in order to do the "right" thing. In this case it's all about the context type. There is nothing special about being in a function body (which could mean any number of things, too) and being the iterable of a The However, it is still true that the type inference of So you don't have to be worried about magic |
I'll reopen this issue because of the confusion about @chloestefantsova and @stereotype441, can you explain why |
Sorry I missed this the first time around. I dug into this in the analyzer implementation, and as far as I can tell, it looks like the spec text in https://github.com/dart-lang/language/blob/main/resources/type-system/inference.md does not precisely match what was implemented. In particular, the spec says, in the section Grounded constraint solution for a type variable:
But the implementation does this (differences in bold): The grounded constraint solution for a type variable
See
This behaves identically except in exactly one circumstance: when both Which is a problem because the grounded constraint solution for a type variable is supposed to be a type, not a type schema (meaning it's not supposed to be, or contain, A later step in type inference (which I don't see documented anywhere in our spec documents), attempts to finish off type inference by running the "instantiate to bounds" algorithm to fill in any types that haven't been successfully inferred. That step is here:
The end result of all of this is that if a type parameter is completely unconstrained, type inference will give it a type based on its bound, and by default a type parameter's bound is So, considering the X whatever<X>() { ... }
...
for (var _ in whatever()) { ... } The type inference context for But in the var fruitList = ...; // Inferred type: `List<Fruit>`
...
for (var fruit in fruitList ?? []) { ... } Things are slightly different. The type inference context for |
Thanks, @stereotype441, that's a great analysis! It would surely be a breaking change to adjust the implemented algorithm such that it matches the specified algorithm because some types used to be It would be possible to bundle this change with a couple of other changes in the same topic area (e.g., dart-lang/language#433). WDYT? |
Nit: It's actually We haven't properly specified what a "default bound" means and does, and implementation treats "no bound" as a sepearate thing than defaulting to a default type, so the default bound might not matter. A place where it does matter is inside the declaration, where member access on a type variable is based on the bound or default bound, not instantiate-to-bounds: void foo<T, D extends dynamic>(T v1, D v2) {
v2.arglebargle(); // sure thing.
v1.arglebargle(); // Error: The method 'arglebargle' isn't defined for the class 'Object?'.
} But not instantiate to bounds 😢. |
Hi everyone,
I am have encounter a problem about a common coding style which potential trigger runtime exception.
But I cannot really tell it is related to dart-core or linter issue.
My dart version is 3.5.1
I have read the 3.7.x lint rule and test case file for omit_obvious_local_variable_types and specify_nonobvious_local_variable_types.
Ref: #58773
Here is the code:
Look at case 1, case 3 and case 6.
It seems that the for-loop declaration have its own type inference on null-coalesce logic.
The text was updated successfully, but these errors were encountered: