Unexpected generic inference only on `async` functions
We recently ran into a weird situation where calling a method with a ?. operator yielded an unexpected inferred generic type in async functions. This snippet highlights the issue:
class Box {
final Object _value;
Box(this._value);
T read<T extends Object>() {
print(T);
return _value as T;
}
}
int? _readIntSync(Box? box) {
return box?.read(); // works: The `T` in `read` is inferred as `int`
}
Future<int?> _readIntAsync(Box? box) async {
return box?.read(); // does not work: The `T` in `read` is inferred as `Never`
}
void main() {
final box = Box(3);
_readIntSync(box); // goes through
_readIntAsync(box); // eventually throws a cast error
}
I would have expected type inference to infer T as int in both cases for the two methods. This is confusing to me since I don't see a compelling reason for inference to behave differently in the two cases.
Apologies if this is working as intended (in which case I'll probably file a language issue instead?).
In _readIntSync, the context type for box?.read() is int?. The type variable is T extends Object, so we need to satisfy T <: Object and T <: int?, which yields T == int.
In _readIntAsync, the context type for box?.read() is FutureOr<int?>, so we need to satisfy T <: Object and T <: FutureOr<int?>. But Object and FutureOr<int?> are not subtype related so we need to take a closer look at the function DOWN here, and we find the case DOWN(T1, T2) where OBJECT(T1) = /*skipping cases that don't apply*/ Never.
It is working as specified. We have had some proposals about improvements to this algorithm, and it is true that int would satisfy the constraints in this case. The difficulty is that NonNull(FutureOr<int?>) is FutureOr<int?> (see this section), so this particular combination of Object and FutureOr yields suboptimal results. But even if we can come up with improved rules it may not be easy to introduce them, because it is a breaking change to infer different type arguments, basically anywhere at all.
@leafpetersen, WDYT?