Skip to content

Commit 7b71ed1

Browse files
eernstgCommit Queue
authored and
Commit Queue
committed
Introduce the lint unsafe_variance
This CL implements a new lint, `unsafe_variance`. This lint emits a warning whenever an instance member declaration has a signature where a type variable declared by the enclosing class/mixin/enum occurs in a non-covariant position in the return type (including the type of an instance variable). Issues: https://github.com/dart-lang/linter/issues/4111, with goals related to dart-lang/language#296 and dart-lang/language#524. Change-Id: I1352d71d61fece03a432ccf0d98825a69e3a457f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384700 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Erik Ernst <[email protected]>
1 parent 218057a commit 7b71ed1

File tree

14 files changed

+815
-162
lines changed

14 files changed

+815
-162
lines changed

Diff for: pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -2441,6 +2441,13 @@ LintCode.unrelated_type_equality_checks_in_expression:
24412441
status: needsEvaluation
24422442
LintCode.unrelated_type_equality_checks_in_pattern:
24432443
status: needsEvaluation
2444+
LintCode.unsafe_variance:
2445+
status: noFix
2446+
notes: |-
2447+
A type like `X Function([X])` could be replaced by `X Function()`,
2448+
or by `X Function([Never])`, or by `Function`, depending on the
2449+
situation and the preferences of the developer. There is no
2450+
universally applicable fix.
24442451
LintCode.use_build_context_synchronously_async_use:
24452452
status: noFix
24462453
LintCode.use_build_context_synchronously_wrong_mounted:

Diff for: pkg/analyzer/tool/diagnostics/diagnostics.md

