Brett Sutton
Brett Sutton

Reputation: 4554

Dart Generics - type is not a subtype

I'm getting a runtime error from a Dart generic function:

 Widget card = widget.cardBuilder(item);

Which generates:

 type '(Contact) => Widget' is not a subtype of type '(DeletableItem) => Widget'

The Contact class is defined as

class Contact
implements DeletableItem
{

I then have a method:

 class UndoableListView<T extends DeletableItem>
 {
      List<T> children;
      final Widget Function(T item) cardBuilder;

And this is where the runtime error occurs

 Widget buildItem(BuildContext context, int index) {
 T item = children[index];
 Widget card = widget.cardBuilder(item);   <<<<< error thrown here.

I'm clearly mis-understanding something with how generics work.

Contact clearly extends DeleteableItem.

So what have I done wrong?

Upvotes: 10

Views: 1550

Answers (1)

lrn
lrn

Reputation: 71653

Contact extends DeletableItem, and is a subtype of it.

Functions are contravariant in their parameters, so

Widget Function(Contact)

is not a subtype of

Widget Function(DeletableItem)

In fact, it's the other way around.

Functions act that way because subtyping means substitutability. You can use a Contact anywhere a DeletableItem is needed (because it implements the entire DeletableItem interface), so Contact is a subtype of DeletableItem.

You can use a Widget Function(DeletableItem) anywhere a Widget Function(Contact) is needed, because you can call that function with any Contact (and it returns a Widget as expected). So Widget Function(DeletableItem) is a subtype of Widget Function(Contact).

Dart generics are covariant, even though it's not always sound.

That means that a UndoableListView<Contact> is a subtype of UndoableListView<DeletableItem>.

The problem comes up because the cardBuilder field uses the class type parameter contravariantly in the function type,

      final Widget Function(T item) cardBuilder;

So, when you do:

UndoableListView<DeletableItem> list = UndoableListView<Contact>();
var builder = list.cardBuilder;

you get an error. The list.cardBuilder expression has a static type of Widget Function(DeletableItem), but a run-time type of Widget Function(Contact), which is not a subtype of the static type. That's a type soundness problem, one the compiler is aware of, and it inserts a type check to ensure that the builder variable won't end up with an invalidly typed value.

And that check throws.

Upvotes: 1

Related Questions