dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.11k stars 1.57k forks source link

Type inference failure with function types with type arguments #51590

Open abitofevrything opened 1 year ago

abitofevrything commented 1 year ago

The easiest way to explain this is with some code:

R callWithSomeTypeArgument<R>(R Function<T>() function) => function<String>();

void main() {
  // The argument type 'List<E> Function<E>({bool growable})' can't
  // be assigned to the parameter type 'List<Z0> Function<T>()'.
  final test = callWithSomeTypeArgument(List.empty);
  dynamic test2 = callWithSomeTypeArgument(List.empty);

  // All OK!
  List<dynamic> test3 = callWithSomeTypeArgument(List.empty);
}

This should work - I think - with the type of test being inferred as List<dynamic> since we have no information on what T will be. Instead it fails and the error message contains a reference to some Z0 type which is I assume generated for type inference.

lrhn commented 1 year ago

@stereotype441 Is this working as intended?

I'm guessing we treat no type and dynamic as imposing no context type, so R has to be found by upwards inference. We then try to solve List<E> Function<E>({...}) <: R Function<T>() for R and fail because we don't just want to guess Object? as a solution, and there is no List<X> that works.

abitofevrything commented 1 year ago

Doesn't List<dynamic> work here? We know the function returns a List because the return type is List<E> but we can't guess E. I feel that guessing dynamic for E is then ok in this case.

Edit: Is there some issue with the fact that E is the same type argument and return type whereas T and R are different?

lrhn commented 1 year ago

True, List<Object?> is also a solution. So maybe it's because the inference decides on List<T>, then figures out that it doesn't actually know T and gives up? (But now I'm just guessing.)

eernstg commented 1 year ago

As @lrhn mentioned, an invocation of callWithSomeTypeArgument needs inference for R such that List<X> Function<X>({bool growable}) <: R Function<X>().

When there is a context type C it will be used during downward inference, and it may or may not work. List<dynamic> works, and so does List<Object?>, Object, and every top type. Other context types will just cause a failure during type inference, because List<X> <: C isn't true for all X.

So I'd expect dynamic test2 = ... to succeed, using the type argument dynamic.

However, when there is no context type we'd generate constraints according to this rule.

I would expect the outcome to be List<Object?> <: R <: _, and the invocation would then type check without errors using the type argument List<Object?>.

@stereotype441, WDYT?

abitofevrything commented 1 year ago

After some more testing, I have found out that this issue only occurs in the analyzer. The frontend correctly infers the type argument and reports no errors, so commands like dart compile work fine - dart analyze continues to report the error though.

abitofevrything commented 1 year ago

A bit more investigation and the issue seems to arise here. This line incorrectly gathers that R should be a supertype of List<Z0> when it should gather that R is a supertype of List<E>, as it substitues Z0 for E in the check.

I'm not sure if this is a bug in the analyzer implementation or an issue in the spec itself.