+111
Original file line numberDiff line numberDiff line change
@@ -29147,6 +29147,117 @@ bool f(String s) {
2914729147
}
2914829148
```
2914929149

29150+
### unsafe_variance
29151+
29152+
_This type is unsafe: a type parameter occurs in a non-covariant position._
29153+
29154+
#### Description
29155+
29156+
This lint warns against declaring non-covariant members.
29157+
29158+
An instance variable whose type contains a type parameter of the
29159+
enclosing class, mixin, or enum in a non-covariant position is
29160+
likely to cause run-time failures due to failing type
29161+
checks. For example, in `class C<X> {...}`, an instance variable
29162+
of the form `void Function(X) myVariable;` may cause this kind
29163+
of run-time failure.
29164+
29165+
The same is true for a getter or method whose return type has a
29166+
non-covariant occurrence of a type parameter of the enclosing
29167+
declaration.
29168+
29169+
This lint flags this kind of member declaration.
29170+
29171+
#### Example
29172+
29173+
**BAD:**
29174+
```dart
29175+
class C<X> {
29176+
final bool Function([!X!]) fun; // LINT
29177+
C(this.fun);
29178+
}
29179+
29180+
void main() {
29181+
C<num> c = C<int>((i) => i.isEven);
29182+
c.fun(10); // Throws.
29183+
}
29184+
```
29185+
29186+
The problem is that `X` occurs as a parameter type in the type
29187+
of `fun`.
29188+
29189+
#### Common fixes
29190+
29191+
One way to reduce the potential for run-time type errors is to
29192+
ensure that the non-covariant member `fun` is _only_ used on
29193+
`this`. We cannot strictly enforce this, but we can make it
29194+
private and add a forwarding method `fun` such that we can check
29195+
locally in the same library that this constraint is satisfied:
29196+
29197+
**BETTER:**
29198+
```dart
29199+
class C<X> {
29200+
// ignore: unsafe_variance
29201+
final bool Function(X) _fun;
29202+
bool fun(X x) => _fun(x);
29203+
C(this._fun);
29204+
}
29205+
29206+
void main() {
29207+
C<num> c = C<int>((i) => i.isEven);
29208+
c.fun(10); // Succeeds.
29209+
}
29210+
```
29211+
29212+
A fully safe approach requires a feature that Dart does not yet
29213+
have, namely statically checked variance. With that, we could
29214+
specify that the type parameter `X` is invariant (`inout X`).
29215+
29216+
It is possible to emulate invariance without support for statically
29217+
checked variance. This puts some restrictions on the creation of
29218+
subtypes, but faithfully provides the typing that `inout` would
29219+
give:
29220+
29221+
**GOOD:**
29222+
```dart
29223+
typedef Inv<X> = X Function(X);
29224+
typedef C<X> = _C<X, Inv<X>>;
29225+
29226+
class _C<X, Invariance extends Inv<X>> {
29227+
// ignore: unsafe_variance
29228+
final bool Function(X) fun; // Safe!
29229+
_C(this.fun);
29230+
}
29231+
29232+
void main() {
29233+
C<int> c = C<int>((i) => i.isEven);
29234+
c.fun(10); // Succeeds.
29235+
}
29236+
```
29237+
29238+
With this approach, `C<int>` is not a subtype of `C<num>`, so
29239+
`c` must have a different declared type.
29240+
29241+
Another possibility is to declare the variable to have a safe
29242+
but more general type. It is then safe to use the variable
29243+
itself, but every invocation will have to be checked at run
29244+
time:
29245+
29246+
**HONEST:**
29247+
```dart
29248+
class C<X> {
29249+
final bool Function(Never) fun;
29250+
C(this.fun);
29251+
}
29252+
29253+
void main() {
29254+
C<num> c = C<int>((int i) => i.isEven);
29255+
var cfun = c.fun; // Local variable, enables promotion.
29256+
if (cfun is bool Function(int)) cfun(10); // Succeeds.
29257+
if (cfun is bool Function(bool)) cfun(true); // Not called.
29258+
}
29259+
```
29260+
2915029261
### use_build_context_synchronously
2915129262

2915229263
_Don't use 'BuildContext's across async gaps, guarded by an unrelated 'mounted'

Diff for: pkg/linter/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- new _(experimental)_ lint: `omit_obvious_property_types`
77
- new _(experimental)_ lint: `specify_nonobvious_property_types`
88
- removed lint: `unsafe_html`
9+
- new _(experimental)_ lint: `unsafe_variance`
910

1011
# 3.6.0
1112

Diff for: pkg/linter/example/all.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ linter:
206206
- unnecessary_to_list_in_spreads
207207
- unreachable_from_main
208208
- unrelated_type_equality_checks
209+
- unsafe_variance
209210
- use_build_context_synchronously
210211
- use_colored_box
211212
- use_decorated_box

Diff for: pkg/linter/lib/src/lint_codes.g.dart

+8
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,14 @@ class LinterLintCode extends LintCode {
17621762
uniqueName: 'unrelated_type_equality_checks_in_pattern',
17631763
);
17641764

1765+
static const LintCode unsafe_variance = LinterLintCode(
1766+
LintNames.unsafe_variance,
1767+
"This type is unsafe: a type parameter occurs in a non-covariant position.",
1768+
correctionMessage:
1769+
"Try using a more general type that doesn't contain any type "
1770+
"parameters in such a position.",
1771+
);
1772+
17651773
static const LintCode
17661774
use_build_context_synchronously_async_use = LinterLintCode(
17671775
LintNames.use_build_context_synchronously,

Diff for: pkg/linter/lib/src/lint_names.g.dart

+2
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@ abstract final class LintNames {
546546

547547
static const String unsafe_html = 'unsafe_html';
548548

549+
static const String unsafe_variance = 'unsafe_variance';
550+
549551
static const String use_build_context_synchronously =
550552
'use_build_context_synchronously';
551553

Diff for: pkg/linter/lib/src/rules.dart

+2
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ import 'rules/unnecessary_to_list_in_spreads.dart';
225225
import 'rules/unreachable_from_main.dart';
226226
import 'rules/unrelated_type_equality_checks.dart';
227227
import 'rules/unsafe_html.dart';
228+
import 'rules/unsafe_variance.dart';
228229
import 'rules/use_build_context_synchronously.dart';
229230
import 'rules/use_colored_box.dart';
230231
import 'rules/use_decorated_box.dart';
@@ -470,6 +471,7 @@ void registerLintRules() {
470471
..registerLintRule(UnreachableFromMain())
471472
..registerLintRule(UnrelatedTypeEqualityChecks())
472473
..registerLintRule(UnsafeHtml())
474+
..registerLintRule(UnsafeVariance())
473475
..registerLintRule(UseBuildContextSynchronously())
474476
..registerLintRule(UseColoredBox())
475477
..registerLintRule(UseDecoratedBox())

0 commit comments

Comments
 (0)