Skip to content

Make inference know and use the special rules for num operators #597

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

Closed
lrhn opened this issue Sep 26, 2019 · 5 comments
Closed

Make inference know and use the special rules for num operators #597

lrhn opened this issue Sep 26, 2019 · 5 comments
Labels
feature Proposed language feature that solves one or more problems nnbd NNBD related issues

Comments

@lrhn
Copy link
Member

lrhn commented Sep 26, 2019

We have special rules for the static typing of num operators, so that 3 + 3 has static type int and 3 + 3.0 has static type double.
The type inference can use this for var x = 1 + 2.0;, but it can't use the constraint in the other direction. Example:

var numbers = [1, 2, 3];
int sum(int value, int other) => value + other;
var value = 0;
value = value + numbers.fold(0, sum);  // Fails.

This code looks like it should work. However, the + means that the numbers.fold(0, sum) occurs as the second operand of int.operator+ which has static type num. Hence, the context type is num so the type argument to fold becomes num (context type always wins), and then sum is not a valid argument because it does not accept num values as first argument.

However, we know (or should know) that for int + x to be int, the expected argument type is int, not num, so we could infer a context type of int in this case.
This works when the left operand has static type int, and the context type for the operation is either int or double, then the right operand must have the same type as the context type.

So, likewise double x = 2 + _; could infer that _ must have type double.
(That might actually be a little confusing because then int x = 0; double y = x + 1; would work but double z = 1 + x; would not, because of double literal coercion.)

This applies to all four operations where there is a link between the more precise special-cased result type and one of the operand types: int.+, int.-, int.*, and int.%.
We should probably also special-case int.remainder(...). I've hit issues when converting that to NNBD because the return type is always num.

This may become extra important after NNBD because we remove implicit downcasts, so expressions with context type num may no longer be used in places where an int is needed. Not all situations will fail as statically as this one. For example, if sum had been written as

T sum<T extends num>(T value, T other) => (value + other) as T;

then

value = value + numbers.fold(0, sum);

would currently be valid, but would stop being valid after NNBD because the context type of num would cause the sum to be instantiated at num, and the result of int + num is not assignable to int.

This has already caused problems with code written while += did not introduce the correct context type for the second operand. The code had to be rewritten when the type inference was fixed.

@leafpetersen @eernstg @johnniwinther @stereotype441

@lrhn lrhn added feature Proposed language feature that solves one or more problems nnbd NNBD related issues labels Sep 26, 2019
@eernstg
Copy link
Member

eernstg commented Sep 26, 2019

If we introduce something like case functions which is a more general mechanism with a similar typing structure as the int special casing of operator +/-/*/%, it would be nice to have done something which can be generalized.

The int special case allows for those operators to have types int Function(int) and double Function(double), along with the type num Function(num) which is received by int from the superinterface num. So we can get a result of type int when the argument has static type int and a result of type double when the argument has static type double; only an argument of static type num yields the weaker result type num.

It makes sense to say that type inference should use T Function(T) for these operators of int when the context type is some T in {int, double, num}. This rule coincides with this one (at least for context int and double, and I'm assuming the num case arises by default):

This works when the left operand ...

.. but is specified in terms of the member signatures, in order to elucidate the underlying structure.

When the set of available typings for a given function does not have such a clear connection from the return type to the choice of a typing for the function, we might be able to use the types of actual arguments to make the choice. However, we probably don't want to run inference with all choices (for an expression containing invocations of multiple functions with such types: all combinations of choices) in order to select the best one.

So it would be really nice if we could find a foundation for how to make this mechanism generalizable, rather than just piling special treatments on top of other special treatments.

@lrhn
Copy link
Member Author

lrhn commented Sep 27, 2019

We need the "no implicit downcast" requirement (and or a combination of classees being abstract and sealed) before we can generally use a return-type/argument-type connection to enforce a type context on the argument.

The reason it works for num is that num itself is abstract and sealed, so for int a = 1 + expr; we know that expr must return a num, int or double (because the type hierarchy is sealed) and that it can't be num because num is abstract. So, we don't have to worry that it might return something with static type num which can be successfully downcast to int - the only possible class with that property is int itself. (Except on the web, where 1.5 + 2.5 is an int at runtime).

@lrhn
Copy link
Member Author

lrhn commented Dec 6, 2019

One thought: What if we had a "weak context type" or "context type hint" which could be applied to the static type analysis of an expression, but which does not require the expression to satisfy the type like a proper context type?
Then we could analyse e1 + e2 with context type double by first analysing e1 with double as context type hint. If it ends up as double or int, then we analyze e2 with double as context type hint.

The hint has no effect unless the expression uses the context type.
It's also something that could be useful for as casts, so [] as List<int> would allow it to infer <int>[] instead of just failing at run-time.

@leafpetersen
Copy link
Member

Is this superseded by the update num rules that we landed?

@lrhn
Copy link
Member Author

lrhn commented Sep 11, 2020

Yes, this is done now.

@lrhn lrhn closed this as completed Sep 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems nnbd NNBD related issues
Projects
None yet
Development

No branches or pull requests

3 participants