How to use covariant in constructors? - dart

Minimum reproducible code:
class Foo {}
class Bar extends Foo {}
class Baz {
final void Function(Foo) f;
Baz._(this.f);
Baz.one(void Function(Bar) func) : this._(func); // Compile error
}
I am getting this error:
The argument type 'void Function(Bar)' can't be assigned to the parameter type 'void Function(Foo)'
How can I use the covariant keyword to tell analyzer that a call to f will return Bar in my particular case?
Note: I can use this._(func as void Function(Foo)) to make it compile, but I'm looking for a better solution.

Related

How to use type parameter when using redirecting constructor?

Minimum reproducible code:
class Foo<T extends num> {
final T t;
Foo._(this.t);
Foo.zero() : this._<int>(0); // Error
}
'' isn't a field in the enclosing class.
Note: I want to do it without casting, i.e. this._(0 as T) works but I want to use type argument int.

Why type inference is not able to infer a type in a function callback?

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.

Type Erasure with dart LinkedList?

This is the program:
import 'dart:collection';
class MyLinkedListEntry<T> extends LinkedListEntry<MyLinkedListEntry> {
T value;
MyLinkedListEntry(T this.value);
#override
String toString() => '${super.toString()}: ${value}';
}
void main(List<String> args) {
var l = LinkedList<MyLinkedListEntry>();
var s = MyLinkedListEntry("SomeString");
var p = MyLinkedListEntry(125);
l.add(s);
s.insertAfter(p);
p.insertAfter(MyLinkedListEntry(126));
l.forEach((e) => print(e));
}
And it gives this output:
Instance of 'MyLinkedListEntry<String>': SomeString
Instance of 'MyLinkedListEntry<int>': 125
Instance of 'MyLinkedListEntry<dynamic>': 126
I expected the third instance to be of type LinkedList<int> as well. Why it's not?
This is with Dart 2.13.4.
0. dynamic in type checking
Everything is a subclass of dynamic:
print(1 is dynamic); // Outputs true
print("a" is dynamic); // Outputs true
In fact, Dart even shows a warning when using the above code: Unnecessary type check; the result is always 'true'.
1. Omiting type parameters in declarations
In the declaration
class MyLinkedListEntry<T> extends LinkedListEntry<MyLinkedListEntry>
note that you're not passing the type parameter of MyLinkedListEntry in the type parameter of LinkedListEntry. From docs (emphasis mine):
When a generic class is instantiated without explicit type arguments, each type parameter defaults to its type bound [...] if one is explicitly given, or dynamic otherwise.
So Dart interprets this as
class MyLinkedListEntry<T> extends LinkedListEntry<MyLinkedListEntry<dynamic>>
2. The extends clause in type parameters
Let's look at the declaration of LinkedListEntry:
abstract class LinkedListEntry<E extends LinkedListEntry<E>>
Note that LinkedListEntry requires a type parameter named E, which must be a subclass of LinkedListEntry. When you use LinkedListEntry<E>, E must extend LinkedListEntry<E>.
When you declare MyLinkedListEntry<T>, you're passing MyLinkedListEntry<dynamic> as E. Since T always extends from dynamic, MyLinkedListEntry<T> extends LinkedListEntry<MyLinkedListEntry<dynamic>>, so this is a valid declaration.
3. Type parameters in methods
In the expression
p.insertAfter(MyLinkedListEntry(126));
you're using the insertAfter method declared in the LinkedListEntry class. Let's look at its declaration:
void insertAfter(E entry)
Since E is equal to MyLinkedListEntry<dynamic>, Dart will interpret any MyLinkedListEntry call to this method as
void insertAfter(MyLinkedListEntry<dynamic> entry)
Therefore, when you do
p.insertAfter(MyLinkedListEntry(126));
you're actually passing an upcasted MyLinkedListEntry<dynamic>, which explains the output.
The solution
Explicitly pass the type parameter of MyLinkedListEntry when inserting:
p.insertAfter(MyLinkedListEntry<int>(126));

Dart function argument can't be assigned even though it is the same type

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");
}

Java compiler error: raw type with method returning Optional

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.

Resources