class Parent<T> {
void method(T t) {}
}
class Child extends Parent {
#override
void method(int i) {} // error: mentioned_below
void takesDynamic(dynamic d) {
takesType(d); // no error
}
void takesType(int i) {
takesDynamic(i); // no error
}
}
Error:
void Function(int) isn't a valid override of void Function(dynamic)
When I can easily pass int to dynamic and vice-versa in a method parameter, why do I see the error when I override method.
PS:
I am not looking for a solution which is to use extends Parent<int> and get it working, I want to know the reason why things are treated differently when I am overriding a method vs calling regular methods.
void Function(int x) normally isn't a valid override of void Function(dynamic x) because the int version is not substitutable for the dynamic version.
What are the allowed inputs to Parent<dynamic>.method? Anything.
What are the allowed inputs to Child.method? Just ints.
Such an override therefore could violate the contract of Parent<dynamic>'s interface. (For example, what if you had an instance of Child and passed it to something that expected Parent<dynamic>, which then invoked method('not an int') on it?)
(Note that this is not specific to method overrides. In general, a function that takes a narrower type cannot be used where a function that takes a wider type is expected, even if the narrower type derives from the wider type.)
Dart does allow you to use the covariant keyword to suppress the static type error and explicitly allow the override, but be aware that doing so isn't necessarily type-safe, and you would be responsible for ensuring that you don't get type errors at runtime.
Further reading: Covariance and contravariance (computer science) from Wikipedia
Related
I'm trying to call a static method from a generic type I receive.
Is that even possible?
Furthermore, I apply a Type constraint in order to only manipulate the object from its parent class.
Here is a short example of what I'm trying to achieve:
class A {
static func() {
print("A");
}
}
class B extends A {
static func() {
print("B");
}
}
concret<T extends A>() {
T.func(); // I expected a print('B')
}
main() {
concret<B>();
}
No, it's not possible.
Dart static method invocations are resolved at compile-time, so it's not possible to call them on type variables which only have a value at run-time.
If it was possible, it would be completely unsafe. Anyone can create a class C extending A which does not have a static func member and invoke concret<C>();. Since static members are not inherited, it would have to give you a run-time error, and there is nothing you can do to detect that at compile-time. That is the primary reason why it is not allowed.
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();
}
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'm trying to understand the Java compiler's thinking (I know, bad idea)...
Consider this program:
import java.util.Optional;
public class xx {
public static class Foo<T> {
public interface Bar<T> {
int getX();
}
public Optional<Bar<T>> getBar() {
return Optional.empty();
}
}
public static void main(String[] args) throws Exception {
Foo foo = new Foo(); // note raw type
foo.getBar().get().getX();
}
}
The java 1.8.0_112 compiler gives:
xx.java:15: error: cannot find symbol
foo.getBar().get().getX();
^
symbol: method getX()
location: class Object
1 error
The question is: why doesn't the compiler, given the raw type Foo for foo, realize that the return type of foo.getBar() is Optional<? extends Bar> instead of what it apparently thinks, which is Optional<?> ?
Note: I know how to change this program to make it compile, that's not the question.
Once you use raw types in conjunction with type inference, the following from JLS 18.5.2 will apply
If unchecked conversion was necessary for the method to be applicable during constraint set reduction in ยง18.5.1, then [...] the return type and thrown types of the invocation type of m are given by the erasure of the return type and thrown types of m's type.
From this follows, that the return type of foo.getBar() is indeed just Optional with all type arguments erased.
Solution: avoid raw types, always.
Assume such conditions:
Some operation does not provide possibility of returning the result.
This operation declared as callback
Using typedef not recommended
Some operation provide of returning the result.
This operation declared as callback
Using typedef not recommended
Assume such scenario:
void main() {
executeVoidOperation(methodNonVoid); // Must throw if method void?
executeNonVoidOperation(methodVoid); // Must throw if method non-void?
}
int methodNonVoid() {
return 0;
}
void methodVoid() {
}
void executeVoidOperation(void operation()) {
operation(); // Must throw if method non-void?
}
void executeNonVoidOperation(dynamic operation()) {
var result = operation(); // Must throw if method void?
print(result); // Result of void operation? (if such passed as argument)
}
Displayed results:
null
Questions (where I wrong?):
Null is object. From where this null appeared (as result) if void function cannot return result (even null)?
Functions with different return types in Dart assumed as the same (not conflicting) types?
How in Dart called this function transformations?
executeNonVoidOperation(methodVoid); works because the callback is defined as dynamic operation(). dynamic can be anything, including void. It's the same as if you just don't specify a type.
The null value stems from a simple rule in Dart. Quoted from the Dart Language Tour:
All functions return a value. If no return value is specified, the statement return null; is implicitly appended to the function body.
That means that every void method always returns null. If you try to return something else, you'll get a runtime error (in checked mode).
executeVoidOperation(methodNonVoid); is a bit more tricky - I'd expect it to throw a runtime error, but it seems the callback is treated as dynamic operation() instead of void operation(). Dart Editor's analyzer seems to think that, too. This may be either a bug or a design choice by the Dart team.