
The problem here is that EntityMixin is generic with a bound, so when you write EntityMixin without any type parameters, it must be instantiate with something as type parameter before it can be used, and we can't infer that type parameter for you. So the system tells you that you must instantiate it, it cannot be inferred.
In Dart 2/Strong Mode, EntityMixin it can't just be instantiated as EntityMixin<dynamic> in the bound, because if it were, then the bound for T would be EntityMixin<dynamic> and dynamic doesn't satisfy that.
That is, if you explicitly wrote: abstract class EntityMixin<T Extends EntityMixin<dynamic>> then it would be invalid because dynamic doesn't satisfy the bound of T.
We can't just use the bound of EntityMixin to figure out what to use, because we're still trying to figure out what that bound is.
We could infer an infinite type, abstract class EntityMixin<T extends EntityMixin<EntityMixin<EntityMixin<EntityMixin<EntityMixin<....ad infinitum...>>>>>, except that Dart doesn't have infinite types.
We could use T (it works), but it's a little too magic for the system to infer that, so we don't.
That means we're stuck and need to programmer to tell us how to instantiate the bound.
Options are either T or another type that references itself:
abstract class EntityMixin<T extends EntityMixin<T>> ...
abstract class EntityMixin<T extends EntityMixin<OtherMixin>> ...
class OtherMixin implements EntityMixin<OtherMixin> ...
This is a case of F-bounded polymorphism, and you need to handle it carefully.
I wonder why it is required for a type parameter constraint to be instantiated (without knowing anything what the VM is dealing with under the hood)
Thanks anyway. Your suggestions work for me.
Everything Lasse said is spot on. For some extra background, check issues #27526 and #28580. We are currently discussing how to make this more precise (here is the current proposal).
I wonder why it is required for a type parameter constraint to be instantiated
We couldn't omit the actual type arguments completely, they have to be provided either explicitly or implicitly.
If we insist that we can _actually_ omit the type arguments then a declaration of a formal type parameter like T extends List would mean that T is a higher-kinded type parameter. This means that we would be allowed to use things like T<int> in the scope of T, and there are lots of other implications (and complications). Scala has had higher-kinded type parameters for some years (see, e.g., this paper by Adriaan Moors), but I think it's a safe bet that they are not coming to Dart any time soon. ;-)
So type parameter bounds will have to receive type arguments as they always did, but the simple 'always use dynamic' rule for the implicitly provided type arguments doesn't work for Dart 2. The 'instantiate to bound' mechanism is the replacement that we have been using for a while in strong mode, and in Dart 2 we will have something very similar.
The VM doesn't care (yet), it's the Dart 2 strong mode type system that cares in this case.
In Dart, all "generic types" are instantiated before they are used as types. They aren't actually types before, they are more like functions from types to types. If you omit the type argument, one is inferred for you, if possible. In Dart 1 it was always possible to use dynamic (it was considered both a supertype and a sybtype of any type), so there was never a problem with leaving off the type argument.
In Dart 2, dynamic is not a subtype of anything except itself, Object and void (which are all equivalent at runtime, just with different static behavior). That means that dynamic doesn't satisfy a constraint on a type parameter. If you have:
class C<T extends num> {}
and write new C(), then it can't be implicitly instantiated as new C<dynamic>() like in Dart 1 because dynamic does not extends num. So, Dart 2 has to think more. In this case it can then "instantiate to bound" and infer new C<num>() for you.
This gets worse when you write:
class D<T extends D<T>>{}
which is a classic "F-bounded" type. If you just write new D(), there is no finite type X that can be inserted to make new D<X>() valid. The type X must be a subtype of D<X> which only has the infinite type D<D<D<D<....turtles all the way down ....>>>> as solution. Because of that, in Dart 2, you have to provide an argument type to new D(), otherwise the program is rejected.
What you hit here is a slight variation of this problem, when the uninstantiated type is the type constraint:
class E<T extends E> {}
For the same reasons as new D() above, you need to specify the bound on the inner E because the system cannot find one for you. It can't use dynamic, and it can't use E<dynamic> or E<E<dynamic>>, etc., because none of them would themselves satisfy the bound that they specify.
So, again, you're painting yourself into an infinitely complex corner and you need to tell the compiler what the way out is, because it cannot find one.
(In this exact case, it could actually use T as the parameter, but I don't think that's possible in all the different ways you can trigger the problem, and it might still be a little too magical for comfort).
@lrhn @eernstg thanks a lot for the great explanation.