-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
type '(int) => void' is not a subtype of type '(Object) => void' #57052
Comments
Summary: The user is encountering a runtime error where a function accepting an |
The error message is correct: So assume that we expect to use a function In general, this is what it means when it is stated that "function parameter types are contravariant": In order to obtain a subtype, we must replace the parameter type by a supertype (in particular, The culprit here is that you have declared a "non-covariant member". class DropdownField<V> extends ModelField {
final void Function(V value) setValue; // <-- Non-covariant, that is, dangerous!
DropdownField({required this.setValue});
} This member is non-covariant in the sense that its type contains a type parameter declared by the enclosing class in a contravariant (that is, non-covariant) position, namely as the function type's parameter type. We could emit a warning when this occurs (see #59050), but this hasn't yet been implemented. (You can vote for that issue in order to support that it gets implemented.) One step you could take in order to reduce the danger is to make sure that the non-covariant member is never used from a client, it must always be used as a member of class DropdownField<V> extends ModelField {
final void Function(V value) _setValue;
void setValue(V value) => this._setValue(value);
DropdownField({required void Function(V) setValue}): _setValue = setValue;
} This is sufficient to make your example run without run-time failures: class ModelField {}
class DropdownField<V> extends ModelField {
final void Function(V value) _setValue;
void setValue(V value) => this._setValue(value);
final List<V> options;
DropdownField({
required setValue,
required this.options,
}) : _setValue = setValue;
}
void _buildDropdownField<V>(DropdownField<V> field) {
final options = field.options;
final data = options.first;
field.setValue(data); // error Here
}
void _renderField(ModelField field) {
/// from server
// if field is StringField, else if field is DateField, ... else
if (field is DropdownField<Object>) {
// same thing occurs with DropdownField<dynamic>
_buildDropdownField(field);
}
}
void main() {
final field =
DropdownField<int>(setValue: (value) => print(value), options: [1, 2, 3]);
_renderField(field);
} The reason why this helps is that the non-covariant member is never accessed under an unsafe type (like In contrast, clients will now call the method It will be checked that the actual argument has the required type ( I'll close this issue because it's all working as specified. (But I'd very much like to have the lint that warns developers against declaring non-covariant members in the first place ;-). |
Thank you! |
Sorry to bump into this again, but the prescribed solutions feels like a workaround. Since in reality I have several method belonging to the dropdown such as Is there an actual proper solution to this issue? Is there a best practice that I did not follow ? |
I found that simply adding the experimental flag keyword What are the potential issue of this keyword ? Would I lose type safety in some edge cases ? |
It is! William Cook described the underlying issue about variance and type safety 35 years ago, and various approaches have been taken. The simplest approach is to say that every type parameter is invariant. This is safe. It means that When Dart was created, a new approach was taken: Every class type parameter is considered to be covariant, and there will be a run-time type check in every case where the operation isn't guaranteed to succeed at run time. This means that we can have assignments like The situation where an instance variable has a type like So we're transforming the instance variable to a method in order to avoid that we evaluate the variable in a situation where the statically known If you're using So that's safe, but it takes away your flexibility. In particular, If you wish to use the strict approach now (where we don't yet have dart-lang/language#524 -- yes, please vote for it!) then you can actually emulate invariance: class ModelField {}
typedef Inv<X> = X Function(X);
typedef DropdownField<V> = _DropdownField<V, Inv<V>>;
class _DropdownField<V, Invariance extends Inv<V>> extends ModelField {
final void Function(V value) setValue;
final List<V> options;
_DropdownField({
required this.setValue,
required this.options,
});
}
void _buildDropdownField<V>(DropdownField<V> field) {
final options = field.options;
final data = options.first;
field.setValue(data); // error Here
}
void _renderField(ModelField field) {
/// from server
// if field is StringField, else if field is DateField, ... else
if (field is DropdownField<Object>) {
// same thing occurs with DropdownField<dynamic>
print("Choosing the `Object` path.");
_buildDropdownField(field);
} else if (field is DropdownField<int>) {
print("Choosing the `int` path.");
_buildDropdownField(field);
}
}
void main() {
final field =
DropdownField<int>(setValue: (value) => print(value), options: [1, 2, 3]);
_renderField(field);
} This means that you actually emulate the declaration However, as you can see, the fact that you do not have subtype relationships like " |
So in summary, there two ways to simulate what First is the wrap the field with The second option is That is why I chose to go with |
First, I'm assuming that we're talking about the actual feature (not the incomplete implementation which is available using The approach that uses a type alias of the form If needed, this approach can be generalized to handle multiple type parameters, some of which are invariant. In any case, we're protecting the original class ( The experiment This emulation technique will work for class hierarchies that are declared in the same library, but you can't make the underlying class private if you must have subtypes/subclasses in different libraries (because they need to be subclasses of In any case, this will give you the real thing in a rather general way. The other approach that I mentioned (making the non-covariant member private and wrapping it by a public method) does not emulate invariance. What it does is actually to use privacy to help enforcing that the non-covariant member is never accessed on any other receiver than Here's an example which demonstrates that the type parameter isn't invariant: class A<X> {
final void Function(X) _fun;
A(this._fun);
void fun(X x) => _fun(x);
}
void main() {
A<num> a = A<int>((i) => print(i)); // OK.
} If the type parameter But we don't get that, because This is actually useful because it means that the class class A<X> {
final void Function(X) _fun;
A(this._fun);
void fun(X x) => _fun(x);
}
void main() {
A<num> a = A<int>((i) => print(i));
a._fun; // Throws!
} |
I checked all my generics are all OK. Inheritance seems ok. Type inference seems ok. No compiler error. I don't have any hard type casting. Everything is seems strongly typed.
But when I run:
How can
int
not be a subtype ofObject
???I am so confused (or Dart is) I hope it's me. But this error occurs only at run time.
The text was updated successfully, but these errors were encountered: