-
Notifications
You must be signed in to change notification settings - Fork 1.7k
int doesn't extend Comparable<int> #43763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The behavior is unfortunate, but not easily correctible. Since |
If we do make the type argument of class Comparable<in T> {...} then |
Yep. This is quite annoying. |
I regularly run into this problem with The class hierarchy is as follows:
If Comparable was considered contravariant, that would solve this problem. P.S. I think this a design mistake in the fixnum package. |
Another solution would be to allow multiple type constraints on a parameter, e.g. like this:
Currently this isn't supported by Dart, as discussed in Issue 1152. For completeness sake, I'll point out that in Java the problem can be solved with a lower bounded wildcard:
Java uses wildcards extensively instead of supporting covariant/contravariant typing. Java's system is more flexible and more sound than the Dart's (which assumes all generic types are covariant, which is nonsense of course), though it leads to more verbose type signatures. I suspect the Dart team would prefer to label types explicitly as covariant/contravariant like Erik suggested above, even if it's less flexible. |
I think an easy solution may just be to mention this gotcha in the docs of Here is a dartpad example: import 'package:collection/collection.dart';
class Foo {
Foo(this.value);
int value;
String toString() => value.toString();
}
void main() {
final a = <Foo>[Foo(2), Foo(5), Foo(3)];
// final b = a.sortedBy((f) => f.value);
// Will fail with:
// lib/main.dart:13:15:
// Error: Inferred type argument 'int' doesn't conform to the bound 'Comparable<K>' of the type variable 'K' on 'IterableExtension|sortedBy'.
// - 'Comparable' is from 'dart:core'.
// final b = a.sortedBy((f) => f.value);
// ^
// /app/local_pub_cache/hosted/pub.dev/collection-1.17.2/lib/src/iterable_extensions.dart:65:20:
// Info: This is the type variable whose bound isn't conformed to.
// List<T> sortedBy<K extends Comparable<K>>(K Function(T element) keyOf) {
// ^
// Error: Compilation failed.
// final b = a.sortedBy<int>((f) => f.value);
// Will fail with:
// lib/main.dart:25:18:
// Error: Type argument 'int' doesn't conform to the bound 'Comparable<K>' of the type variable 'K' on 'IterableExtension|sortedBy'.
// - 'Comparable' is from 'dart:core'.
// final b = a.sortedBy<int>((f) => f.value);
// ^
// /app/local_pub_cache/hosted/pub.dev/collection-1.17.2/lib/src/iterable_extensions.dart:65:20:
// Info: This is the type variable whose bound isn't conformed to.
// List<T> sortedBy<K extends Comparable<K>>(K Function(T element) keyOf) {
// ^
// Error: Compilation failed.
// So you have to do:
final b = a.sortedBy<num>((f) => f.value);
// Or casting also works:
// final b = a.sortedBy((f) => f.value as num);
print(b);
} |
@bwilkerson - would it be feasible to specialize the "Couldn't infer parameter" message in the case of This could save us from having to add a warning about this in every method that uses |
We have the option of declaring num-specific extensions: extension ComparableNum<T extends num> on Iterable<T> {
List<T> sortedBy([int Function(T, T)? compare]) => [...this]..sort(compare ?? Comparable.compare<num>);
} which should take precedence over Then, at least, our own extensions won't give these errors. It's annoying, and special-casey, but But if we get variance annotations, and |
I do not think that would help here. These are all generic methods, and we need to specialize on the generic argument to the method - not the generic argument for the collection. These methods are passed a |
We should consider generalizing type inference such that it is able to find solutions in this kind of situation. Here is an example that works fine in Kotlin: open class A<out X: A<X>>() {}
open class B() : A<B>() {}
open class C() : B() {}
fun <X: A<X>>f(x : X) {}
fun main() {
f(C()); // Inferred type argument `B`.
} The corresponding example in Dart fails, because it does not make an attempt to use class A<X extends A<X>> {}
class B extends A<B> {}
class C extends B {}
void f<X extends A<X>>(X x) {}
void main() {
f<B>(C()); // OK.
f(C()); // Compile-time error: "Couldn't infer ..".
} This situation is not directly covered by the discussion in dart-lang/language#1194, but it does seem to be related: We need to take the bound of |
Closing: The generalization of type inference that I mentioned here has been performed by means of the new feature known as 'inference-using-bounds', which is enabled by default in Dart 3.7.0. See dart-lang/language#3009 for further info. In particular, the following examples are working in DartPad, main channel at this time: void main() {
print([1]..sortBy((x) => x));
}
extension ListExtension<T> on List<T> {
void sortBy<T2 extends Comparable<T2>>(T2 Function(T element) f) {
this.sort((a, b) => f(a).compareTo(f(b)));
}
} import 'package:collection/collection.dart';
class Foo {
Foo(this.value);
int value;
String toString() => value.toString();
}
void main() {
final a = <Foo>[Foo(2), Foo(5), Foo(3)];
final b1 = a.sortedBy((f) => f.value);
final b2 = a.sortedBy<num>((f) => f.value);
final b3 = a.sortedBy((f) => f.value as num);
print('$b1, $b2, $b3');
// The following still fails, it doesn't satisfy the declare bounds:
// final b = a.sortedBy<int>((f) => f.value);
} |
This is an old issue since Dart 1 and doesn't seem to have been fixed. #8741
This behavior is preventing the below API patterns from being efficiently implemented.
Trying to invoke this function
will cause error:
int doesn't extend Comparable<int>
We need to change generics parameter to
num
at every invocation site.The text was updated successfully, but these errors were encountered: