Skip to content

Commit 82d6929

Browse files
authored
Merge pull request #521 from srawlins/add-strict-raw-types
Add spec for strict-raw-types
2 parents 1a85be7 + 18ed689 commit 82d6929

File tree

1 file changed

+125
-0
lines changed

1 file changed

+125
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Strict raw types static analysis option
2+
3+
This document specifies the "Strict raw types" mode enabled with a static
4+
analysis option. As a static analysis option, we only intend to implement this
5+
feature in the Dart Analyzer. Under this feature, a type with omitted type
6+
argument(s) is defined as a "raw type." Dart fills in such type arguments with
7+
their bounds, or `dynamic` if there are no bounds.
8+
9+
## Enabling strict raw types
10+
11+
To enable strict raw types, set the `strict-raw-types` option to `true`, under
12+
the Analyzer's `language` section:
13+
14+
```yaml
15+
analyzer:
16+
language:
17+
strict-raw-types: true
18+
```
19+
20+
## Motivation
21+
22+
It is possible to write Dart code that passes all static type analysis and
23+
compile-time checks that is guaranteed to result in runtime errors. Common
24+
examples include runtime type errors, and no-such-method errors. Developers are
25+
often surprised to see such errors at runtime, which look like they should be
26+
caught at compile time.
27+
28+
The strict raw types mode aims to highlight such code during static analysis.
29+
We can look at some common examples:
30+
31+
```dart
32+
void main() {
33+
List a = [1, 2, 3];
34+
}
35+
```
36+
37+
Developers often think that inference fills in the type of `a` from the right
38+
side of the assignment. It may look like `a` has the type `List<int>`. But Dart
39+
fills in omitted type arguments, like `E` on `List`, with `dynamic` (or the
40+
corresponding type parameter's bound); `List a;` is purely a shorthand for
41+
`List<dynamic> a;`. Inference then flows from `a` onto the expression on the
42+
right side of the assignment. This is more obvious in another example:
43+
44+
```dart
45+
void main() {
46+
List a = [1, 2, 3]..forEach((e) => print(e.length));
47+
var b = [4, 5, 6]..forEach((e) => print(e.length));
48+
}
49+
```
50+
51+
The first statement does not result in any static analysis errors, since the
52+
type of the list is inferred to be `List<dynamic>`. Instead, the code results
53+
in a runtime no-such-method error, when the `length` getter is called on an
54+
`int`.
55+
56+
The second statement, however, allows the type of the list to be inferred from
57+
its elements, as `List<int>`, which results in a static analysis type error,
58+
which notes that the getter `length` is not defined on `int`.
59+
60+
Raw types can also lead to unintended dynamic dispatch:
61+
62+
```dart
63+
void main() {
64+
List a = [1, 2, 3];
65+
a.forEach((e) => print(e.isEven));
66+
}
67+
```
68+
69+
The developer likely does not realize that the parameter `e` of the callback is
70+
`dynamic`, and that the call to `isEven` is a dynamic dispatch.
71+
72+
Reporting strict raw types encourages developers to fill in omitted type
73+
arguments, hopefully with something other than `dynamic`. In cases where the
74+
only good type is `dynamic`, then including it as an explicit type argument
75+
avoids the raw type, and makes the dynamic behavior more explicit in the code.
76+
77+
## Conditions for a raw type Hint
78+
79+
Any raw type results in a raw type Hint, except under the following conditions:
80+
81+
* the raw type is on the right side of an `as` or an `is` expression
82+
* the raw type is defined by a class, mixin, or typedef annotated with the
83+
`optionalTypeArgs` annotation from the meta package.
84+
85+
## Examples
86+
87+
This section is non-normative. It does not represent an exhaustive selection of
88+
conditions for a raw type Hint.
89+
90+
```dart
91+
import 'package:meta/meta.dart';
92+
93+
List l1 = [1, 2, 3]; // Hint
94+
List<List> l2 = [1, 2, 3]; // Hint
95+
final f1 = Future.value(7); // OK
96+
97+
fn1(Map map) => print(map); // Hint
98+
Map fn2() => {}; // Hint
99+
100+
class C1 {
101+
List l3 = [1, 2, 3]; // Hint
102+
print([] is Set); // OK
103+
104+
m(Map map) => print(map); // Hint
105+
}
106+
107+
class C2<T> {}
108+
109+
class C3 extends C2 {} // Hint
110+
class C4 implements C2 {} // Hint
111+
class C5 with C2 {} // Hint
112+
113+
typedef Callback<T> = void Function(T);
114+
115+
Callback = (int n) => print(n); // Hint
116+
117+
@optionalTypeArgs
118+
class C6<T> {}
119+
120+
C6 a; // OK
121+
List<C6> b; // OK
122+
C6<List> c; // Hint
123+
124+
class C7 extends C6 {} // OK
125+
```

0 commit comments

Comments
 (0)