unsafe_variance
Unsafe type: Has a type variable in a non-covariant position.
Details
#An instance variable whose type contains a type parameter of the enclosing class, mixin, or enum in a non-covariant position is likely to cause run-time failures due to failing type checks. For example, in class C<X> {...}
, an instance variable of the form void Function(X) myVariable;
may cause this kind of run-time failure.
The same is true for a getter or method whose return type has a non-covariant occurrence of a type parameter of the enclosing declaration.
This lint flags this kind of member declaration.
BAD:
class C<X> {
final bool Function(X) fun; // LINT
C(this.fun);
}
void main() {
C<num> c = C<int>((i) => i.isEven);
c.fun(10); // Throws.
}
The problem is that X
occurs as a parameter type in the type of fun
.
One way to reduce the potential for run-time type errors is to ensure that the non-covariant member fun
is only used on this
. We cannot strictly enforce this, but we can make it private and add a forwarding method fun
such that we can check locally in the same library that this constraint is satisfied:
BETTER:
class C<X> {
// ignore: unsafe_variance
final bool Function(X) _fun;
bool fun(X x) => _fun(x);
C(this._fun);
}
void main() {
C<num> c = C<int>((i) => i.isEven);
c.fun(10); // Succeeds.
}
A fully safe approach requires a feature that Dart does not yet have, namely statically checked variance. With that, we could specify that the type parameter X
is invariant (inout X
).
It is possible to emulate invariance without support for statically checked variance. This puts some restrictions on the creation of subtypes, but faithfully provides the typing that inout
would give:
GOOD:
typedef Inv<X> = X Function(X);
typedef C<X> = _C<X, Inv<X>>;
class _C<X, Invariance extends Inv<X>> {
// ignore: unsafe_variance
final bool Function(X) fun; // Safe!
_C(this.fun);
}
void main() {
C<int> c = C<int>((i) => i.isEven);
c.fun(10); // Succeeds.
}
With this approach, C<int>
is not a subtype of C<num>
, so c
must have a different declared type.
Another possibility is to declare the variable to have a safe but more general type. It is then safe to use the variable itself, but every invocation will have to be checked at run time:
HONEST:
class C<X> {
final bool Function(Never) fun;
C(this.fun);
}
void main() {
C<num> c = C<int>((int i) => i.isEven);
var cfun = c.fun; // Local variable, enables promotion.
if (cfun is bool Function(int)) cfun(10); // Succeeds.
if (cfun is bool Function(bool)) cfun(true); // Not called.
}
Enable
#To enable the unsafe_variance
rule, add unsafe_variance
under linter > rules in your analysis_options.yaml
file:
linter:
rules:
- unsafe_variance
If you're instead using the YAML map syntax to configure linter rules, add unsafe_variance: true
under linter > rules:
linter:
rules:
unsafe_variance: true
Unless stated otherwise, the documentation on this site reflects Dart 3.7.0. Page last updated on 2025-01-27. View source or report an issue.