Dart incorrectly infers type as `dynamic` when a generic function is given a value

The following example showcases the issue (also available on DartPad).

abstract class Provider<T> {
  T get bar;
}

class StringProvider extends Provider<String> {
  String get bar => 'bar';
}

class NumberProvider extends Provider<int> {
  int get bar => 42;
}

class Foo {
  final T Function<T>(Provider<T> provider) provide;

  const Foo({ this.provide });
}

void main() {
  final foo = Foo(provide: (provider) => provider.bar);

  String strValue = foo.provide(StringProvider()); // should be 'bar'
  int numValue = foo.provide(NumberProvider()); // should be 42
}

The following line:

final foo = Foo(provide: (provider) => provider.bar);

Raises the following error:

Error: The argument type 'dynamic Function(Provider<dynamic>)' can't be assigned to the parameter type 'T Function<T>(Provider<T>)'.

I believe that this behavior is incorrect, since Dart can actually infer the correct types on the calling site, meaning that foo.provide(StringProvider()) does indeed return a String and using NumberProvider returns an integer as expected.

The type system can be helped out with adding just a tiny bit of extra information when defining the value, but this should probably be done automatically and not require suck a “hack”.

final foo = Foo(provide: <int>(provider) => provider.bar);

Any type except dynamic is allowed here in the place of int, but something has to be given for Dart to stop complaining.

The fixed version is also available on DartPad.

My Dart SDK version is 2.3.0-dev.0.1.


For the StackOverflow question that originated this issue report, please click here.

Apologies if this is a duplicate issue. I’ve read through a couple of (possibly) related reports, but none seemed to match my exact case. In case I’ve missed something, please feel free to consolidate.

Author: Fantashit

1 thought on “Dart incorrectly infers type as `dynamic` when a generic function is given a value

  1. The story is slightly longer. The original fix you mentioned was using <int>(provider) => provider.bar. That denotes a generic function accepting a type argument named int (not a good choice of name, but also not an error!). The argument type and return type are subject to inference, and the resulting type is int Function<int>(Provider<int>), which, of course, ought to be written like T Function<T>(Provider<T>).

    So the fact that you declare a type parameter makes the function generic, and the fact that you’re passing that function literal to a receiver that wants a function of type S Function<S>(Provider<S>) helps type inference to fill in the blanks. So “it just works” as soon as you add <SomeName>. 😉

    However, you rely on inference for that, so it won’t work if you don’t have that context:

    main() {
      var f = <T>(provider) => provider.bar;
      print(f.runtimeType); // Prints '<T1>(dynamic) => dynamic'.
    }

    It would not work to pass a function of that type to the constructor of a new Foo, so it’s crucial that you’re passing the function literal directly as a constructor argument, such that it’s subject to type inference relative to the type T Function<T>(Provider<T>).

    In any case, it’s all working as intended, so we should close the issue.

Comments are closed.