-
Notifications
You must be signed in to change notification settings - Fork 205
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
Should type inference infer nullable types where necessary? #630
Comments
Interesting suggestion. It seems reasonable to me. |
@lrhn @munificent @eernstg What do you think of this? On further consideration, I have some concerns about the usability of this. On the one hand, it's nice if the user can write: void foo(Object Function(int) f) {
print(f(3));
}
f( ([i]) => i ?? 3); // inferred as `int Function([int?])
} but on the other hand it feels like the error message you get for this code might be surprising: void foo(Object Function(int) f) {
print(f(3));
}
f( ([i]) => i.isEven); // Error, `isEven` can't be called on a nullable type.
} |
If we are inferring the type of an optional parameter with no default-value, then we should infer a nullable type. It must be nullable, and being contravariant, making the context type's parameter type nullable will still be valid. I have no problem with the error message above. The code writes I do think readers will quickly internalize an association like:
Exceptions from that can exist, but they likely frighten and confuse users. |
If the code were: main() {
void Function({required int param}) f = ({param}) {};
} Would we infer Even when extra clever rules like this do the right thing, it's still another rule to learn and sometimes it's better for users for things to be simple and explicit even if the result is more verbose. |
We could have a relatively simple rule about inference of the type annotation on a formal parameter p of a function literal: If the context type specifies that p has type But I tend to support @munificent here:
After all, even though it's not a hideously complex rule, it is a new concept that inference would "correct" a parameter type obtained from the context. Developers would need to have that extra step in mind whenever they are trying to understand what any particular function literal might mean, for instance in the case where there is an error in the body which arises because of that implicitly added But if the developer is reasoning about the code in terms of p having type annotation Moreover, this situation will inherently imply that information is lost: The parameter is optional in the function literal, but it is required in the context type, so the parameter is guaranteed to be provided in all calls based on the context type. So we are protecting the function literal from a behavior (omitting the argument) that the caller can't do based on the given type, there would have to be a dynamic type test: void foo(Object Function(int) f) {
if (f is Object Function([Never]) {
print(f()); // Only now can we omit the argument.
}
}
void main() {
foo(([i]) => i.isEven); // Error, `isEven` can't be called on a nullable type.
} Of course, it isn't sound to assume that no such dynamic type test will occur. But the fact that the optionality of the parameter is erased by the context type makes me think that it isn't worth the trouble to make it work. It seems more useful to flag the conflict and ask the developer to resolve it, that is, to infer parameter type It would be helpful if the diagnostic message about this situation could indicate that one way to eliminate the error is to make the parameter required. |
There are two possible approaches. I don't see adding a default value or making the parameter required as options. The body doesn't matter, we don't use it for parameter inference, so it's not important whether the body contains an If we infer So, inferring So, I think we should infer a nullable type, and I don't think it's confusing to a user if we point out that this optional parameter is nullable. For the main() {
void Function({required int param}) f = ({param}) {};
} we will not infer |
Created #938 to discuss the required case. I don't see a firm consensus in the discussion above. @munificent and @eernstg seem somewhat opposed, @lrhn on the fence? Further thoughts on this? |
As usual, I'd like to emphasize readability and comprehensibility. If we allow a program to be a few characters shorter it gets easier to write that program, but this may be a bad trade-off if it's harder to read what it does. Presumably, we'll be reading any given snippet of code more times than we'll write/update it. void foo(Object Function(int) f) => print(f(3));
void main() {
foo(([i]) => i.isEven); // Error, `isEven` can't be called on a nullable type.
} In particular, if we infer a nullable type annotation for a non-type reason (like: the parameter If the If So I tend to think that inference of |
I agree with Erik. I don't think a special rule here carries its weight. In particular, I'm struggling to come up with a real-world example of reasonable code where this would come into play. As I understand it, this only occurs when you have downwards inference of a lambda expression. So you're creating a lambda that is immediately bound to either a parameter or variable. In the type of that variable, the parameter is not optional. You have no other references to the lambda to invoke it through. Why would anyone ever bother to mark the parameter optional then? |
This is not planned. |
Here's some weird code I was playing with today:
If I enable NNBD, the analyzer reports the following error:
This is happening because the type of the parameter
i
is being inferred from the context typevoid Function(int)
, so its type is non-nullableint
. However, sincei
is an optional parameter, it must have a nullable type.Obviously the user could work around this by giving an explicit type:
But I wonder whether it would be worth modifying the type inference algorithm so that in contexts where a nullable type is required.
The text was updated successfully, but these errors were encountered: