OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library inferrer_visitor; | |
6 | |
7 import '../dart2jslib.dart' hide Selector, TypedSelector; | |
8 import '../dart_types.dart'; | |
9 import '../elements/elements.dart'; | |
10 import '../tree/tree.dart'; | |
11 import '../universe/universe.dart'; | |
12 import '../util/util.dart'; | |
13 import '../types/types.dart' show TypeMask; | |
14 import 'dart:collection' show IterableMixin; | |
15 | |
16 /** | |
17 * The interface [InferrerVisitor] will use when working on types. | |
18 */ | |
19 abstract class TypeSystem<T> { | |
20 T get dynamicType; | |
21 T get nullType; | |
22 T get intType; | |
23 T get uint31Type; | |
24 T get uint32Type; | |
25 T get positiveIntType; | |
26 T get doubleType; | |
27 T get numType; | |
28 T get boolType; | |
29 T get functionType; | |
30 T get listType; | |
31 T get constListType; | |
32 T get fixedListType; | |
33 T get growableListType; | |
34 T get mapType; | |
35 T get constMapType; | |
36 T get stringType; | |
37 T get typeType; | |
38 | |
39 T stringLiteralType(DartString value); | |
40 | |
41 T nonNullSubtype(ClassElement type); | |
42 T nonNullSubclass(ClassElement type); | |
43 T nonNullExact(ClassElement type); | |
44 T nonNullEmpty(); | |
45 bool isNull(T type); | |
46 Selector newTypedSelector(T receiver, Selector selector); | |
47 | |
48 T allocateList(T type, | |
49 Node node, | |
50 Element enclosing, | |
51 [T elementType, int length]); | |
52 | |
53 T allocateMap(T type, Node node, Element element, [List<T> keyType, | |
54 List<T> valueType]); | |
55 | |
56 T allocateClosure(Node node, Element element); | |
57 | |
58 /** | |
59 * Returns the least upper bound between [firstType] and | |
60 * [secondType]. | |
61 */ | |
62 T computeLUB(T firstType, T secondType); | |
63 | |
64 /** | |
65 * Returns the intersection between [T] and [annotation]. | |
66 * [isNullable] indicates whether the annotation implies a null | |
67 * type. | |
68 */ | |
69 T narrowType(T type, DartType annotation, {bool isNullable: true}); | |
70 | |
71 /** | |
72 * Returns a new type that unions [firstInput] and [secondInput]. | |
73 */ | |
74 T allocateDiamondPhi(T firstInput, T secondInput); | |
75 | |
76 /** | |
77 * Returns a new type for holding the potential types of [element]. | |
78 * [inputType] is the first incoming type of the phi. | |
79 */ | |
80 T allocatePhi(Node node, Local variable, T inputType); | |
81 | |
82 /** | |
83 * Simplies the phi representing [element] and of the type | |
84 * [phiType]. For example, if this phi has one incoming input, an | |
85 * implementation of this method could just return that incoming | |
86 * input type. | |
87 */ | |
88 T simplifyPhi(Node node, Local variable, T phiType); | |
89 | |
90 /** | |
91 * Adds [newType] as an input of [phiType]. | |
92 */ | |
93 T addPhiInput(Local variable, T phiType, T newType); | |
94 | |
95 /** | |
96 * Returns `true` if `selector` should be updated to reflect the new | |
97 * `receiverType`. | |
98 */ | |
99 bool selectorNeedsUpdate(T receiverType, Selector selector); | |
100 | |
101 /** | |
102 * Returns a new receiver type for this [selector] applied to | |
103 * [receiverType]. | |
104 */ | |
105 T refineReceiver(Selector selector, T receiverType); | |
106 | |
107 /** | |
108 * Returns the internal inferrer representation for [mask]. | |
109 */ | |
110 T getConcreteTypeFor(TypeMask mask); | |
111 } | |
112 | |
113 /** | |
114 * A variable scope holds types for variables. It has a link to a | |
115 * parent scope, but never changes the types in that parent. Instead, | |
116 * updates to locals of a parent scope are put in the current scope. | |
117 * The inferrer makes sure updates get merged into the parent scope, | |
118 * once the control flow block has been visited. | |
119 */ | |
120 class VariableScope<T> { | |
121 Map<Local, T> variables; | |
122 | |
123 /// The parent of this scope. Null for the root scope. | |
124 final VariableScope<T> parent; | |
125 | |
126 /// The [Node] that created this scope. | |
127 final Node block; | |
128 | |
129 VariableScope(this.block, [parent]) | |
130 : this.variables = null, | |
131 this.parent = parent; | |
132 | |
133 VariableScope.deepCopyOf(VariableScope<T> other) | |
134 : variables = other.variables == null | |
135 ? null | |
136 : new Map<Local, T>.from(other.variables), | |
137 block = other.block, | |
138 parent = other.parent == null | |
139 ? null | |
140 : new VariableScope<T>.deepCopyOf(other.parent); | |
141 | |
142 T operator [](Local variable) { | |
143 T result; | |
144 if (variables == null || (result = variables[variable]) == null) { | |
145 return parent == null ? null : parent[variable]; | |
146 } | |
147 return result; | |
148 } | |
149 | |
150 void operator []=(Local variable, T mask) { | |
151 assert(mask != null); | |
152 if (variables == null) { | |
153 variables = new Map<Local, T>(); | |
154 } | |
155 variables[variable] = mask; | |
156 } | |
157 | |
158 void forEachOwnLocal(void f(Local variable, T type)) { | |
159 if (variables == null) return; | |
160 variables.forEach(f); | |
161 } | |
162 | |
163 void forEachLocalUntilNode(Node node, | |
164 void f(Local variable, T type), | |
165 [Setlet<Local> seenLocals]) { | |
166 if (seenLocals == null) seenLocals = new Setlet<Local>(); | |
167 if (variables != null) { | |
168 variables.forEach((variable, type) { | |
169 if (seenLocals.contains(variable)) return; | |
170 seenLocals.add(variable); | |
171 f(variable, type); | |
172 }); | |
173 } | |
174 if (block == node) return; | |
175 if (parent != null) parent.forEachLocalUntilNode(node, f, seenLocals); | |
176 } | |
177 | |
178 void forEachLocal(void f(Local variable, T type)) { | |
179 forEachLocalUntilNode(null, f); | |
180 } | |
181 | |
182 bool updates(Local variable) { | |
183 if (variables == null) return false; | |
184 return variables.containsKey(variable); | |
185 } | |
186 | |
187 String toString() { | |
188 String rest = parent == null ? "null" : parent.toString(); | |
189 return '$variables $rest'; | |
190 } | |
191 } | |
192 | |
193 class FieldInitializationScope<T> { | |
194 final TypeSystem<T> types; | |
195 Map<Element, T> fields; | |
196 bool isThisExposed; | |
197 | |
198 FieldInitializationScope(this.types) : isThisExposed = false; | |
199 | |
200 FieldInitializationScope.internalFrom(FieldInitializationScope<T> other) | |
201 : types = other.types, | |
202 isThisExposed = other.isThisExposed; | |
203 | |
204 factory FieldInitializationScope.from(FieldInitializationScope<T> other) { | |
205 if (other == null) return null; | |
206 return new FieldInitializationScope<T>.internalFrom(other); | |
207 } | |
208 | |
209 void updateField(Element field, T type) { | |
210 if (isThisExposed) return; | |
211 if (fields == null) fields = new Map<Element, T>(); | |
212 fields[field] = type; | |
213 } | |
214 | |
215 T readField(Element field) { | |
216 return fields == null ? null : fields[field]; | |
217 } | |
218 | |
219 void forEach(void f(Element element, T type)) { | |
220 if (fields == null) return; | |
221 fields.forEach(f); | |
222 } | |
223 | |
224 void mergeDiamondFlow(FieldInitializationScope<T> thenScope, | |
225 FieldInitializationScope<T> elseScope) { | |
226 // Quick bailout check. If [isThisExposed] is true, we know the | |
227 // code following won't do anything. | |
228 if (isThisExposed) return; | |
229 if (elseScope == null || elseScope.fields == null) { | |
230 elseScope = this; | |
231 } | |
232 | |
233 thenScope.forEach((Element field, T type) { | |
234 T otherType = elseScope.readField(field); | |
235 if (otherType == null) return; | |
236 updateField(field, types.allocateDiamondPhi(type, otherType)); | |
237 }); | |
238 isThisExposed = thenScope.isThisExposed || elseScope.isThisExposed; | |
239 } | |
240 } | |
241 | |
242 /** | |
243 * Placeholder for inferred arguments types on sends. | |
244 */ | |
245 class ArgumentsTypes<T> extends IterableMixin<T> { | |
246 final List<T> positional; | |
247 final Map<String, T> named; | |
248 ArgumentsTypes(this.positional, named) | |
249 : this.named = (named == null || named.isEmpty) ? const {} : named { | |
250 assert(this.positional.every((T type) => type != null)); | |
251 assert(this.named.values.every((T type) => type != null)); | |
252 } | |
253 | |
254 int get length => positional.length + named.length; | |
255 | |
256 Iterator<T> get iterator => new ArgumentsTypesIterator(this); | |
257 | |
258 String toString() => "{ positional = $positional, named = $named }"; | |
259 | |
260 bool operator==(other) { | |
261 if (positional.length != other.positional.length) return false; | |
262 if (named.length != other.named.length) return false; | |
263 for (int i = 0; i < positional.length; i++) { | |
264 if (positional[i] != other.positional[i]) return false; | |
265 } | |
266 named.forEach((name, type) { | |
267 if (other.named[name] != type) return false; | |
268 }); | |
269 return true; | |
270 } | |
271 | |
272 int get hashCode => throw new UnsupportedError('ArgumentsTypes.hashCode'); | |
273 | |
274 bool hasNoArguments() => positional.isEmpty && named.isEmpty; | |
275 | |
276 bool hasOnePositionalArgumentThatMatches(bool f(T type)) { | |
277 return named.isEmpty && positional.length == 1 && f(positional[0]); | |
278 } | |
279 | |
280 void forEach(void f(T type)) { | |
281 positional.forEach(f); | |
282 named.values.forEach(f); | |
283 } | |
284 | |
285 bool every(bool f(T type)) { | |
286 return positional.every(f) && named.values.every(f); | |
287 } | |
288 | |
289 bool contains(T type) { | |
290 return positional.contains(type) || named.containsValue(type); | |
291 } | |
292 } | |
293 | |
294 class ArgumentsTypesIterator<T> implements Iterator<T> { | |
295 final Iterator<T> positional; | |
296 final Iterator<T> named; | |
297 bool _iteratePositional = true; | |
298 | |
299 ArgumentsTypesIterator(ArgumentsTypes<T> iteratee) | |
300 : positional = iteratee.positional.iterator, | |
301 named = iteratee.named.values.iterator; | |
302 | |
303 Iterator<T> get _currentIterator => _iteratePositional ? positional : named; | |
304 | |
305 T get current => _currentIterator.current; | |
306 | |
307 bool moveNext() { | |
308 if (_iteratePositional && positional.moveNext()) { | |
309 return true; | |
310 } | |
311 _iteratePositional = false; | |
312 return named.moveNext(); | |
313 } | |
314 } | |
315 | |
316 | |
317 abstract class MinimalInferrerEngine<T> { | |
318 /** | |
319 * Returns the type of [element]. | |
320 */ | |
321 T typeOfElement(Element element); | |
322 | |
323 /** | |
324 * Records that [node] sets non-final field [element] to be of type | |
325 * [type]. | |
326 */ | |
327 void recordTypeOfNonFinalField(Node node, Element field, T type); | |
328 | |
329 /** | |
330 * Records that the captured variable [local] is read. | |
331 */ | |
332 void recordCapturedLocalRead(Local local); | |
333 | |
334 /** | |
335 * Records that the variable [local] is being updated. | |
336 */ | |
337 void recordLocalUpdate(Local local, T type); | |
338 } | |
339 | |
340 /** | |
341 * Placeholder for inferred types of local variables. | |
342 */ | |
343 class LocalsHandler<T> { | |
344 final Compiler compiler; | |
345 final TypeSystem<T> types; | |
346 final MinimalInferrerEngine<T> inferrer; | |
347 final VariableScope<T> locals; | |
348 final Map<Local, Element> captured; | |
349 final Map<Local, Element> capturedAndBoxed; | |
350 final FieldInitializationScope<T> fieldScope; | |
351 LocalsHandler<T> tryBlock; | |
352 bool seenReturnOrThrow = false; | |
353 bool seenBreakOrContinue = false; | |
354 | |
355 bool get aborts { | |
356 return seenReturnOrThrow || seenBreakOrContinue; | |
357 } | |
358 bool get inTryBlock => tryBlock != null; | |
359 | |
360 LocalsHandler(this.inferrer, | |
361 this.types, | |
362 this.compiler, | |
363 Node block, | |
364 [this.fieldScope]) | |
365 : locals = new VariableScope<T>(block), | |
366 captured = new Map<Local, Element>(), | |
367 capturedAndBoxed = new Map<Local, Element>(), | |
368 tryBlock = null; | |
369 | |
370 LocalsHandler.from(LocalsHandler<T> other, | |
371 Node block, | |
372 {bool useOtherTryBlock: true}) | |
373 : locals = new VariableScope<T>(block, other.locals), | |
374 fieldScope = new FieldInitializationScope<T>.from(other.fieldScope), | |
375 captured = other.captured, | |
376 capturedAndBoxed = other.capturedAndBoxed, | |
377 types = other.types, | |
378 inferrer = other.inferrer, | |
379 compiler = other.compiler { | |
380 tryBlock = useOtherTryBlock ? other.tryBlock : this; | |
381 } | |
382 | |
383 LocalsHandler.deepCopyOf(LocalsHandler<T> other) | |
384 : locals = new VariableScope<T>.deepCopyOf(other.locals), | |
385 fieldScope = new FieldInitializationScope<T>.from(other.fieldScope), | |
386 captured = other.captured, | |
387 capturedAndBoxed = other.capturedAndBoxed, | |
388 tryBlock = other.tryBlock, | |
389 types = other.types, | |
390 inferrer = other.inferrer, | |
391 compiler = other.compiler; | |
392 | |
393 T use(Local local) { | |
394 if (capturedAndBoxed.containsKey(local)) { | |
395 return inferrer.typeOfElement(capturedAndBoxed[local]); | |
396 } else { | |
397 if (captured.containsKey(local)) { | |
398 inferrer.recordCapturedLocalRead(local); | |
399 } | |
400 return locals[local]; | |
401 } | |
402 } | |
403 | |
404 void update(LocalElement local, T type, Node node) { | |
405 assert(type != null); | |
406 if (compiler.trustTypeAnnotations || compiler.enableTypeAssertions) { | |
407 type = types.narrowType(type, local.type); | |
408 } | |
409 updateLocal() { | |
410 T currentType = locals[local]; | |
411 locals[local] = type; | |
412 if (currentType != type) { | |
413 inferrer.recordLocalUpdate(local, type); | |
414 } | |
415 } | |
416 if (capturedAndBoxed.containsKey(local)) { | |
417 inferrer.recordTypeOfNonFinalField( | |
418 node, capturedAndBoxed[local], type); | |
419 } else if (inTryBlock) { | |
420 // We don't know if an assignment in a try block | |
421 // will be executed, so all assigments in that block are | |
422 // potential types after we have left it. We update the parent | |
423 // of the try block so that, at exit of the try block, we get | |
424 // the right phi for it. | |
425 T existing = tryBlock.locals.parent[local]; | |
426 if (existing != null) { | |
427 T phiType = types.allocatePhi(tryBlock.locals.block, local, existing); | |
428 T inputType = types.addPhiInput(local, phiType, type); | |
429 tryBlock.locals.parent[local] = inputType; | |
430 } | |
431 // Update the current handler unconditionnally with the new | |
432 // type. | |
433 updateLocal(); | |
434 } else { | |
435 updateLocal(); | |
436 } | |
437 } | |
438 | |
439 void setCaptured(Local local, Element field) { | |
440 captured[local] = field; | |
441 } | |
442 | |
443 void setCapturedAndBoxed(Local local, Element field) { | |
444 capturedAndBoxed[local] = field; | |
445 } | |
446 | |
447 void mergeDiamondFlow(LocalsHandler<T> thenBranch, | |
448 LocalsHandler<T> elseBranch) { | |
449 if (fieldScope != null && elseBranch != null) { | |
450 fieldScope.mergeDiamondFlow(thenBranch.fieldScope, elseBranch.fieldScope); | |
451 } | |
452 seenReturnOrThrow = thenBranch.seenReturnOrThrow | |
453 && elseBranch != null | |
454 && elseBranch.seenReturnOrThrow; | |
455 seenBreakOrContinue = thenBranch.seenBreakOrContinue | |
456 && elseBranch != null | |
457 && elseBranch.seenBreakOrContinue; | |
458 if (aborts) return; | |
459 | |
460 void mergeOneBranch(LocalsHandler<T> other) { | |
461 other.locals.forEachOwnLocal((Local local, T type) { | |
462 T myType = locals[local]; | |
463 if (myType == null) return; // Variable is only defined in [other]. | |
464 if (type == myType) return; | |
465 locals[local] = types.allocateDiamondPhi(myType, type); | |
466 }); | |
467 } | |
468 | |
469 void inPlaceUpdateOneBranch(LocalsHandler<T> other) { | |
470 other.locals.forEachOwnLocal((Local local, T type) { | |
471 T myType = locals[local]; | |
472 if (myType == null) return; // Variable is only defined in [other]. | |
473 if (type == myType) return; | |
474 locals[local] = type; | |
475 }); | |
476 } | |
477 | |
478 if (thenBranch.aborts) { | |
479 if (elseBranch == null) return; | |
480 inPlaceUpdateOneBranch(elseBranch); | |
481 } else if (elseBranch == null) { | |
482 mergeOneBranch(thenBranch); | |
483 } else if (elseBranch.aborts) { | |
484 inPlaceUpdateOneBranch(thenBranch); | |
485 } else { | |
486 void mergeLocal(Local local) { | |
487 T myType = locals[local]; | |
488 if (myType == null) return; | |
489 T elseType = elseBranch.locals[local]; | |
490 T thenType = thenBranch.locals[local]; | |
491 if (thenType == elseType) { | |
492 locals[local] = thenType; | |
493 } else { | |
494 locals[local] = types.allocateDiamondPhi(thenType, elseType); | |
495 } | |
496 } | |
497 | |
498 thenBranch.locals.forEachOwnLocal((Local local, _) { | |
499 mergeLocal(local); | |
500 }); | |
501 elseBranch.locals.forEachOwnLocal((Local local, _) { | |
502 // Discard locals we already processed when iterating over | |
503 // [thenBranch]'s locals. | |
504 if (!thenBranch.locals.updates(local)) mergeLocal(local); | |
505 }); | |
506 } | |
507 } | |
508 | |
509 /** | |
510 * Merge all [LocalsHandler] in [handlers] into [:this:]. | |
511 * | |
512 * If [keepOwnLocals] is true, the types of locals in this | |
513 * [LocalsHandler] are being used in the merge. [keepOwnLocals] | |
514 * should be true if this [LocalsHandler], the dominator of | |
515 * all [handlers], also direclty flows into the join point, | |
516 * that is the code after all [handlers]. For example, consider: | |
517 * | |
518 * [: switch (...) { | |
519 * case 1: ...; break; | |
520 * } | |
521 * :] | |
522 * | |
523 * The [LocalsHandler] at entry of the switch also flows into the | |
524 * exit of the switch, because there is no default case. So the | |
525 * types of locals at entry of the switch have to take part to the | |
526 * merge. | |
527 * | |
528 * The above situation is also true for labeled statements like | |
529 * | |
530 * [: L: { | |
531 * if (...) break; | |
532 * ... | |
533 * } | |
534 * :] | |
535 * | |
536 * where [:this:] is the [LocalsHandler] for the paths through the | |
537 * labeled statement that do not break out. | |
538 */ | |
539 void mergeAfterBreaks(List<LocalsHandler<T>> handlers, | |
540 {bool keepOwnLocals: true}) { | |
541 Node level = locals.block; | |
542 Set<Local> seenLocals = new Setlet<Local>(); | |
543 // If we want to keep the locals, we first merge [this] into itself to | |
544 // create the required Phi nodes. | |
545 if (keepOwnLocals && !seenReturnOrThrow) { | |
546 mergeHandler(this, seenLocals); | |
547 } | |
548 bool allBranchesAbort = true; | |
549 // Merge all other handlers. | |
550 for (LocalsHandler handler in handlers) { | |
551 allBranchesAbort = allBranchesAbort && handler.seenReturnOrThrow; | |
552 mergeHandler(handler, seenLocals); | |
553 } | |
554 // Clean up Phi nodes with single input. | |
555 locals.forEachLocal((Local variable, T type) { | |
556 if (!seenLocals.contains(variable)) return; | |
557 T newType = types.simplifyPhi(level, variable, type); | |
558 if (newType != type) { | |
559 locals[variable] = newType; | |
560 } | |
561 }); | |
562 seenReturnOrThrow = allBranchesAbort && | |
563 (!keepOwnLocals || seenReturnOrThrow); | |
564 } | |
565 | |
566 /** | |
567 * Merge [other] into this handler. Returns whether a local in this | |
568 * has changed. If [seen] is not null, we allocate new Phi nodes | |
569 * unless the local is already present in the set [seen]. This effectively | |
570 * overwrites the current type knowledge in this handler. | |
571 */ | |
572 bool mergeHandler(LocalsHandler<T> other, [Set<Local> seen]) { | |
573 if (other.seenReturnOrThrow) return false; | |
574 bool changed = false; | |
575 other.locals.forEachLocalUntilNode(locals.block, (local, otherType) { | |
576 T myType = locals[local]; | |
577 if (myType == null) return; | |
578 T newType; | |
579 if (seen != null && !seen.contains(local)) { | |
580 newType = types.allocatePhi(locals.block, local, otherType); | |
581 seen.add(local); | |
582 } else { | |
583 newType = types.addPhiInput(local, myType, otherType); | |
584 } | |
585 if (newType != myType) { | |
586 changed = true; | |
587 locals[local] = newType; | |
588 } | |
589 }); | |
590 return changed; | |
591 } | |
592 | |
593 /** | |
594 * Merge all [LocalsHandler] in [handlers] into this handler. | |
595 * Returns whether a local in this handler has changed. | |
596 */ | |
597 bool mergeAll(List<LocalsHandler<T>> handlers) { | |
598 bool changed = false; | |
599 assert(!seenReturnOrThrow); | |
600 handlers.forEach((other) { | |
601 changed = mergeHandler(other) || changed; | |
602 }); | |
603 return changed; | |
604 } | |
605 | |
606 void startLoop(Node loop) { | |
607 locals.forEachLocal((Local variable, T type) { | |
608 T newType = types.allocatePhi(loop, variable, type); | |
609 if (newType != type) { | |
610 locals[variable] = newType; | |
611 } | |
612 }); | |
613 } | |
614 | |
615 void endLoop(Node loop) { | |
616 locals.forEachLocal((Local variable, T type) { | |
617 T newType = types.simplifyPhi(loop, variable, type); | |
618 if (newType != type) { | |
619 locals[variable] = newType; | |
620 } | |
621 }); | |
622 } | |
623 | |
624 void updateField(Element element, T type) { | |
625 fieldScope.updateField(element, type); | |
626 } | |
627 } | |
628 | |
629 abstract class InferrerVisitor | |
630 <T, E extends MinimalInferrerEngine<T>> extends ResolvedVisitor<T> { | |
631 final Compiler compiler; | |
632 final AstElement analyzedElement; | |
633 final TypeSystem<T> types; | |
634 final E inferrer; | |
635 final Map<JumpTarget, List<LocalsHandler<T>>> breaksFor = | |
636 new Map<JumpTarget, List<LocalsHandler<T>>>(); | |
637 final Map<JumpTarget, List<LocalsHandler>> continuesFor = | |
638 new Map<JumpTarget, List<LocalsHandler<T>>>(); | |
639 LocalsHandler<T> locals; | |
640 final List<T> cascadeReceiverStack = new List<T>(); | |
641 | |
642 bool accumulateIsChecks = false; | |
643 bool conditionIsSimple = false; | |
644 List<Send> isChecks; | |
645 int loopLevel = 0; | |
646 | |
647 bool get inLoop => loopLevel > 0; | |
648 bool get isThisExposed { | |
649 return analyzedElement.isGenerativeConstructor | |
650 ? locals.fieldScope.isThisExposed | |
651 : true; | |
652 } | |
653 void set isThisExposed(value) { | |
654 if (analyzedElement.isGenerativeConstructor) { | |
655 locals.fieldScope.isThisExposed = value; | |
656 } | |
657 } | |
658 | |
659 InferrerVisitor(AstElement analyzedElement, | |
660 this.inferrer, | |
661 this.types, | |
662 this.compiler, | |
663 [LocalsHandler<T> handler]) | |
664 : this.analyzedElement = analyzedElement, | |
665 this.locals = handler, | |
666 super(analyzedElement.resolvedAst.elements) { | |
667 if (handler != null) return; | |
668 Node node = analyzedElement.node; | |
669 FieldInitializationScope<T> fieldScope = | |
670 analyzedElement.isGenerativeConstructor | |
671 ? new FieldInitializationScope<T>(types) | |
672 : null; | |
673 locals = new LocalsHandler<T>(inferrer, types, compiler, node, fieldScope); | |
674 } | |
675 | |
676 T visitSendSet(SendSet node); | |
677 | |
678 T visitSuperSend(Send node); | |
679 | |
680 T visitStaticSend(Send node); | |
681 | |
682 T visitGetterSend(Send node); | |
683 | |
684 T visitClosureSend(Send node); | |
685 | |
686 T visitDynamicSend(Send node); | |
687 | |
688 T visitForIn(ForIn node); | |
689 | |
690 T visitReturn(Return node); | |
691 | |
692 T visitFunctionExpression(FunctionExpression node); | |
693 | |
694 T visitAssert(Send node) { | |
695 if (!compiler.enableUserAssertions) { | |
696 return types.nullType; | |
697 } | |
698 // TODO(johnniwinther): Don't handle assert like a regular static call since | |
699 // it break the selector name check. | |
700 return visitStaticSend(node); | |
701 } | |
702 | |
703 T visitNode(Node node) { | |
704 return node.visitChildren(this); | |
705 } | |
706 | |
707 T visitNewExpression(NewExpression node) { | |
708 return node.send.accept(this); | |
709 } | |
710 | |
711 T visit(Node node) { | |
712 return node == null ? null : node.accept(this); | |
713 } | |
714 | |
715 T visitFunctionDeclaration(FunctionDeclaration node) { | |
716 locals.update(elements[node], types.functionType, node); | |
717 return visit(node.function); | |
718 } | |
719 | |
720 T visitLiteralString(LiteralString node) { | |
721 return types.stringLiteralType(node.dartString); | |
722 } | |
723 | |
724 T visitStringInterpolation(StringInterpolation node) { | |
725 node.visitChildren(this); | |
726 return types.stringType; | |
727 } | |
728 | |
729 T visitStringJuxtaposition(StringJuxtaposition node) { | |
730 node.visitChildren(this); | |
731 return types.stringType; | |
732 } | |
733 | |
734 T visitLiteralBool(LiteralBool node) { | |
735 return types.boolType; | |
736 } | |
737 | |
738 T visitLiteralDouble(LiteralDouble node) { | |
739 ConstantSystem constantSystem = compiler.backend.constantSystem; | |
740 // The JavaScript backend may turn this literal into an integer at | |
741 // runtime. | |
742 return types.getConcreteTypeFor( | |
743 constantSystem.createDouble(node.value).computeMask(compiler)); | |
744 } | |
745 | |
746 T visitLiteralInt(LiteralInt node) { | |
747 ConstantSystem constantSystem = compiler.backend.constantSystem; | |
748 // The JavaScript backend may turn this literal into a double at | |
749 // runtime. | |
750 return types.getConcreteTypeFor( | |
751 constantSystem.createInt(node.value).computeMask(compiler)); | |
752 } | |
753 | |
754 T visitLiteralList(LiteralList node) { | |
755 node.visitChildren(this); | |
756 return node.isConst ? types.constListType : types.growableListType; | |
757 } | |
758 | |
759 T visitLiteralMap(LiteralMap node) { | |
760 node.visitChildren(this); | |
761 return node.isConst ? types.constMapType : types.mapType; | |
762 } | |
763 | |
764 T visitLiteralNull(LiteralNull node) { | |
765 return types.nullType; | |
766 } | |
767 | |
768 T visitLiteralSymbol(LiteralSymbol node) { | |
769 // TODO(kasperl): We should be able to tell that the type of a literal | |
770 // symbol is always a non-null exact symbol implementation -- not just | |
771 // any non-null subtype of the symbol interface. | |
772 return types.nonNullSubtype(compiler.symbolClass); | |
773 } | |
774 | |
775 T visitTypePrefixSend(Send node) { | |
776 // TODO(johnniwinther): Remove the need for handling this node. | |
777 return types.dynamicType; | |
778 } | |
779 | |
780 T visitTypeLiteralSend(Send node) { | |
781 return types.typeType; | |
782 } | |
783 | |
784 bool isThisOrSuper(Node node) => node.isThis() || node.isSuper(); | |
785 | |
786 Element get outermostElement { | |
787 return analyzedElement.outermostEnclosingMemberOrTopLevel.implementation; | |
788 } | |
789 | |
790 T _thisType; | |
791 T get thisType { | |
792 if (_thisType != null) return _thisType; | |
793 ClassElement cls = outermostElement.enclosingClass; | |
794 ClassWorld classWorld = compiler.world; | |
795 if (classWorld.isUsedAsMixin(cls)) { | |
796 return _thisType = types.nonNullSubtype(cls); | |
797 } else { | |
798 return _thisType = types.nonNullSubclass(cls); | |
799 } | |
800 } | |
801 | |
802 T _superType; | |
803 T get superType { | |
804 if (_superType != null) return _superType; | |
805 return _superType = types.nonNullExact( | |
806 outermostElement.enclosingClass.superclass); | |
807 } | |
808 | |
809 T visitIdentifier(Identifier node) { | |
810 if (node.isThis()) { | |
811 return thisType; | |
812 } else if (node.isSuper()) { | |
813 return superType; | |
814 } else { | |
815 Element element = elements[node]; | |
816 if (Elements.isLocal(element)) { | |
817 LocalElement local = element; | |
818 return locals.use(local); | |
819 } | |
820 return null; | |
821 } | |
822 } | |
823 | |
824 void potentiallyAddIsCheck(Send node) { | |
825 if (!accumulateIsChecks) return; | |
826 if (!Elements.isLocal(elements[node.receiver])) return; | |
827 isChecks.add(node); | |
828 } | |
829 | |
830 void potentiallyAddNullCheck(Send node, Node receiver) { | |
831 if (!accumulateIsChecks) return; | |
832 if (!Elements.isLocal(elements[receiver])) return; | |
833 isChecks.add(node); | |
834 } | |
835 | |
836 void updateIsChecks(List<Node> tests, {bool usePositive}) { | |
837 void narrow(Element element, DartType type, Node node) { | |
838 if (element is LocalElement) { | |
839 T existing = locals.use(element); | |
840 T newType = types.narrowType(existing, type, isNullable: false); | |
841 locals.update(element, newType, node); | |
842 } | |
843 } | |
844 | |
845 if (tests == null) return; | |
846 for (Send node in tests) { | |
847 if (node.isTypeTest) { | |
848 if (node.isIsNotCheck) { | |
849 if (usePositive) continue; | |
850 } else { | |
851 if (!usePositive) continue; | |
852 } | |
853 DartType type = elements.getType(node.typeAnnotationFromIsCheckOrCast); | |
854 narrow(elements[node.receiver], type, node); | |
855 } else { | |
856 Element receiverElement = elements[node.receiver]; | |
857 Element argumentElement = elements[node.arguments.first]; | |
858 String operator = node.selector.asOperator().source; | |
859 if ((operator == '==' && usePositive) | |
860 || (operator == '!=' && !usePositive)) { | |
861 // Type the elements as null. | |
862 if (Elements.isLocal(receiverElement)) { | |
863 locals.update(receiverElement, types.nullType, node); | |
864 } | |
865 if (Elements.isLocal(argumentElement)) { | |
866 locals.update(argumentElement, types.nullType, node); | |
867 } | |
868 } else { | |
869 // Narrow the elements to a non-null type. | |
870 DartType objectType = compiler.objectClass.rawType; | |
871 if (Elements.isLocal(receiverElement)) { | |
872 narrow(receiverElement, objectType, node); | |
873 } | |
874 if (Elements.isLocal(argumentElement)) { | |
875 narrow(argumentElement, objectType, node); | |
876 } | |
877 } | |
878 } | |
879 } | |
880 } | |
881 | |
882 T visitOperatorSend(Send node) { | |
883 Operator op = node.selector; | |
884 if ("[]" == op.source) { | |
885 return visitDynamicSend(node); | |
886 } else if ("&&" == op.source) { | |
887 conditionIsSimple = false; | |
888 bool oldAccumulateIsChecks = accumulateIsChecks; | |
889 List<Send> oldIsChecks = isChecks; | |
890 if (!accumulateIsChecks) { | |
891 accumulateIsChecks = true; | |
892 isChecks = <Send>[]; | |
893 } | |
894 visit(node.receiver); | |
895 LocalsHandler<T> saved = locals; | |
896 locals = new LocalsHandler<T>.from(locals, node); | |
897 updateIsChecks(isChecks, usePositive: true); | |
898 if (!oldAccumulateIsChecks) { | |
899 accumulateIsChecks = false; | |
900 isChecks = oldIsChecks; | |
901 } | |
902 visit(node.arguments.head); | |
903 saved.mergeDiamondFlow(locals, null); | |
904 locals = saved; | |
905 return types.boolType; | |
906 } else if ("||" == op.source) { | |
907 conditionIsSimple = false; | |
908 List<Send> tests = <Send>[]; | |
909 bool isSimple = handleCondition(node.receiver, tests); | |
910 LocalsHandler<T> saved = locals; | |
911 locals = new LocalsHandler<T>.from(locals, node); | |
912 if (isSimple) updateIsChecks(tests, usePositive: false); | |
913 bool oldAccumulateIsChecks = accumulateIsChecks; | |
914 accumulateIsChecks = false; | |
915 visit(node.arguments.head); | |
916 accumulateIsChecks = oldAccumulateIsChecks; | |
917 saved.mergeDiamondFlow(locals, null); | |
918 locals = saved; | |
919 return types.boolType; | |
920 } else if ("!" == op.source) { | |
921 bool oldAccumulateIsChecks = accumulateIsChecks; | |
922 accumulateIsChecks = false; | |
923 node.visitChildren(this); | |
924 accumulateIsChecks = oldAccumulateIsChecks; | |
925 return types.boolType; | |
926 } else if ("is" == op.source) { | |
927 potentiallyAddIsCheck(node); | |
928 node.visitChildren(this); | |
929 return types.boolType; | |
930 } else if ("as" == op.source) { | |
931 T receiverType = visit(node.receiver); | |
932 DartType type = elements.getType(node.arguments.head); | |
933 return types.narrowType(receiverType, type); | |
934 } else if (node.argumentsNode is Prefix) { | |
935 // Unary operator. | |
936 return visitDynamicSend(node); | |
937 } else if ('===' == op.source | |
938 || '!==' == op.source) { | |
939 node.visitChildren(this); | |
940 return types.boolType; | |
941 } else if ('!=' == op.source) { | |
942 visitDynamicSend(node); | |
943 return types.boolType; | |
944 } else { | |
945 // Binary operator. | |
946 return visitDynamicSend(node); | |
947 } | |
948 } | |
949 | |
950 // Because some nodes just visit their children, we may end up | |
951 // visiting a type annotation, that may contain a send in case of a | |
952 // prefixed type. Therefore we explicitly visit the type annotation | |
953 // to avoid confusing the [ResolvedVisitor]. | |
954 visitTypeAnnotation(TypeAnnotation node) {} | |
955 | |
956 T visitConditional(Conditional node) { | |
957 List<Send> tests = <Send>[]; | |
958 bool simpleCondition = handleCondition(node.condition, tests); | |
959 LocalsHandler<T> saved = locals; | |
960 locals = new LocalsHandler<T>.from(locals, node); | |
961 updateIsChecks(tests, usePositive: true); | |
962 T firstType = visit(node.thenExpression); | |
963 LocalsHandler<T> thenLocals = locals; | |
964 locals = new LocalsHandler<T>.from(saved, node); | |
965 if (simpleCondition) updateIsChecks(tests, usePositive: false); | |
966 T secondType = visit(node.elseExpression); | |
967 saved.mergeDiamondFlow(thenLocals, locals); | |
968 locals = saved; | |
969 T type = types.allocateDiamondPhi(firstType, secondType); | |
970 return type; | |
971 } | |
972 | |
973 T visitVariableDefinitions(VariableDefinitions node) { | |
974 for (Link<Node> link = node.definitions.nodes; | |
975 !link.isEmpty; | |
976 link = link.tail) { | |
977 Node definition = link.head; | |
978 if (definition is Identifier) { | |
979 locals.update(elements[definition], types.nullType, node); | |
980 } else { | |
981 assert(definition.asSendSet() != null); | |
982 visit(definition); | |
983 } | |
984 } | |
985 return null; | |
986 } | |
987 | |
988 bool handleCondition(Node node, List<Send> tests) { | |
989 bool oldConditionIsSimple = conditionIsSimple; | |
990 bool oldAccumulateIsChecks = accumulateIsChecks; | |
991 List<Send> oldIsChecks = isChecks; | |
992 accumulateIsChecks = true; | |
993 conditionIsSimple = true; | |
994 isChecks = tests; | |
995 visit(node); | |
996 bool simpleCondition = conditionIsSimple; | |
997 accumulateIsChecks = oldAccumulateIsChecks; | |
998 isChecks = oldIsChecks; | |
999 conditionIsSimple = oldConditionIsSimple; | |
1000 return simpleCondition; | |
1001 } | |
1002 | |
1003 T visitIf(If node) { | |
1004 List<Send> tests = <Send>[]; | |
1005 bool simpleCondition = handleCondition(node.condition, tests); | |
1006 LocalsHandler<T> saved = locals; | |
1007 locals = new LocalsHandler<T>.from(locals, node); | |
1008 updateIsChecks(tests, usePositive: true); | |
1009 visit(node.thenPart); | |
1010 LocalsHandler<T> thenLocals = locals; | |
1011 locals = new LocalsHandler<T>.from(saved, node); | |
1012 if (simpleCondition) updateIsChecks(tests, usePositive: false); | |
1013 visit(node.elsePart); | |
1014 saved.mergeDiamondFlow(thenLocals, locals); | |
1015 locals = saved; | |
1016 return null; | |
1017 } | |
1018 | |
1019 void setupBreaksAndContinues(JumpTarget element) { | |
1020 if (element == null) return; | |
1021 if (element.isContinueTarget) continuesFor[element] = <LocalsHandler>[]; | |
1022 if (element.isBreakTarget) breaksFor[element] = <LocalsHandler>[]; | |
1023 } | |
1024 | |
1025 void clearBreaksAndContinues(JumpTarget element) { | |
1026 continuesFor.remove(element); | |
1027 breaksFor.remove(element); | |
1028 } | |
1029 | |
1030 List<LocalsHandler<T>> getBreaks(JumpTarget element) { | |
1031 List<LocalsHandler<T>> list = <LocalsHandler<T>>[locals]; | |
1032 if (element == null) return list; | |
1033 if (!element.isBreakTarget) return list; | |
1034 return list..addAll(breaksFor[element]); | |
1035 } | |
1036 | |
1037 List<LocalsHandler<T>> getLoopBackEdges(JumpTarget element) { | |
1038 List<LocalsHandler<T>> list = <LocalsHandler<T>>[locals]; | |
1039 if (element == null) return list; | |
1040 if (!element.isContinueTarget) return list; | |
1041 return list..addAll(continuesFor[element]); | |
1042 } | |
1043 | |
1044 T handleLoop(Node node, void logic()) { | |
1045 loopLevel++; | |
1046 bool changed = false; | |
1047 JumpTarget target = elements.getTargetDefinition(node); | |
1048 LocalsHandler<T> saved = locals; | |
1049 saved.startLoop(node); | |
1050 do { | |
1051 // Setup (and clear in case of multiple iterations of the loop) | |
1052 // the lists of breaks and continues seen in the loop. | |
1053 setupBreaksAndContinues(target); | |
1054 locals = new LocalsHandler<T>.from(saved, node); | |
1055 logic(); | |
1056 changed = saved.mergeAll(getLoopBackEdges(target)); | |
1057 } while (changed); | |
1058 loopLevel--; | |
1059 saved.endLoop(node); | |
1060 bool keepOwnLocals = node.asDoWhile() == null; | |
1061 saved.mergeAfterBreaks( | |
1062 getBreaks(target), keepOwnLocals: keepOwnLocals); | |
1063 locals = saved; | |
1064 clearBreaksAndContinues(target); | |
1065 return null; | |
1066 } | |
1067 | |
1068 T visitWhile(While node) { | |
1069 return handleLoop(node, () { | |
1070 List<Send> tests = <Send>[]; | |
1071 handleCondition(node.condition, tests); | |
1072 updateIsChecks(tests, usePositive: true); | |
1073 visit(node.body); | |
1074 }); | |
1075 } | |
1076 | |
1077 T visitDoWhile(DoWhile node) { | |
1078 return handleLoop(node, () { | |
1079 visit(node.body); | |
1080 List<Send> tests = <Send>[]; | |
1081 handleCondition(node.condition, tests); | |
1082 updateIsChecks(tests, usePositive: true); | |
1083 }); | |
1084 } | |
1085 | |
1086 T visitFor(For node) { | |
1087 visit(node.initializer); | |
1088 return handleLoop(node, () { | |
1089 List<Send> tests = <Send>[]; | |
1090 handleCondition(node.condition, tests); | |
1091 updateIsChecks(tests, usePositive: true); | |
1092 visit(node.body); | |
1093 visit(node.update); | |
1094 }); | |
1095 } | |
1096 | |
1097 T visitTryStatement(TryStatement node) { | |
1098 LocalsHandler<T> saved = locals; | |
1099 locals = new LocalsHandler<T>.from( | |
1100 locals, node, useOtherTryBlock: false); | |
1101 visit(node.tryBlock); | |
1102 saved.mergeDiamondFlow(locals, null); | |
1103 locals = saved; | |
1104 for (Node catchBlock in node.catchBlocks) { | |
1105 saved = locals; | |
1106 locals = new LocalsHandler<T>.from(locals, catchBlock); | |
1107 visit(catchBlock); | |
1108 saved.mergeDiamondFlow(locals, null); | |
1109 locals = saved; | |
1110 } | |
1111 visit(node.finallyBlock); | |
1112 return null; | |
1113 } | |
1114 | |
1115 T visitThrow(Throw node) { | |
1116 node.visitChildren(this); | |
1117 locals.seenReturnOrThrow = true; | |
1118 return types.nonNullEmpty(); | |
1119 } | |
1120 | |
1121 T visitCatchBlock(CatchBlock node) { | |
1122 Node exception = node.exception; | |
1123 if (exception != null) { | |
1124 DartType type = elements.getType(node.type); | |
1125 T mask = type == null || | |
1126 type.treatAsDynamic || | |
1127 type.isTypeVariable | |
1128 ? types.dynamicType | |
1129 : types.nonNullSubtype(type.element); | |
1130 locals.update(elements[exception], mask, node); | |
1131 } | |
1132 Node trace = node.trace; | |
1133 if (trace != null) { | |
1134 locals.update(elements[trace], types.dynamicType, node); | |
1135 } | |
1136 visit(node.block); | |
1137 return null; | |
1138 } | |
1139 | |
1140 T visitParenthesizedExpression(ParenthesizedExpression node) { | |
1141 return visit(node.expression); | |
1142 } | |
1143 | |
1144 T visitBlock(Block node) { | |
1145 if (node.statements != null) { | |
1146 for (Node statement in node.statements) { | |
1147 visit(statement); | |
1148 if (locals.aborts) break; | |
1149 } | |
1150 } | |
1151 return null; | |
1152 } | |
1153 | |
1154 T visitLabeledStatement(LabeledStatement node) { | |
1155 Statement body = node.statement; | |
1156 if (body is Loop | |
1157 || body is SwitchStatement | |
1158 || Elements.isUnusedLabel(node, elements)) { | |
1159 // Loops and switches handle their own labels. | |
1160 visit(body); | |
1161 } else { | |
1162 JumpTarget targetElement = elements.getTargetDefinition(body); | |
1163 setupBreaksAndContinues(targetElement); | |
1164 visit(body); | |
1165 locals.mergeAfterBreaks(getBreaks(targetElement)); | |
1166 clearBreaksAndContinues(targetElement); | |
1167 } | |
1168 return null; | |
1169 } | |
1170 | |
1171 T visitBreakStatement(BreakStatement node) { | |
1172 JumpTarget target = elements.getTargetOf(node); | |
1173 locals.seenBreakOrContinue = true; | |
1174 // Do a deep-copy of the locals, because the code following the | |
1175 // break will change them. | |
1176 breaksFor[target].add(new LocalsHandler<T>.deepCopyOf(locals)); | |
1177 return null; | |
1178 } | |
1179 | |
1180 T visitContinueStatement(ContinueStatement node) { | |
1181 JumpTarget target = elements.getTargetOf(node); | |
1182 locals.seenBreakOrContinue = true; | |
1183 // Do a deep-copy of the locals, because the code following the | |
1184 // continue will change them. | |
1185 continuesFor[target].add(new LocalsHandler<T>.deepCopyOf(locals)); | |
1186 return null; | |
1187 } | |
1188 | |
1189 void internalError(String reason, {Node node}) { | |
1190 compiler.internalError(node, reason); | |
1191 } | |
1192 | |
1193 T visitSwitchStatement(SwitchStatement node) { | |
1194 visit(node.parenthesizedExpression); | |
1195 | |
1196 setupBreaksAndContinues(elements.getTargetDefinition(node)); | |
1197 if (Elements.switchStatementHasContinue(node, elements)) { | |
1198 void forEachLabeledCase(void action(JumpTarget target)) { | |
1199 for (SwitchCase switchCase in node.cases) { | |
1200 for (Node labelOrCase in switchCase.labelsAndCases) { | |
1201 if (labelOrCase.asLabel() == null) continue; | |
1202 LabelDefinition labelElement = | |
1203 elements.getLabelDefinition(labelOrCase); | |
1204 if (labelElement != null) { | |
1205 action(labelElement.target); | |
1206 } | |
1207 } | |
1208 } | |
1209 } | |
1210 | |
1211 forEachLabeledCase((JumpTarget target) { | |
1212 setupBreaksAndContinues(target); | |
1213 }); | |
1214 | |
1215 // If the switch statement has a continue, we conservatively | |
1216 // visit all cases and update [locals] until we have reached a | |
1217 // fixed point. | |
1218 bool changed; | |
1219 locals.startLoop(node); | |
1220 do { | |
1221 changed = false; | |
1222 for (Node switchCase in node.cases) { | |
1223 LocalsHandler<T> saved = locals; | |
1224 locals = new LocalsHandler<T>.from(locals, switchCase); | |
1225 visit(switchCase); | |
1226 changed = saved.mergeAll([locals]) || changed; | |
1227 locals = saved; | |
1228 } | |
1229 } while (changed); | |
1230 locals.endLoop(node); | |
1231 | |
1232 forEachLabeledCase((JumpTarget target) { | |
1233 clearBreaksAndContinues(target); | |
1234 }); | |
1235 } else { | |
1236 LocalsHandler<T> saved = locals; | |
1237 List<LocalsHandler<T>> localsToMerge = <LocalsHandler<T>>[]; | |
1238 bool hasDefaultCase = false; | |
1239 | |
1240 for (SwitchCase switchCase in node.cases) { | |
1241 if (switchCase.isDefaultCase) { | |
1242 hasDefaultCase = true; | |
1243 } | |
1244 locals = new LocalsHandler<T>.from(saved, switchCase); | |
1245 visit(switchCase); | |
1246 localsToMerge.add(locals); | |
1247 } | |
1248 saved.mergeAfterBreaks(localsToMerge, keepOwnLocals: !hasDefaultCase); | |
1249 locals = saved; | |
1250 } | |
1251 clearBreaksAndContinues(elements.getTargetDefinition(node)); | |
1252 return null; | |
1253 } | |
1254 | |
1255 T visitCascadeReceiver(CascadeReceiver node) { | |
1256 var type = visit(node.expression); | |
1257 cascadeReceiverStack.add(type); | |
1258 return type; | |
1259 } | |
1260 | |
1261 T visitCascade(Cascade node) { | |
1262 // Ignore the result of the cascade send and return the type of the cascade | |
1263 // receiver. | |
1264 visit(node.expression); | |
1265 return cascadeReceiverStack.removeLast(); | |
1266 } | |
1267 } | |
OLD | NEW |