Skip to content

Commit e88f081

Browse files
eernstgCommit Queue
authored andcommitted
Shortened the documentation of unsafe_variance
It is now 69 lines, and not the longest. Change-Id: Ibf618c2356f0bcf90ab3a4f5661a094f53382b22 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/395523 Commit-Queue: Erik Ernst <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent b196577 commit e88f081

File tree

3 files changed

+123
-153
lines changed

3 files changed

+123
-153
lines changed

pkg/analyzer/tool/diagnostics/diagnostics.md

Lines changed: 46 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -29153,111 +29153,83 @@ _This type is unsafe: a type parameter occurs in a non-covariant position._
2915329153

2915429154
#### Description
2915529155

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.
29156+
The analyzer produces this diagnostic when an instance member has a result
29157+
type which is [contravariant or invariant](https://dart.dev/resources/glossary#variance)
29158+
in a type parameter of the enclosing declaration. The result type of a
29159+
variable is its type, and the result type of a getter or method is its
29160+
return type. This lint warns against such members because they are likely
29161+
to cause a failing type check at run time, with no static warning or error
29162+
at the call site.
2917029163

2917129164
#### Example
2917229165

29173-
**BAD:**
29166+
The following code produces this diagnostic because `X` occurs
29167+
as a parameter type in the type of `f`, which is a
29168+
contravariant occurrence of this type parameter:
29169+
2917429170
```dart
2917529171
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.
29172+
bool Function([!X!]) f;
29173+
C(this.f);
2918329174
}
2918429175
```
2918529176

29186-
The problem is that `X` occurs as a parameter type in the type
29187-
of `fun`.
29177+
This is unsafe: If `c` has static type `C<num>` and run-time type `C<int>`
29178+
then `c.f` will throw. Hence, every invocation `c.f(a)` will also throw,
29179+
even in the case where `a` has a correct type as an argument to `c.f`.
2918829180

2918929181
#### Common fixes
2919029182

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:
29183+
If the linted member is or can be private then you may be able
29184+
to enforce that it is never accessed on any other receiver than `this`.
29185+
This is sufficient to ensure that that the run-time type error does not
29186+
occur. For example:
2919629187

29197-
**BETTER:**
2919829188
```dart
2919929189
class C<X> {
29190+
// NB: Ensure manually that `_f` is only accessed on `this`.
2920029191
// ignore: unsafe_variance
29201-
final bool Function(X) _fun;
29202-
bool fun(X x) => _fun(x);
29203-
C(this._fun);
29204-
}
29192+
bool Function(X) _f;
2920529193

29206-
void main() {
29207-
C<num> c = C<int>((i) => i.isEven);
29208-
c.fun(10); // Succeeds.
29194+
C(this._f);
29195+
29196+
// We can write a forwarding method to allow clients to call `_f`.
29197+
bool f(X x) => _f(x);
2920929198
}
2921029199
```
2921129200

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:
29201+
You can eliminate the unsafe variance by using a more general type for
29202+
the linted member. In this case you may need to check the run-time type
29203+
and perform a downcast at call sites.
2922029204

29221-
**GOOD:**
2922229205
```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.
29206+
class C<X> {
29207+
bool Function(Never) f;
29208+
C(this.f);
2923529209
}
2923629210
```
2923729211

29238-
With this approach, `C<int>` is not a subtype of `C<num>`, so
29239-
`c` must have a different declared type.
29212+
If `c` has static type `C<num>` then you may test the type. For example,
29213+
`c.f is bool Function(num)`. You may safely call it with an argument of
29214+
type `num` if it has that type.
2924029215

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:
29216+
You can also eliminate the unsafe variance by using a much more general
29217+
type like `Function`, which is essentially the type `dynamic` for
29218+
functions.
2924529219

29246-
**HONEST:**
2924729220
```dart
2924829221
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.
29222+
Function f;
29223+
C(this.f);
2925829224
}
2925929225
```
2926029226

29227+
This will make `c.f(a)` dynamically safe: It will throw if and only if the
29228+
argument `a` does not have the type required by the function. This is
29229+
better than the original version because it will not throw because of a
29230+
mismatched static type. It only throws when it _must_ throw for soundness
29231+
reasons.
29232+
2926129233
### use_build_context_synchronously
2926229234

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

pkg/linter/messages.yaml

Lines changed: 76 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -13195,8 +13195,83 @@ LintCode:
1319513195
documentation: |-
1319613196
#### Description
1319713197
13198-
This lint warns against declaring non-covariant members.
13198+
The analyzer produces this diagnostic when an instance member has a result
13199+
type which is [contravariant or invariant](https://dart.dev/resources/glossary#variance)
13200+
in a type parameter of the enclosing declaration. The result type of a
13201+
variable is its type, and the result type of a getter or method is its
13202+
return type. This lint warns against such members because they are likely
13203+
to cause a failing type check at run time, with no static warning or error
13204+
at the call site.
1319913205
13206+
#### Example
13207+
13208+
The following code produces this diagnostic because `X` occurs
13209+
as a parameter type in the type of `f`, which is a
13210+
contravariant occurrence of this type parameter:
13211+
13212+
```dart
13213+
class C<X> {
13214+
bool Function([!X!]) f;
13215+
C(this.f);
13216+
}
13217+
```
13218+
13219+
This is unsafe: If `c` has static type `C<num>` and run-time type `C<int>`
13220+
then `c.f` will throw. Hence, every invocation `c.f(a)` will also throw,
13221+
even in the case where `a` has a correct type as an argument to `c.f`.
13222+
13223+
#### Common fixes
13224+
13225+
If the linted member is or can be private then you may be able
13226+
to enforce that it is never accessed on any other receiver than `this`.
13227+
This is sufficient to ensure that that the run-time type error does not
13228+
occur. For example:
13229+
13230+
```dart
13231+
class C<X> {
13232+
// NB: Ensure manually that `_f` is only accessed on `this`.
13233+
// ignore: unsafe_variance
13234+
bool Function(X) _f;
13235+
13236+
C(this._f);
13237+
13238+
// We can write a forwarding method to allow clients to call `_f`.
13239+
bool f(X x) => _f(x);
13240+
}
13241+
```
13242+
13243+
You can eliminate the unsafe variance by using a more general type for
13244+
the linted member. In this case you may need to check the run-time type
13245+
and perform a downcast at call sites.
13246+
13247+
```dart
13248+
class C<X> {
13249+
bool Function(Never) f;
13250+
C(this.f);
13251+
}
13252+
```
13253+
13254+
If `c` has static type `C<num>` then you may test the type. For example,
13255+
`c.f is bool Function(num)`. You may safely call it with an argument of
13256+
type `num` if it has that type.
13257+
13258+
You can also eliminate the unsafe variance by using a much more general
13259+
type like `Function`, which is essentially the type `dynamic` for
13260+
functions.
13261+
13262+
```dart
13263+
class C<X> {
13264+
Function f;
13265+
C(this.f);
13266+
}
13267+
```
13268+
13269+
This will make `c.f(a)` dynamically safe: It will throw if and only if the
13270+
argument `a` does not have the type required by the function. This is
13271+
better than the original version because it will not throw because of a
13272+
mismatched static type. It only throws when it _must_ throw for soundness
13273+
reasons.
13274+
deprecatedDetails: |-
1320013275
An instance variable whose type contains a type parameter of the
1320113276
enclosing class, mixin, or enum in a non-covariant position is
1320213277
likely to cause run-time failures due to failing type
@@ -13210,8 +13285,6 @@ LintCode:
1321013285
1321113286
This lint flags this kind of member declaration.
1321213287
13213-
#### Example
13214-
1321513288
**BAD:**
1321613289
```dart
1321713290
class C<X> {
@@ -13228,8 +13301,6 @@ LintCode:
1322813301
The problem is that `X` occurs as a parameter type in the type
1322913302
of `fun`.
1323013303
13231-
#### Common fixes
13232-
1323313304
One way to reduce the potential for run-time type errors is to
1323413305
ensure that the non-covariant member `fun` is _only_ used on
1323513306
`this`. We cannot strictly enforce this, but we can make it
@@ -13299,79 +13370,6 @@ LintCode:
1329913370
if (cfun is bool Function(bool)) cfun(true); // Not called.
1330013371
}
1330113372
```
13302-
deprecatedDetails: |-
13303-
Don't declare non-covariant members.
13304-
13305-
An instance variable whose type contains a type parameter of the
13306-
enclosing class, mixin, or enum in a non-covariant position is
13307-
likely to cause run-time failures due to failing type
13308-
checks. For example, in `class C<X> {...}`, an instance variable
13309-
of the form `void Function(X) myVariable;` may cause this kind
13310-
of run-time failure.
13311-
13312-
The same is true for a getter or method whose return type has a
13313-
non-covariant occurrence of a type parameter of the enclosing
13314-
declaration.
13315-
13316-
This lint flags this kind of member declaration.
13317-
13318-
**BAD:**
13319-
```dart
13320-
class C<X> {
13321-
final bool Function(X) fun; // LINT
13322-
C(this.fun);
13323-
}
13324-
13325-
void main() {
13326-
C<num> c = C<int>((i) => i.isEven);
13327-
c.fun(10); // Throws.
13328-
}
13329-
```
13330-
13331-
The problem is that `X` occurs as a parameter type in the type
13332-
of `fun`. A better approach is to ensure that the non-covariant
13333-
member `fun` is _only_ used on `this`. We cannot strictly
13334-
enforce this, but we can make it private and add a forwarding
13335-
method `fun`:
13336-
13337-
**BETTER:**
13338-
```dart
13339-
class C<X> {
13340-
// ignore: unsafe_variance
13341-
final bool Function(X) _fun;
13342-
bool fun(X x) => _fun(x);
13343-
C(this.fun);
13344-
}
13345-
13346-
void main() {
13347-
C<num> c = C<int>((i) => i.isEven);
13348-
c.fun(10); // Succeeds.
13349-
}
13350-
```
13351-
13352-
A fully safe approach requires a feature that Dart does not yet
13353-
have, namely statically checked variance. With that, we could
13354-
specify that the type parameter `X` is invariant (`inout X`).
13355-
13356-
Another possibility is to declare the variable to have a safe
13357-
but more general type. It is then safe to use the variable
13358-
itself, but every invocation will have to be checked at run
13359-
time:
13360-
13361-
**HONEST:**
13362-
```dart
13363-
class C<X> {
13364-
final bool Function(Never) fun;
13365-
C(this.fun);
13366-
}
13367-
13368-
void main() {
13369-
C<num> c = C<int>((i) => i.isEven);
13370-
var cfun = c.fun; // Local variable, enables promotion.
13371-
if (cfun is bool Function(int)) cfun(10); // Succeeds.
13372-
if (cfun is bool Function(bool)) cfun(true); // Not called.
13373-
}
13374-
```
1337513373
use_build_context_synchronously_async_use:
1337613374
sharedName: use_build_context_synchronously
1337713375
problemMessage: "Don't use 'BuildContext's across async gaps."

pkg/linter/tool/machine/rules.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2938,7 +2938,7 @@
29382938
"incompatible": [],
29392939
"sets": [],
29402940
"fixStatus": "noFix",
2941-
"details": "Don't declare non-covariant members.\n\nAn instance variable whose type contains a type parameter of the\nenclosing class, mixin, or enum in a non-covariant position is\nlikely to cause run-time failures due to failing type\nchecks. For example, in `class C<X> {...}`, an instance variable\nof the form `void Function(X) myVariable;` may cause this kind\nof run-time failure.\n\nThe same is true for a getter or method whose return type has a\nnon-covariant occurrence of a type parameter of the enclosing\ndeclaration.\n\nThis lint flags this kind of member declaration.\n\n**BAD:**\n```dart\nclass C<X> {\n final bool Function(X) fun; // LINT\n C(this.fun);\n}\n\nvoid main() {\n C<num> c = C<int>((i) => i.isEven);\n c.fun(10); // Throws.\n}\n```\n\nThe problem is that `X` occurs as a parameter type in the type\nof `fun`. A better approach is to ensure that the non-covariant\nmember `fun` is _only_ used on `this`. We cannot strictly\nenforce this, but we can make it private and add a forwarding\nmethod `fun`:\n\n**BETTER:**\n```dart\nclass C<X> {\n // ignore: unsafe_variance\n final bool Function(X) _fun;\n bool fun(X x) => _fun(x);\n C(this.fun);\n}\n\nvoid main() {\n C<num> c = C<int>((i) => i.isEven);\n c.fun(10); // Succeeds.\n}\n```\n\nA fully safe approach requires a feature that Dart does not yet\nhave, namely statically checked variance. With that, we could\nspecify that the type parameter `X` is invariant (`inout X`).\n\nAnother possibility is to declare the variable to have a safe\nbut more general type. It is then safe to use the variable\nitself, but every invocation will have to be checked at run\ntime:\n\n**HONEST:**\n```dart\nclass C<X> {\n final bool Function(Never) fun;\n C(this.fun);\n}\n\nvoid main() {\n C<num> c = C<int>((i) => i.isEven);\n var cfun = c.fun; // Local variable, enables promotion.\n if (cfun is bool Function(int)) cfun(10); // Succeeds.\n if (cfun is bool Function(bool)) cfun(true); // Not called.\n}\n```",
2941+
"details": "An instance variable whose type contains a type parameter of the\nenclosing class, mixin, or enum in a non-covariant position is\nlikely to cause run-time failures due to failing type\nchecks. For example, in `class C<X> {...}`, an instance variable\nof the form `void Function(X) myVariable;` may cause this kind\nof run-time failure.\n\nThe same is true for a getter or method whose return type has a\nnon-covariant occurrence of a type parameter of the enclosing\ndeclaration.\n\nThis lint flags this kind of member declaration.\n\n**BAD:**\n```dart\nclass C<X> {\n final bool Function([!X!]) fun; // LINT\n C(this.fun);\n}\n\nvoid main() {\n C<num> c = C<int>((i) => i.isEven);\n c.fun(10); // Throws.\n}\n```\n\nThe problem is that `X` occurs as a parameter type in the type\nof `fun`.\n\nOne way to reduce the potential for run-time type errors is to\nensure that the non-covariant member `fun` is _only_ used on\n`this`. We cannot strictly enforce this, but we can make it\nprivate and add a forwarding method `fun` such that we can check\nlocally in the same library that this constraint is satisfied:\n\n**BETTER:**\n```dart\nclass C<X> {\n // ignore: unsafe_variance\n final bool Function(X) _fun;\n bool fun(X x) => _fun(x);\n C(this._fun);\n}\n\nvoid main() {\n C<num> c = C<int>((i) => i.isEven);\n c.fun(10); // Succeeds.\n}\n```\n\nA fully safe approach requires a feature that Dart does not yet\nhave, namely statically checked variance. With that, we could\nspecify that the type parameter `X` is invariant (`inout X`).\n\nIt is possible to emulate invariance without support for statically\nchecked variance. This puts some restrictions on the creation of\nsubtypes, but faithfully provides the typing that `inout` would\ngive:\n\n**GOOD:**\n```dart\ntypedef Inv<X> = X Function(X);\ntypedef C<X> = _C<X, Inv<X>>;\n\nclass _C<X, Invariance extends Inv<X>> {\n // ignore: unsafe_variance\n final bool Function(X) fun; // Safe!\n _C(this.fun);\n}\n\nvoid main() {\n C<int> c = C<int>((i) => i.isEven);\n c.fun(10); // Succeeds.\n}\n```\n\nWith this approach, `C<int>` is not a subtype of `C<num>`, so\n`c` must have a different declared type.\n\nAnother possibility is to declare the variable to have a safe\nbut more general type. It is then safe to use the variable\nitself, but every invocation will have to be checked at run\ntime:\n\n**HONEST:**\n```dart\nclass C<X> {\n final bool Function(Never) fun;\n C(this.fun);\n}\n\nvoid main() {\n C<num> c = C<int>((int i) => i.isEven);\n var cfun = c.fun; // Local variable, enables promotion.\n if (cfun is bool Function(int)) cfun(10); // Succeeds.\n if (cfun is bool Function(bool)) cfun(true); // Not called.\n}\n```",
29422942
"sinceDartSdk": "3.7"
29432943
},
29442944
{

0 commit comments

Comments
 (0)