I'm trying to define a callback function that needs to accept a generic parameter and return a value of the same type. Keep in mind that the following example is an oversimplified version of what I actually need.
final T Function<T>(T value) self = (value) => value
This results in the following error, that I can't seem to get rid of.
The argument type '(dynamic) → dynamic' can't be assigned to the parameter type '<T>(T) → T'
dart(argument_type_not_assignable)
The only thing that seems to work is to give the value a type, but that defeats the purpose of using a type parameter in the first place.
final T Function<T>(T value) same = <String>(value) => value;
I need it to be generic so that the caller can pass the type it expects in return. I also need it to be stored in a variable, so I can pass it around as a callback function.
If this isn't directly possible, do you know of any workarounds? Thank you in advance.
Here's a more complete example if the requirements aren't clear.
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 });
}
test() {
final foo = Foo(provide: (provider) => provider.bar);
String strValue = foo.provide(StringProvider()); // should be 'bar'
int numValue = foo.provide(NumberProvider()); // should be 42
}
The annoying thing is that Dart actually understands that foo.provide(StringProvider())
will return a string and that using NumberProvider
will indeed return an integer, and yet, the error is still risen for the line where the variable is actually given a value.
final foo = Foo(provide: (provider) => provider.bar);
The argument type '(dynamic) → dynamic' can't be assigned to the parameter type '<T>(Provider<T>) → T'
It turns out that I can cheat the type checker by giving any concrete type when defining the value. Note that dynamic
is not allowed, but anything else goes.
final foo = Foo(provide: <int>(provider) => provider.bar);
This both gets rid of the error and allows for the provide
method to return the correct type when called.
In conclusion, this seems like a simple shortcoming of the type checker, not something that's actually impossible or difficult to achieve using the already existing language features. I will be raising an issue on the language's GitHub repository to allow for further investigation and discussion.
Update #1: the issue has been opened on GitHub.
Update #2: the issue has been resolved, and it turns out that the behavior is by design. Quoting Erik Ernst from the SDK team:
Try this:
final foo = Foo(provide: <T>(Provider<T> provider) => provider.bar);
!The problem is that you're passing a non-generic function to the Foo constructor, and you should pass a generic function. There is no subtype relationship between a generic function type and a non-generic function type, so according to the type checker you might as well pass a String, and that's the reason for the 'can't be assigned to' message.
It turns out that simply adding <T>
before the parameter list (instead of int
as in the original workaround) solved the issue.
final foo = Foo(provide: <T>(provider) => provider.bar);
This forces Dart to understand that provider is of type Provider<T>
and that the method will return a value of type T
, saving us from using a concrete type and still getting rid of the error.