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