I have class that takes to generics (for example A and B) and then i have a subclass which specifies one of those generics, like that:
class Foo<A, B>{
void f() {}
}
class Boo<A> extends Foo<A, String> {
void b() {}
}
When i am trying to cast a Foo variable to Boo the smart cast some how is not working:
void test<A, B>(Foo<A, B> foo) {
if (foo is Boo<A>)
foo.b();
}
The error message is: Error: The method 'b' isn't defined for the class 'Foo<A, B>'.
But when i drop the generic types the function compiles:
void test(Foo foo) {
if (foo is Boo)
foo.b();
}
Is there any way i can get the smart cast to work without dropping the generic types?
This is working as intended.
Dart only promotes variables to subtypes (a more precise type) to ensure that you don't lose information. That's why it's called "promotion", not just "casting".
When you do foo is Boo<A>, then it's checking whether Boo<A> is a subtype of the currently known type of the foo variable, which is Foo<A, B>.
It is not.
The type Boo<A> a subtype of Foo<A, String> (because it extends that), but String is not (statically known to be) a subtype of the type variable B. So as static types, Boo<A> is not a subtype of Foo<A, B>, and the promotion doesn't promote the static type of the foo variable.
There is not a lot you can do about this as written. The type B could be bound to Never at runtime, so no other type would be sound to assume to be a subtype of B at compile time.
When you drop the generics, the type of foo becomes Foo<Object?, Object?>, and any type implementing Foo is a subtype of that, so you do get promotion to Bar<whatever>.
If you declared test as:
void test<A>(Foo<A, String> foo) {
then the promotion would succeed.
Related
Dart has a handy map function on iterables, and it accepts a lambda. So I can write something like:
// Stupid example class
class Foo {
int v;
int v2() { return v*v; }
}
List<int> mapFoos(List<Foo> foos) {
return foos.map( (Foo f) => f.v2() );
}
But this feels a little clunky to me. I'm used to being able to tell map to use the member function directly, something that would look more like:
// does not compile
List<int> mapFoos(List<Foo> foos) {
return foos.map(Foo.v2);
}
But this fails to compile with the error:
The argument type '() → int' can't be assigned to the parameter type '(Foo) → int'
Is there some way to turn the member function into a lambda in a succinct way, so that
we can have something closer to the second example.
I could write
int applyV2(Foo f) {
return f.v2();
}
List<int> mapFoos(List<Foo> foos) {
return foos.map(applyV2);
}
but then I'd need to create that for each member function I want to map, which isn't really any better than using the lambda function.
If it makes any difference I'm using dart 1 due to "legacy reasons", if this has changed in recent versions I'd love to know that too.
No.
There is no shorter way to create a function which takes a Foo and calls its v2 method, than (f) => f.v2().
You can omit the Foo type on the parameter, because it can be inferred from the context (a List<X>.map<R> requires an R Function(X) as argument).
You cannot tear off Foo.v2 because v2 is an interface method, not a static method.
Just to elaborate on why Dart doesn't allow that, you can stop reading now if you just want to know what works:
Some languages allow you to tear off instance methods, so Foo.v2 becomes a function which expects its this object as an argument, in Dart a function of type int Function(Foo). Dart does not allow that. Probably for many different reasons, but most importantly because it cannot work. Dart types are interfaces, all class types can be implemented by another class without inheriting any implementation.
If you then tear off Foo.v2, you can call it with an instance of another class which implements Foo, but which won't necessarily find the private fields that Foo has, and which v2 could depend on.
Also, the tear-off would be covariant in its this-parameter.
Take SubFoo which extends Foo and has its own v2 method. If you do Foo foo = SubFoo(); var vtoo = foo.v2; then the static type of vtoo will be int Function(Foo), but the implementation from SubFoo will necessarily have runtime type int Function(SubFoo), which is not a subtype of the static type. That means it's unsound. The torn off function will have to do a run-time type check that its argument is actually a SubFoo, and throw if it's not. (So, that feature is not a good match for Dart.)
I've recently found myself in a situation where I wanted to check if a Type is a subtype of another Type this is what I've tried
abstract class Record{}
class TimeRecord extends Record{}
void test(){
print(TimeRecord is Record); // return false but why ??
}
The only time it makes sense to check if one type is a subtype of another type is when at least one of the types is a type variable. (Otherwise, you can just look at the source and write a constant true or false into the code).
There is a way to check whether one type is a subtype of another, and it does use the is operator, but you need to have an instance as the first operand and a type as the second. You can't just create an instance of an unknown type, so we instead rely in Dart's covariant generics:
bool isSubtype<S, T>() => <S>[] is List<T>;
(You can use any generic class, or even create your own, instead of using List. All it needs is a way to create the object.)
Then you can write:
print(isSubtype<TimeRecord, Record>()); // true!
The is keyword is used to check if an object instance is an object of type T, and not if a type is another type:
abstract class Record{}
class TimeRecord extends Record{}
void test(){
print(TimeRecord() is Record); // returns true!
}
Just to add up to #lrn answer.
You could also do something like:
extension NullableObjectsExtensions<T> on T {
bool isSubtypeOf<S>() => <T>[] is List<S>;
bool isSupertypeOf<S>() => <S>[] is List<T>;
}
So this way you can test any variable anywhere.
I found something strange in dart. If there is a list that contains instances of a base class (in this example Super), the list can be set with a list of inherited instances. It seems that this changes the list type at runtime.
Is this intended behavior or is this a bug in Dart?
abstract class Super {}
class A extends Super {}
class B extends Super {}
class Container {
List<Super> mylist = [];
Container(this.mylist);
}
void main() {
// 1. dont't works
final container = Container(<A>[A(), A()]);
// 2. works
final container = Container([A(), A()]);
print(container.mylist.runtimeType);
container.mylist.add(B());
print(container.mylist);
}
If case 1 is used in the code above I get the following error:
JSArray<A>
Uncaught Error: TypeError: Instance of 'B': type 'B' is not a subtype of type 'A'
The error is at the line where I try to add an instance of B:
container.mylist.add(B());
Dart has a system called type promotion, where it can promote the type of a variable, similar to type inference.
It works as a cast. On the first example you've explicit promoted the type of your list to be of type A, so there's nothing strange about this.
Take a look at the first article that explains this mechanism.
When you do:
final container = Container(<A>[A(), A()]);
you explicitly create a List<A> object. Although Container's constructor expects a List<Super>, it accepts a List<A> argument because Dart considers Generic<Derived> to be a subtype of Generic<Base> if Derived is a subtype of Base. Your later attempt to do container.mylist.add(B()); will fail because container.mylist is actually a List<A> and therefore cannot legally store any B elements.
When you instead do:
final container = Container([A(), A()]);
then, because the List literal is not given an explicit type, its type is inferred to be List<Super> from Container's expected construction parameter. container.mylist.add(B()); will succeed since container.mylist is actually a List<Super> and therefore can legally store B elements.
This is a followup question after reading this Q&A:
Generic Sorting function accepts T, but want to ensure T is comparable
I have a class like so:
class BinarySearchTree<E extends Comparable> { ... }
so I can create an instance like this:
final tree = BinarySearchTree<int>();
My question is about using Comparable vs Comparable<E>. When I do this:
class BinarySearchTree<E extends Comparable> { ... }
then the type defaults to E extends Comparable<dynamic>. I normally try to avoid dynamic, so in order to be more explicit about the type that is being compared, it seems like I should write it this:
class BinarySearchTree<E extends Comparable<E>> { ... }
But in that case I get an error here:
final tree = BinarySearchTree<int>();
// 'int' doesn't conform to the bound 'Comparable<int>' of the type parameter 'E'.
// Try using a type that is or is a subclass of 'Comparable<int>'.
This demonstrates my lack of understanding of generics. What am I missing?
In Dart, a class cannot implement 2 different concrete instances of a generic interface:
abstract class Foo<T> {}
// error: Foo can only be implemented once
class Bar implements Foo<String>, Foo<int> {}
num implements Comparable<num>, because it would be slightly absurd for the built-in number types to not be comparable. However, since int is a subtype of num (and therefore inherits Comparable<num>, it cannot have Comparable<int>.
This leads to the slightly weird consequence that int does not implement Comparable<int>.
The problem you're facing is that from the language's point of view, there are 2 types involved: the type of the elements being compared, and the type of the elements they are being compared to.
As such, your type will need 2 type parameters:
class Tree<T extends Comparable<S>, S> {
T get foo;
}
final intTree = Tree<int, num>();
final foo = intTree.foo; // returns an int
Admittedly, this isn't a super clean solution, but if you're using Dart 2.13 or higher, you can use typedefs to make it a bit nicer:
typedef IntTree = Tree<int, num>;
typedef RegularTree<T> = Tree<T, T>;
final intTree = IntTree();
final stringTree = RegularTree<String>();
intTree.foo // is an int
stringTree.foo // is a String
There is another option, which is to just drop some type safety and use Comparable<dynamic>, but personally I'd recommend against it. BTW, if you want to avoid accidentally missing type parameters you can disable implicit-dynamic as described here: https://dart.dev/guides/language/analysis-options#enabling-additional-type-checks
This will give an error any time the type dynamic is inferred from context without the programmer actually typing the word dynamic
AngularDart has a class called AppView, i.e. abstract class AppView<T> {}.
One (at least) of these are generated for every class annotated with #Component:
// file.dart
#Component(...)
class DashboardComponent {}
// file.template.dart (Generated)
class ViewDashboardComponent extends AppView<DashboardComponent> {}
I have code elsewhere in the framework that doesn't care what this T type is. I'm a little confused with Dart 2 what the "right" "anything" type to use. For example, I could use:
AppView
AppView<dynamic>
AppView<Object>
AppView<Null>
AppView<void>
I think more than one of these will "work". But which is the "right" one to use in this case?
You should be fine to use AppView (or AppView<dynamic>) just about anywhere. I can think of two examples where this will get you into trouble though:
If you are instantiating an AppView, you definitely want that type parameter. See the following error when you don't:
$ cat a.dart
void main() {
List<dynamic> a = ["one", "two", "three"];
List<String> b = a;
}
$ dart --preview-dart-2 a.dart
Unhandled exception:
type 'List' is not a subtype of type 'List<String>' where
List is from dart:core
List is from dart:core
String is from dart:core
#0 main (file:///Users/sam/a.dart:3:20)
#1 _startIsolate.<anonymous closure> (dart:isolate/isolate_patch.dart:279:19)
#2 _RawReceivePortImpl._handleMessage (dart:isolate/isolate_patch.dart:165:12)
If you are ever assigning a closure to a site that expects a closure with one or more typed parameters that involve T, you will see a "uses dynamic as bottom" static error (from the analyzer), and probably a runtime error as well:
$ cat f.dart
void main() {
List a = <String>["one", "two", "three"];
a.map((String s) => s.toUpperCase());
List b = ["one", "two", "three"];
b.map((String s) => s.toUpperCase());
}
$ dart --preview-dart-2 f.dart
f.dart:3:9: Error: A value of type '(dart.core::String) → dart.core::String' can't be assigned to a variable of type '(dynamic) → dynamic'.
Try changing the type of the left hand side, or casting the right hand side to '(dynamic) → dynamic'.
a.map((String s) => s.toUpperCase());
^
f.dart:6:9: Error: A value of type '(dart.core::String) → dart.core::String' can't be assigned to a variable of type '(dynamic) → dynamic'.
Try changing the type of the left hand side, or casting the right hand side to '(dynamic) → dynamic'.
b.map((String s) => s.toUpperCase());
^
(I'm not certain any Dart tool yet has complete Dart 2 runtime and compile time semantics, so this might change slightly.)
In these cases, it is best to use generic classes, generic methods, and generic typedefs to encapsulate, for a given scope, what the values of an object's type parameters might be.
I suspect there is a difference between dynamic and Object in Dart 2, and I think Günter covered this in his response, though if your code "doesn't care what this T type is", then you're probably not calling any methods on the component.
Edit: void
AppView<void> might be a good choice in this case, as an actual check that you actually never touch the underlying component (Object would probably serve the same purpose). See how we are allowed to access properties of a List<void> but not properties of the elements:
$ cat g.dart
void main() {
var c = <String>["one", "two", "three"];
fn(c);
fn2(c);
}
int fn(List<void> list) => list.length;
int fn2(List<void> list) => list.first.length;
$ dart --preview-dart-2 g.dart
g.dart:9:40: Error: The getter 'length' isn't defined for the class 'void'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 'length'.
int fn2(List<void> list) => list.first.length;
^
I assume you know better than me, but my attempt
AppView - works - same as AppView<dynamic>
AppView<dynamic> - works - really means any type
AppView<Object> - works - really means any type
AppView<Null> - won't work, only null and void values match for T
AppView<void> - won't work, only null and void values match for T
AppView<void> - works (see also comment below from lrn)
The difference between <dynamic> and <Object> would be that for values of type T with T == dynamic property or method access won't be checked statically, while for T == Object only methods and properties of the Object class can be accessed without a previous cast.