language icon indicating copy to clipboard operation
language copied to clipboard

Unable to specify interfaces or mixins as type arguments

Open miyoyo opened this issue 5 years ago • 7 comments

// Let A be a class
class A {}

// Let X be a method taking a type argument
void X<T>(){}

// Calling X with A as a type argument works, as intended.
final v = X<A>();

// Change X to take a Type argument that extends A
void X<T extends A>(){}

// Again, this works, as A is A
final v = X<A>();

// Now, Let B be a mixin
mixin B {}

// Let class C extend A and mixin B
class C extends A with B {} //EDIT: forgot "extends A"

// Change X to take a Type argument that extends A, and mixes in B
// This does not work as it is not valid syntax
void X<T extends A with B>(){}

// Change X to take a Type argument that extends A, and implements B
// This is not valid either
void X<T extends A implements B>(){}

// For now, the only solutions are creating a class that pre-mixes a mixin
class C extends A with B{}
void X<T extends C>(){}

// Or make the mixin specific to a class, and tell the function to take types that extend the mixin
// This does not allow for multiple mixins however
mixin B on A {}
class C with B{}
void X<T extends B>(){}

miyoyo avatar Nov 26 '18 08:11 miyoyo

The bound T extends A with B is not allowed. You must write a single type as the bound and A with B is not a type. It represents a class creation, and it can only be used in class declarations (where classes are created). The extends in the bound means something completely different from the extends in a class declaration. The former is descriptive (it says that T must be a type which is a sub-type of the bound) and the latter is prescriptive (it says that a new class is introduces which extends the super-class).

The same thing with implements - it describes that a new thing is made to implement those interfaces. It has no meaning in a type variable bound.

(The first class C does not extend A in your example code, contrary to the comment. I also don't think it's used).

So, what is the problem that you are trying to solve, and that you cannot? I'm guessing that it's that you want a type variable to have two unrelated bounds.

Dart currently gives you no way to put multiple bounds on a single type variable. You cannot say that type variable argument must be a subtype of both A and B, you can only give one limit. So, if you need something to implement both A and B, where A and B are not sub-types of each other in any direction, then the only solution is indeed to give that combination a new name, and require the arguments to implement the type with that name.

lrhn avatar Nov 26 '18 08:11 lrhn

Would #65 be a possible solution?

miyoyo avatar Nov 26 '18 08:11 miyoyo

Java uses & to specify intersection types (because comma creates ambiguities) for type variable bounds, so we could probably rather easily do something similar. However, there is also an ongoing debate about having union types, and they would probably pull intersection types along, in which case we would get this feature from there. In any case, an extension that adds such a special case of intersection types should be designed in such a way that it can be further extended to cover intersection types in general.

eernstg avatar Nov 26 '18 08:11 eernstg

I don't think #65 makes much difference here, because that proposal is about being able to declare aliases of types in general, rather than only being able to declare aliases of function types (which were needed way back when function types couldn't be written inline at all). But we probably won't introduce intersection types as a syntactic special case that you can only use in a type alias, and if we want to introduce a special case of intersection types (that is: only for type variable bounds) then it's actually even less likely that it would help to go via type aliases (because we would then have to have very special rules about the usage of type aliases, to enforce that no intersection type occurs anywhere except as a type variable bound).

eernstg avatar Nov 26 '18 09:11 eernstg

So, if you need something to implement both A and B, where A and B are not sub-types of each other in any direction, then the only solution is indeed to give that combination a new name, and require the arguments to implement the type with that name.

What if I need a method, that operates on both: Int16List and Int32List and uses asMap from List class and buffer from TypedData classs? Common ancestor of Int16List and Int32List is _TypedIntList which is private. How could I solve my problem using your aproach @lrhn ?

Java uses & to specify intersection types

& from Java works very well. I think this may become great feature in Dart as well

eximius313 avatar Aug 09 '22 15:08 eximius313

What if I need a method, that operates on both: Int16List and Int32List and uses asMap from List class and buffer from TypedData classs?

Dart doesn't currently offer statically safe ways to use methods that aren't guaranteed to be there, and you can't specify a type that has both asMap and buffer since, as you said, _TypedIntList is private. You also can't make your own superclass as suggested because Dart won't let you extend or implement TypedData.

Here are some workarounds:

  • Runtime check, then cast.
void test1(Object data) {
  if (data is! TypedData || data is! List<int>) throw "Wrong type";
  final typed = data as TypedData;  // unnecessary, see below
  final list = data as List<int>;
  print(typed.buffer);
  print(list.asMap);
}
  • Runtime check, use dynamic.
void test3(dynamic data) {
  if (data is! Int16List || data is! Int32List) throw "Wrong type";
  print(data.buffer);
  print(data.asMap);
}

Regarding intersection types, it's interesting that in the first example, you'll notice that Dart actually infers that data has to be a TypedData, but fails to realize that it must also be a List<int>. That's why technically typed is unneeded -- Dart already promotes data to a TypedData for you. With intersection types, you could specify this more accurately, but I think this comes up rarely enough that intersection types are better off left alone. It would be much better if the API exposed _TypedIntList for you to use directly.

Also see #1612, although be sure to read https://github.com/dart-lang/language/issues/1612#issuecomment-836538987 and https://github.com/dart-lang/language/issues/1612#issuecomment-837263089.

Levi-Lesches avatar Aug 10 '22 11:08 Levi-Lesches

Thanks @lrhn. I think that using & indicates, that someone wanted to use these particular interfaces explicitly. I hope that some form of this feature will come to Dart one day.

eximius313 avatar Aug 10 '22 19:08 eximius313