-
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
Compile time error is missing (opted out of null safety) or confusing (opted in to null safety) for generic type inference #1751
Comments
I was also confused, but apparently As for your specific error, I think Dart is realizing that |
I agree, it looks like the inference is selecting the bottom type in some cases but I want to question if that is a good choice. I think it leads to a confusing error message for the author of the code because To me the more concerning cases are when there is no error at compile time because it seems that the compiler should be able to know that this code will never run without an error. |
I saw an error message once that said I forgot what the context was and I haven't been able to reproduce it since, but if that type of message can be applied here, it should make the
|
@nshahan wrote:
That's because an actual argument with static type So a type check is generated and no compile-time error is emitted. However, a dynamic error occurs when we execute the program and reach that point. So that's all working as intended. With null safety, assignability is made more strict: A type Inference chooses Considering the other example:
The inference chooses The CFE generates code for a type check as before. But the analyzer uses the special case of a literal (here: a literal string) to conclude more about the type of the actual argument: It has type exactly With null safety, the analysis again infers the type argument We may wish to specify exact types (cf. dart-lang/sdk#33307), but we should also be aware of the incomplete nature of this kind of analysis. Also, note that the very property of having an exact type is controversial, because it may be considered to be a violation of the encapsulation of the given object. |
I'm glad this broken code will be caught by a compile time error with null safe Dart. 🎉
Do you think there is room for a better error from the inference algorithm? Instead of working with the two constraints For comparison if we simply remove the assignment of the return value (and that constraint along with it) we get a much more helpful message. class A {}
T fn<T extends A>(T x) => x;
Null gn(Null x) => x;
void main() {
var s = 'hello';
fn(s);
}
|
@stereotype441, WDYT? @bwilkerson, do you think this error message could be made more helpful? |
@eernstg Yeah, I think there might be room for a better error from the inference algorithm here, at least for opted in code. Before I go into detail about my idea, let me just clarify exactly what is happening in this example: class A {}
T fn<T extends A>(T x) => x;
void main() {
var s = 'hello';
s = fn(s);
} Here's what's going on (let's consider opted in code for the moment):
Now, on to my idea. What if, after downwards inference assigns a type of
At this point, rather than just shortcutting upwards inference entirely because we already have a type from downwards inference, we could take the type from downwards inference and ask if it satisfies all the constraints. If it does, then there's nothing further to do. But if it doesn't, then we know we're in an error condition (*), so we can do deeper analysis. The deeper analysis is to look through the constraints to try to find two constraints that are mutually incompatible. There are three possibilities:
This would all be non-breaking because it would only happen in cases where we already are going to report a compile time error; we would just be replacing a confusing error about assignability with a more comprehensible message explaining that there was a type inference failure. (* I told a little lie above when I said "we know we're in an error condition" if the constraints can't be satisfied. The fact is, constraints from upwards inference are softer thatn constraints from downwards inference, because if the type violates them, but the compiler can insert implicit downcasts, we may still be able to compile without error. For opted in code, we only allow impicit downcasts from Anyhow, that's all fairly brainstormy. What do y'all think? I can try to flesh this out into something more concrete if there's interest. |
Great explanation, @stereotype441, thanks! As a somewhat general principle, I think it would indeed be helpful if we could stop early in every analysis process that turns out to end in failure (in particular, type inference). This might be achieved by running the analysis "as usual", using the results (the inferred types), detecting that there is a compile-time error in the inference result (but not reporting it to the user just yet), and then exploring some data produced by the analysis (or running a customized variant of the analysis), aiming to detect a minimal set of facts that suffice to produce the error. In the concrete case, I think it would be useful if we could tell the developer that the type parameter bound and the context type interact to give Perhaps we can just focus on this particular setup:
|
This point sound like a good idea to me. It seems that it would also help even in the case of legacy code that is opted out of null safety. |
Updated to correct my description of the null safety modes.
NOTE: These are simplified reproductions from a real world issue with a missing compile time error and subsequent runtime error that appeared in a google3 application.
Consider:
With weak null safetyWhen the file is opted out of null safety there is no compile time error (analyzer or CFE) and various errors at runtime in all backends. It seems like this should be a compile time error because there is no way typeString
could ever be passed as the type argument<T extends A>
.In sound modeWhen the file is opted into null safety this produces the compile time errorThe argument type 'String' can't be assigned to the parameter type 'Never'.
I find this message confusing, where did theNever
come from?In a related example:
Here the analyzer and CFE do not agree.
With weak null safetyWhen the file is opted out of null safety the analyzer produces the errorThe literal ''hello'' with type 'String' isn't of expected type 'Null'.
The CFE gives no error and the code causes runtime exceptions.With sound null safetyWhen the file is opted into null safety the analyzer and CFE agree with the compile time errorThe argument type 'String' can't be assigned to the parameter type 'Never'.
The errors are still somewhat confusing. Where did the
Null
andNever
come from? It appears there is some issue with the inference of the generic type argument that is either a bug or unspecified.@leafpetersen @eernstg @stereotype441 @johnniwinther
The text was updated successfully, but these errors were encountered: