-
Notifications
You must be signed in to change notification settings - Fork 213
[NNBD] Type promotion fails for variable uses in anonymous functions #1536
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
Yeah, this is a known limitation of type promotion. When it sees an anonymous function, it makes a pretty conservative assumption about what might happen to any variables that anonymous function reads: it reasons that if the variable is ever written to, then it's possible that the variable's promotion will be invalidated by a write sometime between the time it's created and the time it's called. So to be on the safe side, it invalidates the type promotion. Obviously in this example, that's overly conservative, for three reasons: (a) because the anonymous function is called exactly once, immediately after it's created, (b) because the only assignment to I believe we have some other bugs in the language repo discussing similar issues. I'll try to dig them up and link them here, so that we remember to think about this case when we think about future improvements to type promotion. |
FWIW, you can work around this limitation by creating a temporary variable that's never written to: int foo({int? arg}) {
arg = 0;
var tmp = arg;
return (() => tmp)();
} |
This is a pretty rough thing to have to work around—Dart heavily encourages the use of anonymous functions for things like collection manipulation, so added friction passing values in produces a lot of pain.
For what it's worth, (a) here is basically a coincidence—in the actual code in which I'm seeing this issue, there's no way for the analyzer to know that the callback is only called once. But (b) and (c) are both usually true for these situations. |
FYI the concept used in Java and Kotlin of effectively final might be useful. For example, in Kotlin, this works fine: fun main() {
println(foo(true)())
}
fun foo(b: Boolean): () -> Int {
var n: Number
n = if (b) 1 else 0.3
if (n is Int) {
return { n }
}
return { 0 }
} The closure can only accept If you re-assign fun foo(b: Boolean): () -> Int {
var n: Number
n = if (b) 1 else 0.3
if (n is Int) {
return { n } // Smart cast to 'Int' is impossible, because 'n' is a local variable that is captured by a changing closure
}
n = 4
return { 0 }
} Dart should use the same behaviour IMHO. |
BTW In Kotlin, unlike Java, the variable can even be re-assigned as long as it is never re-assigned AFTER the closure captures it (and I didn't use an argument as in the Dart example because in Kotlin, arguments are always final). |
I think implementing this one must be the easiest among the three reasons:
It's easier to explain away the two other cases with technical limitations but this one is a bit surprising. FWIW I recently came across this in this example: https://dartpad.dev/?id=fc440d205d8855f5ebbc919a12eec8ea |
Note that this issue is regularly reported as a bug; it seems that a lot of folks intuitively expect flow analysis to be smart enough to handle this correctly. Here is the latest report: dart-lang/sdk#50573. |
I added some ideas to dart-lang/sdk#50573 about a possible implementation strategy. Copying them here so that all the discussion is in one place:
|
Another pattern I keep seeing is what I'd call "monotonic" variables. It might be assigned in several places, including inside closures, but it's never assigned something that would demote it. It's usually around nullability Example: https://www.reddit.com/r/dartlang/comments/13slo75/why_is_there_no_control_flow_type_analysis_for/ Would it be possible do detect that a nullable variable is never assigned a nullable value, even if it might be assigned inside closures, and if so, allow promotion to work. void foo(int? x) {
bar() {
print(x ??= 42);
}
baz() {
if (x != null) {
print(x.toRadixString(16);
}
}
baz();
bar();
baz();
} Here we'd normally give up on promoting It's probably not trivial, because of self references. Will |
Unfortunately doing this would require a pretty substantial overhaul of how type inference works in the analyzer and the CFE. The key issue is that the design of type inference (and hence flow analysis) in both codebases assumes that the code is visited exactly once, in source order*. In order to detect that a nullable variable is never assigned a nullable value, we would need to visit all writes to the variable before any possibly-promoted reads, and the chances of that order being compatible with source order are pretty low (especially where closures are involved!) (*Minor exception: |
Could the analyser detect if the closure creation happens on Something else that got me thinking was, could we, in these cases where the variable is from outside the scope of the closure, add a better error message? At least for now, so people can understand this limitation. EditComing from https://dart-review.googlesource.com/c/sdk/+/387862 on the review for |
The following function:
Produces this error:
Despite the fact that
arg
will clearly always be non-null when it's returned. There's not even a hypothetical risk thatarg
will be reassigned tonull
between the function being created and being invoked, since the non-nullable assignment appears before the function is created and no nullable assignments exist anywhere.The text was updated successfully, but these errors were encountered: