-
Notifications
You must be signed in to change notification settings - Fork 208
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 LUB be partial function for records? #2539
Comments
TL;DR: I'm split. I can see the issue of getting unusable LUB results, but restricting a solution to record types feels spurious. As I read the idea here, it's:
Basically, it's never OK to infer And then we have to recover from not finding a type where we expect one, typically by introducing a compile-time error. So UP((1, 1), (1, (2, 3)) would be The example, in the issue, of The argument for making records special is that you can more easily make typos in record literal field names, and nothing will stop you because all names are valid for some type. That is a concern. All other names in the program must be declared somewhere, but record literal named field names can be anything. The corresponding type is silently introduced along with the literal, no name is invalid. I hope our tools will eventually become smart enough that to suggest "You wrote '(fooo: 42) Maybe it won't be a problem then, but we're not there yet. Hmm. Take a "tagged value" list like The more I try to come up with examples, the harder it becomes to come up with an example where I want to infer I'm always worried when LUB starts inventing types. |
We used to have a similar discussion about the treatment of the conditional expression and other locations where an UP could yield const one = 1.5;
const two = "two";
void main() {
List<int> numbers = [one, two]; // Silently accepted back when we had implicit downcasts.
} This was a very serious footgun, which is now gone. However, this problem still exists in more subtle forms, and the cases involving record types seem to be perfectly good examples of the same issue. For example, this behavior of UP may give rise to unexpectedly imprecise type arguments ( In any case, the underlying cause is that we incur a massive loss of precision at an upcast from any record type to I think we could consider the same remedy which was proposed back then: Whenever UP yields the result Given that there is nothing unsound about these situations, I'd suggest that we use a lint or a hint to give developers this heads-up. |
Me too. It's good to explore the idea, but it feels weird to take record types out of LUB when really it's LUB in general that's the problem.
That's equally true of function types, which are also structural. Even worse, the test(bool b) {
var f = b ? (({required int foo}) => 1) : (({required int fooo}) => 2);
f(fooooo: 'nooooo');
}
main() {
test(false);
} This program compiles fine and then throws at runtime. At least when LUB gives you
I like this a lot. I could see us long term maybe trying to move away from LUB in general (to something like MAX()), but I think just doing it for record types would feel weird and not actually help that much. |
FWIW, I wish that I had done so for other types back when we designed strong mode (except that of course, the P0 with strong mode was accepting as much existing code as possible, so probably I couldn't have done so).
I only somewhat agree here, because functions have width subtyping, unlike records. So then two function types combine to form another actual function type, it can be useful. When they combine to form
Yeah, we could add something like this to the strict inference flag. I don't know whether we could do so for Function without it being too breaking, but we could definitely do this for Record. cc @srawlins this could be something that @kallentu could take on if we decide to do it. |
Great idea! We haven't implemented the checks, but there is the proposal for two types LUB-ing to |
I'm going to close this out. I think the answer is "no", and we'll just stick with the current specified LUB. I could definitely see us adding lints in the future to avoid LUB with weird pairs of types and in that case, records with different shapes are a prime candidate. |
In the discussion of issue #2528, @munificent made the point here that in general upper bounds on records with different shapes is probably more likely to indicate a programming error than a desired outcome, and that the result of using upper bound is likely to be confusing. I'm not 100% sure, but I think it would be workable to specify the upper bound meta-function to be a partial function for record types, which is undefined when the two inputs are record types with different shapes. We would have to adjust the various places where upper bounds are used in the meta-theory to make it a static error (usually an inference failure) if the upper bound did not exist. But at least for the obvious places I can think of this should work out. It does imply that constraint merging during inference also becomes a partial function, which is also something new - so there may be some implementation concerns, but that code is (or at least was) fairly local. Some examples:
Thoughts on this?
cc @munificent @lrhn @eernstg @jakemac53 @natebosch @stereotype441 @kallentu @scheglov @chloestefantsova
The text was updated successfully, but these errors were encountered: