Consider the following example:
typedef Action<T> = void Function(T arg);
void execute<T>(T arg, Action<T> action) => action(arg);
void main() {
execute(42, (value) => print(value.bar));
}
I would expect the Dart Analyzer to fail at value.bar since T could be inferred to number. Instead, Dart does not infer T to number, but falls back to dynamic.
Is there a way to parameter-driven type-inference in Dart?
If not, is there a list a way to prohibit defaulting to dynamic?
EDIT:
Equivalent example in TypeScript:
type Action<T> = (arg: T) => void;
function execute<T>(arg: T, action: Action<T>) {
return action(arg);
}
function main() {
execute(42, (value) => console.log(value.bar));
}
yields error
Property 'bar' does not exist on type 'number'.
Equivalent example in Kotlin:
typealias Action<T> = (arg: T) -> Unit;
fun<T> execute(arg: T, action: Action<T>) = action(arg);
fun main() {
execute(42) { value -> print(value.bar) };
}
yields error
Unresolved reference: bar
I investigated this issue and discovered that if you simply write
execute(42, (value) => print(value));
then the type will be correct inferred to be int. I believe when attempting to infer a type, the analyzer is looking to the find the smallest type the matches both parameters. It can infer from the first parameter that it must be at least an int. However, when it analyzes the lambda on the second parameter, it infers that value cannot be and int because int does not have bar so it falls back to dynamic as the only type that that type parameter can be.
If you think this is not the way the type inference should work, you should submit an issue to the sdk: https://github.com/dart-lang/sdk/issues
Related
I have a class:
class Foo<T> {
final void Function(T) bar;
Foo(T t, {required this.bar});
}
and I'm passing int value so that T can be inferred as int, but the issue is in the following code, t is of type Object? and not int. Why is that so?
Foo(0, bar: (t) {
// `t` is of type `Object?`
});
Note: I'm not looking for a solution, which is to use Foo<int>(0, bar: ...). I want to know the reason why t is not inferred correctly
The situation you describe should now be fixed as of Dart 2.18.
Original answer for earlier versions of Dart
Your problem can be more clearly observed with:
class Foo<T> {
final void Function(T) bar;
Foo(T t, {required this.bar});
}
Type staticType<T>(T object) => T;
void main() {
var foo = Foo(0, bar: (x) => print(staticType(x)));
print(staticType(foo)); // Prints: Foo<int>
print(staticType(foo.bar)); // Prints: (int) => void
print(foo.bar.runtimeType); // Prints: (Object?) => void
foo.bar(42); // Prints: Object?
}
I'm not an expert on the inference rules, but (prior to Dart 2.18) inference either could flow top-down (from function to arguments) or bottom-up (from arguments to function). Since you don't supply an explicit type parameter when constructing Foo, Foo's generic type argument T must be inferred from its arguments. However, the argument type to the anonymous function also isn't specified, so it's assumed to be Object?.
For Foo's T to be inferred as int and for the anonymous function to be inferred to be (int) => void would require inference to flow from the positional argument up to the function and back down to the named argument, which Dart (prior to 2.18) did not do. You thus instead ended up with the (Object?) => void anonymous function being implicitly cast to (int) => void.
What I'm trying to do
Given the following Node:
class Node<T> {
Node(this.value);
T value;
Node? child;
// TODO: add `visit` method
#override
String toString() => value.toString();
}
I'd like to add a visit method that will perform some action on the value of each node and its child recursively. Then I could do something like this:
void main() {
final root = Node(1);
root.child = Node(2);
root.child!.child = Node(3);
// one of these
root.visit(print);
root.visit((value) => print(value));
// 1
// 2
// 3
}
Naive solution
If I do the following, it works:
void visit(Function action) {
action(value);
child?.visit(action);
}
Problems with the naive solution
However, the value in this statement is inferred to be dynamic:
root.visit((value) => print(value));
I'd like to infer it to be the same type as the Node's generic T type.
Additionally, the compiler allows the following, which causes a runtime crash:
root.visit(() => 42);
I'd like that to be a compile-time error.
Attempted solution 1
If I change visit to the following:
void visit(Function(T value) action) {
action(value);
child?.visit(action(value));
}
Everything looks good at compiletime:
root.visit(print); // OK
root.visit((value) => print(value)); // OK
root.visit(() => 42); // error
But if I comment out that last one and run the code on either of the first two then I'll get the following runtime error:
Unhandled exception:
type 'Null' is not a subtype of type '(dynamic) => dynamic'
I'm not exactly sure what that means.
Attempted solution 2
Added void:
void visit(void Function(T value) action) {
action(value);
child?.visit(action(value)); // error
}
This expression has a type of 'void' so its value can't be used.
Try checking to see if you're using the correct API; there might be a function or call that returns void you didn't expect. Also check type parameters and variables which might also be void. (dartuse_of_void_result)
Attempted solution 3
This one was just a stab in the dark:
void visit(void Function<T>(T value) action) {
action(value);
child?.visit(action);
}
The visit method seems to compile but calling it as before gives compile time errors:
root.visit(print); // error
root.visit((value) => print(value)); // error
The errors read:
The argument type 'void Function(Object?)' can't be assigned to the parameter type 'void Function(T)'. (dartargument_type_not_assignable)
Related questions
These questions seem related but I couldn't figure out how to extract a solution from them:
How to create a generic method in Dart?
Callback with generic type parameter in Dart
Dart: Type error when overrding generic function in abstract class
How to check and cast generic parameters in Dart?
Dart passing generic Function<T>(T t) seems to require cast, all other ways signatures don't match
How can I solve the problem?
Thank you to #jamesdlin in the comments for solving the problem.
You need to set the generic type for the child as Node<T>. Then you can specify the method signature as void visit(Function(T value) action) and pass action itself on to the child.
Here is the full example:
void main() {
final root = Node(1);
root.child = Node(2);
root.child!.child = Node(3);
// one of these
root.visit(print);
root.visit((value) => print(value)); // value inferred as int
// root.visit(() => 42); // compile-time error
// 1
// 2
// 3
}
class Node<T> {
Node(this.value);
T value;
Node<T>? child;
void visit(Function(T value) action) {
action(value);
child?.visit(action);
}
#override
String toString() => value.toString();
}
Let's say I had some function that takes a generic type as an argument. How do I check within that function whether the generic type argument is nullable or not? I want do something like this:
void func<T>() {
print(T is nullable);
}
void main(){
func<int>(); //prints false
func<int?>(); //prints true
}
All I can think of to do is to check if T.toString() ends with a ? which is very hacky.
Try:
bool isNullable<T>() => null is T;
The accepted answer really just checks if the type can be null. It doesn't care about the type that you are operating the null operator on.
If you want to check if a type is a specific nullable type, a.k.a if you want to check if a type is specifically one of type DateTime? and not String?, you can't do this in dart via T == DateTime? as this conflicts with ternary operator syntax.
However, since dart allows passing nullable types into generic arguments, it's possible to it like so:
bool isType<T, Y>() => T == Y;
isType<T, DateTime?>() works.
I have come across this a lot. And #Irn method works, except for when T is type Type (when using generics), it will always return saying that T is not null.
I needed to test the actual type of Type not Type its self.
This is what I have that is working really well for me.
bool get isNullable {
try {
// throws an exception if T is not nullable
final value = null as T;
return true;
} catch (_) {
return false;
}
}
It creates a new List instance to verify if it's type is nullable or not by using the is operator which supports inheritance:
bool isNullable<T>() => <T?>[] is List<T>;
Consider the following code:
void printInt(int i) => print(i);
void printString(String s) => print(s);
void printSomething(Object o) {
final printer = {
int: printInt,
String: printString,
}[o.runtimeType];
print('Printer is $printer');
printer(o);
}
void main() => printSomething('Hello');
It prints the correct printString function and then crashes with the following exception:
TypeError: "Hello": type 'String' is not a subtype of type 'Null'
Why does that happen?
The error comes from the fact that your map has been given the type Map<Type, void Function(Null)> since that is the only type it can use based on the content of you list.
The problem is that Dart cannot give your map another type since anything else would not be valid from the perspective of the type system. Let's say the type was Map<Type, void Function(Object)>. Well, we are then allowed to send any object into a method from this map. But that is not allowed since your two methods in the map clearly are defined as accepting int and String and not Object.
We can either give it the type Map<Type, void Function(int)> since we have a method taking a String.
Also, Map<Type, void Function(dynamic)> has the same problem as Object since both methods are clearly defined to taking a precise type.
So Dart will instead use Null as the type of the parameter since the only thing we know is valid to give both methods are the null value.
And since you are then trying to give a String as parameter to a method with the signature of void Function(Null) you are getting an error from the type system.
If you want you code to run, you need to tell the type system to keep quite about what you are doing. To do that, you should use dynamic for the type of method you are receiving from the map:
void printInt(int i) => print(i);
void printString(String s) => print(s);
void printSomething(Object o) {
final dynamic printer = {
int: printInt,
String: printString,
}[o.runtimeType];
print('Printer is $printer');
printer(o);
}
void main() => printSomething('Hello');
I have this code:
void baz(String s) {
}
void bar<T>(T val, void Function(T) encode) {
}
void foo() {
(<String>(val) => bar<String>(val, baz))("foo");
}
However it gives this error:
The argument type 'void Function(String)' can't be assigned to the parameter type 'void Function(String)'. dart(argument_type_not_assignable)
A confusing error to say the least! What is going on here?
Dart type parameters are kind of strange since you can use reserved keywords and types as names for these parameters. In this case, <String>(val) => ... is actually using String as the name of a type argument in the context of the closure, conflicting with the class String in dart:core. If you drop the type argument to your closure in foo, this code should work and the type of val in bar will still be String. If you want to be sure, you can explicitly type the val parameter in the closure:
void foo() {
((String val) => bar<String>(val, baz))("foo");
}
Also, the type parameter to bar can be left out as type inference will be able to infer the type T is String:
void foo() {
((String val) => bar(val, baz))("foo");
}