OLD | NEW |
---|---|
(Empty) | |
1 # Strong Mode in the Dart Dev Compiler | |
2 | |
3 ## Overview | |
4 | |
5 In the Dart Dev Compiler (DDC), [static strong mode](STATIC_SAFETY.md) checks ar e augmented with stricter runtime behavior. Together, they enforce the soundnes s of Dart type annotations. | |
6 | |
7 In general, and in contrast to Dart's checked mode, most safety is enforced stat ically, at analysis time. DDC exploits this to generate relatively few runtime checks while still providing stronger guarantees than checked mode. | |
8 | |
9 In particular, DDC adds the following: | |
10 | |
11 - Stricter (but fewer) runtime type checks | |
12 - Reified type narrowing | |
13 - Restricted `is`/`as` checks | |
14 | |
15 In all these cases, DDC (with static checks) is stricter than standard checked m ode (or production mode). It may reject (either statically or at runtime) progr ams that run correctly in checked mode (similar to how checked mode may reject p rograms that run in production mode). | |
16 | |
17 On the other hand, programs that statically check and run correctly in DDC shoul d also run the same in checked mode. A caveat to note is that mirrors (or `runt imeType`) may show a more narrow type in DDC (though, in practice, programmers a re discouraged from using these features for performance / code size reasons). | |
18 | |
19 ## Runtime checks | |
20 | |
21 In practice, strong mode enforces most type annotations at compile time, and, th us, requires less work at runtime to enforce safety. Consider the following Dar t code: | |
22 | |
23 ```dart | |
24 String foo(Map<int, String> map, int x) { | |
25 return map[x.abs()]; | |
26 } | |
27 ``` | |
28 | |
29 Strong mode will enforce that the function `foo` is only invoked in a manner con sistent with its signature. DDC - which assumes strong mode static checking - i nserts no further runtime checks. In contrast, standard Dart checked mode would check the type of the parameters - `map` and `x` - along with the type of the r eturn value at runtime on every invocation of `foo`. Even Dart production mode, depending on the implementation and its ability to optimize, may require simila r checking to dynamically dispatch the map lookup and the method call in the bod y of `foo`. | |
Bob Nystrom
2016/02/02 23:02:16
" - " -> "—".
vsm
2016/02/03 00:50:04
Done.
| |
30 | |
31 Nevertheless, there are cases where DDC still requires runtime checks. (Note: D DC may eventually provide a mode to elide these checks, but this would violate s oundness and is beyond the scope of this document.) | |
32 | |
33 ### Implicit casts | |
34 | |
35 Dart has flexible assignability rules. Programmers are not required to explicit ly cast from supertypes to subtypes. For example, the following is valid Dart: | |
36 | |
37 ```dart | |
38 Object o = ...; | |
39 String s = o; // Implicit downcast | |
40 String s2 = s.substring(1); | |
41 ``` | |
42 | |
43 The assignment to `s` is an implicit downcast from `Object` to `String` and trig gers a runtime check in DDC to ensure it is correct. | |
44 | |
45 Note that checked mode would also perform this runtime test. Unlike checked mod e, DDC would not require a check on the assignment to `s2` - this type is establ ished statically. | |
46 | |
47 ### Inferred variables | |
48 | |
49 Dart's inference may narrow the static type of certain variables. If the variab le is mutable, DDC will enforce the narrower type at runtime when necessary. | |
Bob Nystrom
2016/02/02 23:02:16
"will enforce" -> "enforces".
vsm
2016/02/03 00:50:04
Done.
| |
50 | |
51 In the following example, strong mode will infer of the type of the local variab le `y` as an `int`: | |
52 | |
53 ```dart | |
54 int bar(Object x) { | |
55 var y = 0; | |
56 if (x != null) { | |
57 y = x; | |
58 } | |
59 return y.abs(); | |
60 } | |
61 ``` | |
62 | |
63 This allows it to, for example, static verify the call to `y.abs()` and determin e that it returns an `int`. However, the parameter `x` is typed as `Object` and the assignment from `x` to `y` now requires a type check to ensure that `y` is only assigned an `int`. | |
64 | |
65 Note, strong mode and DDC are conservative by enforcing a tighter type than requ ired by standard Dart checked mode. For example, checked mode would accept a no n-`int` `x` with an `abs` method that happened to return an `int`. In strong mo de, a programmer would have to explicitly opt into this behavior by annotating ` y` as an `Object` or `dynamic`. | |
66 | |
67 ### Covariant generics | |
68 | |
69 Strong mode preserves the covariance of Dart's generic classes. To support this soundly, DDC injects runtime checks on parameters in method invocations whose t ype is a class type parameter. Consider the call to `baz` in the parameterized class `A`: | |
70 | |
71 ```dart | |
72 class A<T> { | |
73 T baz(T x, int y) => x; | |
74 } | |
75 | |
76 void foo(A<Object> a) { | |
77 a.baz(42, 38); | |
78 } | |
79 | |
80 void main() { | |
81 var aString = new A<String>(); | |
82 foo(aString); | |
83 } | |
84 ``` | |
85 | |
86 Statically, sound mode will not generate an error or warning on this code. The call to `baz` in `foo` is statically valid as `42` is an `Object` (as required b y the static type of `a`). However, the runtime type of `a` in this example is the narrower `A<String>`. At runtime, when baz is executed, DDC will check that the type of `x` matches the reified type parameter and, in this example, fail. | |
87 | |
88 Note, only `x` requires a runtime check. Unlike checked mode, no runtime check is required for `y` or the return value. Both are statically verified. | |
89 | |
90 ### Dynamic operations | |
91 | |
92 Strong mode allows programmers to explicitly use `dynamic` as a type. It also a llows programmers to omit types, and in some of these cases inference may fall b ack on `dynamic` if it cannot determine a static type. In these cases, DDC inse rts runtime checks (typically in the form of runtime helper calls). | |
93 | |
94 For example, in the following: | |
95 | |
96 ```dart | |
97 int foo(int x) => x + 1; | |
98 | |
99 void main() { | |
100 dynamic bar = foo; | |
101 bar("hello"); // DDC runtime error | |
102 } | |
103 ``` | |
104 | |
105 `foo` (via `bar`) is incorrectly invoked on a `String`. There is no static erro r as `bar` is typed `dynamic`. Instead DDC, performs extra runtime checking on the invocation of `bar`. In this case, it would generate a runtime type error. Note, if the type of `bar` had been omitted, it would have been inferred, and t he error would have been reported statically. | |
106 | |
107 Nevertheless, there are situations where programmers may prefer a dynamic type f or flexibility. | |
108 | |
109 ## Runtime type Narrowing | |
110 | |
111 Strong mode statically infers tighter types for functions and generics. In DDC, this is reflected in the reified type at runtime. This allows DDC to enforce t he stricter type soundly at runtime when necessary. | |
112 | |
113 In particular, this means that DDC may have a stricter concrete runtime type tha n other Dart implementations for generic classes and functions. The DDC type wi ll always be a subtype. | |
114 | |
115 This will impact execution in the following ways: | |
116 - DDC may trigger runtime errors where checked mode is forgiving. | |
117 - Code that uses reflection may observe a narrower type in DDC. | |
118 | |
119 ### Allocation inference | |
120 | |
121 When strong infers a narrower type for a closure literal or other allocation exp ression, DDC reifies this narrower type at runtime. As a result, it can soundly enforce typing errors at runtime. | |
122 | |
123 The following is an example of where static checking fails to catch a typing err or: | |
124 | |
125 ```dart | |
126 apply(int g(x), y) { | |
127 print(g(y)); | |
128 } | |
129 | |
130 typedef int Int2Int(int x); | |
131 | |
132 void main() { | |
133 Int2Int f = (x) => x + x; | |
134 apply(f, "hello"); | |
135 } | |
136 ``` | |
137 | |
138 A programmer examining `apply` would reasonably expect it to print an `int` valu e. The analyzer (with or without strong mode) fails to report a problem. Stand ard Dart checked simply prints `"hellohello"`. In DDC, however, a runtime error is thrown on the application of `g` in `apply`. The closure literal assigned t o `f` in `main` is reified as an `int -> int`, and DDC enforces this at runtime. | |
139 | |
140 In this example, if `apply` and its parameters were fully typed, strong mode wou ld report a static error, and DDC would impose no runtime check. | |
141 | |
142 ### Generic methods | |
143 | |
144 [Note: This is not yet implemented correctly.](https://github.com/dart-lang/dev_ compiler/issues/301) | |
145 | |
146 Similarly, DDC requires that [generic methods](GENERIC_METHODS.md) return the co rrect reified type. In strong mode, `Iterable.map` is a generic method. In DDC , `lengths` in `main` will have a reified type of `List<int>`. In `foo`, this w ill trigger a runtime error when a string is added to the list. | |
147 | |
148 ```dart | |
149 void foo(List l) { | |
150 l.add("a string"); | |
151 } | |
152 | |
153 void main() { | |
154 Iterable<String> list = <String>["hello", "world"]; | |
155 List<int> lengths = list.map((x) => x.length).toList(); | |
156 foo(lengths); | |
157 print(lengths[2]); | |
158 } | |
159 ``` | |
160 | |
161 Standard checked mode would print `"a string"` without error. | |
162 | |
163 ## Is / As restrictions | |
164 | |
165 In standard Dart, `is` and `as` runtime checks expose the unsoundness of the typ e system in certain cases. For example, consider: | |
166 | |
167 ```dart | |
168 var list = ["hello", "world"]; | |
169 if (list is List<int>) { | |
170 ... | |
171 } else if (list is List<String>) { | |
172 ... | |
173 } | |
174 ``` | |
175 | |
176 Perhaps surprisingly, the first test - `list is List<int>` - evaluates to true h ere. Such code is highly likely to be erroneous. | |
177 | |
178 Strong mode provides a stricter subtyping check and DDC enforces this at runtime . For compatibility with standard Dart semantics, however, DDC throws a runtime error when an `is` or `as` check would return a different answer with strong mo de typing semantics. | |
179 | |
180 In the example above, the first `is` check would generate a runtime error. | |
181 | |
182 Note, we are exploring making this a static error or warning in strong mode. In general, an expression: | |
183 | |
184 ```dart | |
185 x is T | |
186 ``` | |
187 | |
188 or | |
189 | |
190 ```dart | |
191 x as T | |
192 ``` | |
193 | |
194 is only guaranteed safe when `T` is a *ground type*: | |
195 | |
196 - A non-generic class type (e.g., `Object`, `String`, `int`, ...). | |
197 - A generic class type where all type parameters are implicitly or explicitly `d ynamic` (e.g., `List<dynamic>`, `Map`, …). | |
198 - A function type where the return type and all parameter types are `dynamic` (e .g., (`dynamic`, `dynamic`) -> `dynamic`, ([`dynamic`]) -> `dynamic`). | |
OLD | NEW |