| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of simple_types_inferrer; |
| |
| /** |
| * The interface [InferrerVisitor] will use when working on types. |
| */ |
| abstract class TypeSystem<T> { |
| T get dynamicType; |
| T get nullType; |
| T get intType; |
| T get doubleType; |
| T get numType; |
| T get boolType; |
| T get functionType; |
| T get listType; |
| T get constListType; |
| T get fixedListType; |
| T get growableListType; |
| T get mapType; |
| T get constMapType; |
| T get stringType; |
| T get typeType; |
| |
| T nonNullSubtype(DartType type); |
| T nonNullSubclass(DartType type); |
| T nonNullExact(DartType type); |
| T nonNullEmpty(); |
| T nullable(T type); |
| Selector newTypedSelector(T receiver, Selector selector); |
| |
| T allocateContainer(T type, |
| Node node, |
| Element enclosing, |
| [T elementType, int length]); |
| |
| /** |
| * Returns the least upper bound between [firstType] and |
| * [secondType]. |
| */ |
| T computeLUB(T firstType, T secondType); |
| |
| /** |
| * Returns the intersection between [T] and [annotation]. |
| * [isNullable] indicates whether the annotation implies a null |
| * type. |
| */ |
| T narrowType(T type, DartType annotation, {bool isNullable: true}); |
| |
| /** |
| * Returns a new type that unions [firstInput] and [secondInput]. |
| */ |
| T allocateDiamondPhi(T firstInput, T secondInput); |
| |
| /** |
| * Returns a new type for holding the potential types of [element]. |
| * [inputType] is the first incoming type of the phi. |
| */ |
| T allocatePhi(Node node, Element element, T inputType); |
| |
| /** |
| * Simplies the phi representing [element] and of the type |
| * [phiType]. For example, if this phi has one incoming input, an |
| * implementation of this method could just return that incoming |
| * input type. |
| */ |
| T simplifyPhi(Node node, Element element, T phiType); |
| |
| /** |
| * Adds [newType] as an input of [phiType]. |
| */ |
| T addPhiInput(Element element, T phiType, T newType); |
| } |
| |
| /** |
| * An implementation of [TypeSystem] for [TypeMask]. |
| */ |
| class TypeMaskSystem implements TypeSystem<TypeMask> { |
| final Compiler compiler; |
| TypeMaskSystem(this.compiler); |
| |
| TypeMask narrowType(TypeMask type, |
| DartType annotation, |
| {bool isNullable: true}) { |
| if (annotation.treatAsDynamic) return type; |
| if (annotation.isVoid) return nullType; |
| if (annotation.element == compiler.objectClass) return type; |
| TypeMask otherType; |
| if (annotation.kind == TypeKind.TYPEDEF |
| || annotation.kind == TypeKind.FUNCTION) { |
| otherType = functionType; |
| } else if (annotation.kind == TypeKind.TYPE_VARIABLE) { |
| // TODO(ngeoffray): Narrow to bound. |
| return type; |
| } else { |
| assert(annotation.kind == TypeKind.INTERFACE); |
| otherType = new TypeMask.nonNullSubtype(annotation); |
| } |
| if (isNullable) otherType = otherType.nullable(); |
| if (type == null) return otherType; |
| return type.intersection(otherType, compiler); |
| } |
| |
| TypeMask computeLUB(TypeMask firstType, TypeMask secondType) { |
| if (firstType == null) { |
| return secondType; |
| } else if (secondType == dynamicType || firstType == dynamicType) { |
| return dynamicType; |
| } else if (firstType == secondType) { |
| return firstType; |
| } else { |
| TypeMask union = firstType.union(secondType, compiler); |
| // TODO(kasperl): If the union isn't nullable it seems wasteful |
| // to use dynamic. Fix that. |
| return union.containsAll(compiler) ? dynamicType : union; |
| } |
| } |
| |
| TypeMask allocateDiamondPhi(TypeMask firstType, TypeMask secondType) { |
| return computeLUB(firstType, secondType); |
| } |
| |
| TypeMask get dynamicType => compiler.typesTask.dynamicType; |
| TypeMask get nullType => compiler.typesTask.nullType; |
| TypeMask get intType => compiler.typesTask.intType; |
| TypeMask get doubleType => compiler.typesTask.doubleType; |
| TypeMask get numType => compiler.typesTask.numType; |
| TypeMask get boolType => compiler.typesTask.boolType; |
| TypeMask get functionType => compiler.typesTask.functionType; |
| TypeMask get listType => compiler.typesTask.listType; |
| TypeMask get constListType => compiler.typesTask.constListType; |
| TypeMask get fixedListType => compiler.typesTask.fixedListType; |
| TypeMask get growableListType => compiler.typesTask.growableListType; |
| TypeMask get mapType => compiler.typesTask.mapType; |
| TypeMask get constMapType => compiler.typesTask.constMapType; |
| TypeMask get stringType => compiler.typesTask.stringType; |
| TypeMask get typeType => compiler.typesTask.typeType; |
| |
| TypeMask nonNullSubtype(DartType type) => new TypeMask.nonNullSubtype(type); |
| TypeMask nonNullSubclass(DartType type) => new TypeMask.nonNullSubclass(type); |
| TypeMask nonNullExact(DartType type) => new TypeMask.nonNullExact(type); |
| TypeMask nonNullEmpty() => new TypeMask.nonNullEmpty(); |
| |
| TypeMask nullable(TypeMask type) { |
| return type.nullable(); |
| } |
| |
| TypeMask allocateContainer(TypeMask type, |
| Node node, |
| Element enclosing, |
| [TypeMask elementType, int length]) { |
| ContainerTypeMask mask = new ContainerTypeMask(type, node, enclosing); |
| mask.elementType = elementType; |
| mask.length = length; |
| return mask; |
| } |
| |
| Selector newTypedSelector(TypeMask receiver, Selector selector) { |
| return new TypedSelector(receiver, selector); |
| } |
| |
| TypeMask addPhiInput(Element element, TypeMask phiType, TypeMask newType) { |
| return computeLUB(phiType, newType); |
| } |
| |
| TypeMask allocatePhi(Node node, Element element, TypeMask inputType) { |
| return inputType; |
| } |
| |
| TypeMask simplifyPhi(Node node, Element element, TypeMask phiType) { |
| return phiType; |
| } |
| } |
| |
| /** |
| * A variable scope holds types for variables. It has a link to a |
| * parent scope, but never changes the types in that parent. Instead, |
| * updates to locals of a parent scope are put in the current scope. |
| * The inferrer makes sure updates get merged into the parent scope, |
| * once the control flow block has been visited. |
| */ |
| class VariableScope<T> { |
| Map<Element, T> variables; |
| |
| /// The parent of this scope. Null for the root scope. |
| final VariableScope<T> parent; |
| |
| /// The [Node] that created this scope. |
| final Node block; |
| |
| VariableScope(this.block, [parent]) |
| : this.variables = null, |
| this.parent = parent; |
| |
| VariableScope.deepCopyOf(VariableScope<T> other) |
| : variables = other.variables == null |
| ? null |
| : new Map<Element, T>.from(other.variables), |
| block = other.block, |
| parent = other.parent == null |
| ? null |
| : new VariableScope<T>.deepCopyOf(other.parent); |
| |
| T operator [](Element variable) { |
| T result; |
| if (variables == null || (result = variables[variable]) == null) { |
| return parent == null ? null : parent[variable]; |
| } |
| return result; |
| } |
| |
| void operator []=(Element variable, T mask) { |
| assert(mask != null); |
| if (variables == null) { |
| variables = new Map<Element, T>(); |
| } |
| variables[variable] = mask; |
| } |
| |
| void forEachOwnLocal(void f(Element element, T type)) { |
| if (variables == null) return; |
| variables.forEach(f); |
| } |
| |
| void forEachLocalUntil(Node node, void f(Element, T type)) { |
| forEachOwnLocal(f); |
| if (block == node) return; |
| if (parent != null) parent.forEachLocalUntil(node, f); |
| } |
| |
| void forEachLocal(void f(Element, T type)) { |
| forEachLocalUntil(null, f); |
| } |
| |
| void remove(Element element) { |
| variables.remove(element); |
| } |
| |
| String toString() { |
| String rest = parent == null ? "null" : parent.toString(); |
| return '$variables $rest'; |
| } |
| } |
| |
| class FieldInitializationScope<T> { |
| final TypeSystem<T> types; |
| Map<Element, T> fields; |
| bool isThisExposed; |
| |
| FieldInitializationScope(this.types) : isThisExposed = false; |
| |
| FieldInitializationScope.internalFrom(FieldInitializationScope<T> other) |
| : types = other.types, |
| isThisExposed = other.isThisExposed; |
| |
| factory FieldInitializationScope.from(FieldInitializationScope<T> other) { |
| if (other == null) return null; |
| return new FieldInitializationScope<T>.internalFrom(other); |
| } |
| |
| void updateField(Element field, T type) { |
| if (isThisExposed) return; |
| if (fields == null) fields = new Map<Element, T>(); |
| fields[field] = type; |
| } |
| |
| T readField(Element field) { |
| return fields == null ? null : fields[field]; |
| } |
| |
| void forEach(void f(Element element, T type)) { |
| if (fields == null) return; |
| fields.forEach(f); |
| } |
| |
| void mergeDiamondFlow(FieldInitializationScope<T> thenScope, |
| FieldInitializationScope<T> elseScope) { |
| // Quick bailout check. If [isThisExposed] is true, we know the |
| // code following won't do anything. |
| if (isThisExposed) return; |
| if (elseScope == null || elseScope.fields == null) { |
| elseScope = this; |
| } |
| |
| thenScope.forEach((Element field, T type) { |
| T otherType = elseScope.readField(field); |
| if (otherType == null) return; |
| updateField(field, types.allocateDiamondPhi(type, otherType)); |
| }); |
| isThisExposed = thenScope.isThisExposed || elseScope.isThisExposed; |
| } |
| } |
| |
| /** |
| * Placeholder for inferred types of local variables. |
| */ |
| class LocalsHandler<T> { |
| final Compiler compiler; |
| final TypeSystem<T> types; |
| final InferrerEngine<T> inferrer; |
| final VariableScope<T> locals; |
| final Map<Element, Element> capturedAndBoxed; |
| final FieldInitializationScope<T> fieldScope; |
| LocalsHandler<T> tryBlock; |
| bool seenReturnOrThrow = false; |
| bool seenBreakOrContinue = false; |
| |
| bool get aborts { |
| return seenReturnOrThrow || seenBreakOrContinue; |
| } |
| bool get inTryBlock => tryBlock != null; |
| |
| LocalsHandler(this.inferrer, |
| this.types, |
| this.compiler, |
| Node block, |
| [this.fieldScope]) |
| : locals = new VariableScope<T>(block), |
| capturedAndBoxed = new Map<Element, Element>(), |
| tryBlock = null; |
| |
| LocalsHandler.from(LocalsHandler<T> other, |
| Node block, |
| {bool useOtherTryBlock: true}) |
| : locals = new VariableScope<T>(block, other.locals), |
| fieldScope = new FieldInitializationScope<T>.from(other.fieldScope), |
| capturedAndBoxed = other.capturedAndBoxed, |
| types = other.types, |
| inferrer = other.inferrer, |
| compiler = other.compiler { |
| tryBlock = useOtherTryBlock ? other.tryBlock : this; |
| } |
| |
| LocalsHandler.deepCopyOf(LocalsHandler<T> other) |
| : locals = new VariableScope<T>.deepCopyOf(other.locals), |
| fieldScope = new FieldInitializationScope<T>.from(other.fieldScope), |
| capturedAndBoxed = other.capturedAndBoxed, |
| tryBlock = other.tryBlock, |
| types = other.types, |
| inferrer = other.inferrer, |
| compiler = other.compiler; |
| |
| T use(Element local) { |
| return capturedAndBoxed.containsKey(local) |
| ? inferrer.typeOfElement(capturedAndBoxed[local]) |
| : locals[local]; |
| } |
| |
| void update(Element local, T type, Node node) { |
| assert(type != null); |
| if (compiler.trustTypeAnnotations || compiler.enableTypeAssertions) { |
| type = types.narrowType(type, local.computeType(compiler)); |
| } |
| if (capturedAndBoxed.containsKey(local)) { |
| inferrer.recordTypeOfNonFinalField( |
| node, capturedAndBoxed[local], type, null); |
| } else if (inTryBlock) { |
| // We don't know if an assignment in a try block |
| // will be executed, so all assigments in that block are |
| // potential types after we have left it. We update the parent |
| // of the try block so that, at exit of the try block, we get |
| // the right phi for it. |
| T existing = tryBlock.locals.parent[local]; |
| T phiType = types.allocatePhi(tryBlock.locals.block, local, existing); |
| T inputType = types.addPhiInput(local, phiType, type); |
| tryBlock.locals.parent[local] = inputType; |
| // Update the current handler unconditionnally with the new |
| // type. |
| locals[local] = type; |
| } else { |
| locals[local] = type; |
| } |
| } |
| |
| void setCapturedAndBoxed(Element local, Element field) { |
| capturedAndBoxed[local] = field; |
| } |
| |
| void mergeDiamondFlow(LocalsHandler<T> thenBranch, |
| LocalsHandler<T> elseBranch) { |
| if (fieldScope != null && elseBranch != null) { |
| fieldScope.mergeDiamondFlow(thenBranch.fieldScope, elseBranch.fieldScope); |
| } |
| seenReturnOrThrow = thenBranch.seenReturnOrThrow |
| && elseBranch != null |
| && elseBranch.seenReturnOrThrow; |
| seenBreakOrContinue = thenBranch.seenBreakOrContinue |
| && elseBranch != null |
| && elseBranch.seenBreakOrContinue; |
| if (aborts) return; |
| if (thenBranch.aborts) { |
| thenBranch = this; |
| if (elseBranch == null) return; |
| } else if (elseBranch == null || elseBranch.aborts) { |
| elseBranch = this; |
| } |
| |
| thenBranch.locals.forEachOwnLocal((Element local, T type) { |
| T otherType = elseBranch.locals[local]; |
| if (otherType == null) return; |
| T existing = locals[local]; |
| if (type == existing && otherType == existing) return; |
| locals[local] = types.allocateDiamondPhi(type, otherType); |
| }); |
| |
| } |
| |
| /** |
| * Merge all [LocalsHandler] in [handlers] into [:this:]. Returns |
| * whether a local in [:this:] has changed. |
| */ |
| bool mergeAll(List<LocalsHandler<T>> handlers) { |
| bool changed = false; |
| handlers.forEach((LocalsHandler<T> handler) { |
| if (handler.seenReturnOrThrow) return; |
| Node level = locals.block; |
| handler.locals.forEachLocalUntil(level, (Element local, T otherType) { |
| T myType = locals[local]; |
| if (myType == null) return; |
| T newType = types.addPhiInput(local, myType, otherType); |
| if (newType != myType) { |
| changed = true; |
| locals[local] = newType; |
| } |
| }); |
| }); |
| return changed; |
| } |
| |
| void startLoop(Node loop) { |
| locals.forEachLocal((Element element, T type) { |
| T newType = types.allocatePhi(loop, element, type); |
| if (newType != type) { |
| locals[element] = type; |
| } |
| }); |
| } |
| |
| void endLoop(Node loop) { |
| locals.forEachLocal((Element element, T type) { |
| T newType = types.simplifyPhi(loop, element, type); |
| if (newType != type) { |
| locals[element] = type; |
| } |
| }); |
| } |
| |
| void updateField(Element element, T type) { |
| fieldScope.updateField(element, type); |
| } |
| } |
| |
| abstract class InferrerVisitor<T> extends ResolvedVisitor<T> { |
| final Element analyzedElement; |
| final TypeSystem<T> types; |
| final InferrerEngine<T> inferrer; |
| final Compiler compiler; |
| final Map<TargetElement, List<LocalsHandler<T>>> breaksFor = |
| new Map<TargetElement, List<LocalsHandler<T>>>(); |
| final Map<TargetElement, List<LocalsHandler>> continuesFor = |
| new Map<TargetElement, List<LocalsHandler<T>>>(); |
| LocalsHandler<T> locals; |
| |
| bool accumulateIsChecks = false; |
| bool conditionIsSimple = false; |
| List<Send> isChecks; |
| int loopLevel = 0; |
| |
| bool get inLoop => loopLevel > 0; |
| bool get isThisExposed { |
| return analyzedElement.isGenerativeConstructor() |
| ? locals.fieldScope.isThisExposed |
| : true; |
| } |
| void set isThisExposed(value) { |
| if (analyzedElement.isGenerativeConstructor()) { |
| locals.fieldScope.isThisExposed = value; |
| } |
| } |
| |
| InferrerVisitor(Element analyzedElement, |
| this.inferrer, |
| this.types, |
| Compiler compiler, |
| [LocalsHandler<T> handler]) |
| : this.compiler = compiler, |
| this.analyzedElement = analyzedElement, |
| this.locals = handler, |
| super(compiler.enqueuer.resolution.getCachedElements(analyzedElement)) { |
| if (handler != null) return; |
| Node node = analyzedElement.parseNode(compiler); |
| FieldInitializationScope<T> fieldScope = |
| analyzedElement.isGenerativeConstructor() |
| ? new FieldInitializationScope<T>(types) |
| : null; |
| locals = new LocalsHandler<T>(inferrer, types, compiler, node, fieldScope); |
| } |
| |
| T visitSendSet(SendSet node); |
| |
| T visitSuperSend(Send node); |
| |
| T visitStaticSend(Send node); |
| |
| T visitGetterSend(Send node); |
| |
| T visitClosureSend(Send node); |
| |
| T visitDynamicSend(Send node); |
| |
| T visitForIn(ForIn node); |
| |
| T visitReturn(Return node); |
| |
| T visitFunctionExpression(FunctionExpression node); |
| |
| T visitNode(Node node) { |
| node.visitChildren(this); |
| } |
| |
| T visitNewExpression(NewExpression node) { |
| return node.send.accept(this); |
| } |
| |
| T visit(Node node) { |
| return node == null ? null : node.accept(this); |
| } |
| |
| T visitFunctionDeclaration(FunctionDeclaration node) { |
| locals.update(elements[node], types.functionType, node); |
| return visit(node.function); |
| } |
| |
| T visitLiteralString(LiteralString node) { |
| return types.stringType; |
| } |
| |
| T visitStringInterpolation(StringInterpolation node) { |
| node.visitChildren(this); |
| return types.stringType; |
| } |
| |
| T visitStringJuxtaposition(StringJuxtaposition node) { |
| node.visitChildren(this); |
| return types.stringType; |
| } |
| |
| T visitLiteralBool(LiteralBool node) { |
| return types.boolType; |
| } |
| |
| T visitLiteralDouble(LiteralDouble node) { |
| return types.doubleType; |
| } |
| |
| T visitLiteralInt(LiteralInt node) { |
| ConstantSystem constantSystem = compiler.backend.constantSystem; |
| Constant constant = constantSystem.createInt(node.value); |
| // The JavaScript backend may turn this literal into a double at |
| // runtime. |
| return constantSystem.isDouble(constant) |
| ? types.doubleType |
| : types.intType; |
| } |
| |
| T visitLiteralList(LiteralList node) { |
| node.visitChildren(this); |
| return node.isConst() ? types.constListType : types.growableListType; |
| } |
| |
| T visitLiteralMap(LiteralMap node) { |
| node.visitChildren(this); |
| return node.isConst() ? types.constMapType : types.mapType; |
| } |
| |
| T visitLiteralNull(LiteralNull node) { |
| return types.nullType; |
| } |
| |
| T visitTypeReferenceSend(Send node) { |
| return types.typeType; |
| } |
| |
| bool isThisOrSuper(Node node) => node.isThis() || node.isSuper(); |
| |
| Element get outermostElement { |
| return |
| analyzedElement.getOutermostEnclosingMemberOrTopLevel().implementation; |
| } |
| |
| T _thisType; |
| T get thisType { |
| if (_thisType != null) return _thisType; |
| ClassElement cls = outermostElement.getEnclosingClass(); |
| if (compiler.world.isUsedAsMixin(cls)) { |
| return _thisType = types.nonNullSubtype(cls.rawType); |
| } else if (compiler.world.hasAnySubclass(cls)) { |
| return _thisType = types.nonNullSubclass(cls.rawType); |
| } else { |
| return _thisType = types.nonNullExact(cls.rawType); |
| } |
| } |
| |
| T _superType; |
| T get superType { |
| if (_superType != null) return _superType; |
| return _superType = types.nonNullExact( |
| outermostElement.getEnclosingClass().superclass.rawType); |
| } |
| |
| T visitIdentifier(Identifier node) { |
| if (node.isThis()) { |
| return thisType; |
| } else if (node.isSuper()) { |
| return superType; |
| } |
| } |
| |
| void potentiallyAddIsCheck(Send node) { |
| if (!accumulateIsChecks) return; |
| if (!Elements.isLocal(elements[node.receiver])) return; |
| isChecks.add(node); |
| } |
| |
| void updateIsChecks(List<Node> tests, {bool usePositive}) { |
| if (tests == null) return; |
| for (Send node in tests) { |
| if (node.isIsNotCheck) { |
| if (usePositive) continue; |
| } else { |
| if (!usePositive) continue; |
| } |
| DartType type = elements.getType(node.typeAnnotationFromIsCheckOrCast); |
| Element element = elements[node.receiver]; |
| T existing = locals.use(element); |
| T newType = types.narrowType(existing, type, isNullable: false); |
| locals.update(element, newType, node); |
| } |
| } |
| |
| T visitOperatorSend(Send node) { |
| Operator op = node.selector; |
| if (const SourceString("[]") == op.source) { |
| return visitDynamicSend(node); |
| } else if (const SourceString("&&") == op.source) { |
| conditionIsSimple = false; |
| bool oldAccumulateIsChecks = accumulateIsChecks; |
| accumulateIsChecks = true; |
| if (isChecks == null) isChecks = <Send>[]; |
| visit(node.receiver); |
| accumulateIsChecks = oldAccumulateIsChecks; |
| if (!accumulateIsChecks) isChecks = null; |
| LocalsHandler<T> saved = locals; |
| locals = new LocalsHandler<T>.from(locals, node); |
| updateIsChecks(isChecks, usePositive: true); |
| visit(node.arguments.head); |
| saved.mergeDiamondFlow(locals, null); |
| locals = saved; |
| return types.boolType; |
| } else if (const SourceString("||") == op.source) { |
| conditionIsSimple = false; |
| visit(node.receiver); |
| LocalsHandler<T> saved = locals; |
| locals = new LocalsHandler<T>.from(locals, node); |
| updateIsChecks(isChecks, usePositive: false); |
| bool oldAccumulateIsChecks = accumulateIsChecks; |
| accumulateIsChecks = false; |
| visit(node.arguments.head); |
| accumulateIsChecks = oldAccumulateIsChecks; |
| saved.mergeDiamondFlow(locals, null); |
| locals = saved; |
| return types.boolType; |
| } else if (const SourceString("!") == op.source) { |
| bool oldAccumulateIsChecks = accumulateIsChecks; |
| accumulateIsChecks = false; |
| node.visitChildren(this); |
| accumulateIsChecks = oldAccumulateIsChecks; |
| return types.boolType; |
| } else if (const SourceString("is") == op.source) { |
| potentiallyAddIsCheck(node); |
| node.visitChildren(this); |
| return types.boolType; |
| } else if (const SourceString("as") == op.source) { |
| T receiverType = visit(node.receiver); |
| DartType type = elements.getType(node.arguments.head); |
| return types.narrowType(receiverType, type); |
| } else if (node.argumentsNode is Prefix) { |
| // Unary operator. |
| return visitDynamicSend(node); |
| } else if (const SourceString('===') == op.source |
| || const SourceString('!==') == op.source) { |
| node.visitChildren(this); |
| return types.boolType; |
| } else { |
| // Binary operator. |
| return visitDynamicSend(node); |
| } |
| } |
| |
| // Because some nodes just visit their children, we may end up |
| // visiting a type annotation, that may contain a send in case of a |
| // prefixed type. Therefore we explicitly visit the type annotation |
| // to avoid confusing the [ResolvedVisitor]. |
| visitTypeAnnotation(TypeAnnotation node) {} |
| |
| T visitConditional(Conditional node) { |
| List<Send> tests = <Send>[]; |
| bool simpleCondition = handleCondition(node.condition, tests); |
| LocalsHandler<T> saved = locals; |
| locals = new LocalsHandler<T>.from(locals, node); |
| updateIsChecks(tests, usePositive: true); |
| T firstType = visit(node.thenExpression); |
| LocalsHandler<T> thenLocals = locals; |
| locals = new LocalsHandler<T>.from(saved, node); |
| if (simpleCondition) updateIsChecks(tests, usePositive: false); |
| T secondType = visit(node.elseExpression); |
| saved.mergeDiamondFlow(thenLocals, locals); |
| locals = saved; |
| T type = types.allocateDiamondPhi(firstType, secondType); |
| return type; |
| } |
| |
| T visitVariableDefinitions(VariableDefinitions node) { |
| for (Link<Node> link = node.definitions.nodes; |
| !link.isEmpty; |
| link = link.tail) { |
| Node definition = link.head; |
| if (definition is Identifier) { |
| locals.update(elements[definition], types.nullType, node); |
| } else { |
| assert(definition.asSendSet() != null); |
| visit(definition); |
| } |
| } |
| } |
| |
| bool handleCondition(Node node, List<Send> tests) { |
| bool oldConditionIsSimple = conditionIsSimple; |
| bool oldAccumulateIsChecks = accumulateIsChecks; |
| List<Send> oldIsChecks = isChecks; |
| accumulateIsChecks = true; |
| conditionIsSimple = true; |
| isChecks = tests; |
| visit(node); |
| bool simpleCondition = conditionIsSimple; |
| accumulateIsChecks = oldAccumulateIsChecks; |
| isChecks = oldIsChecks; |
| conditionIsSimple = oldConditionIsSimple; |
| return simpleCondition; |
| } |
| |
| T visitIf(If node) { |
| List<Send> tests = <Send>[]; |
| bool simpleCondition = handleCondition(node.condition, tests); |
| LocalsHandler<T> saved = locals; |
| locals = new LocalsHandler<T>.from(locals, node); |
| updateIsChecks(tests, usePositive: true); |
| visit(node.thenPart); |
| LocalsHandler<T> thenLocals = locals; |
| locals = new LocalsHandler<T>.from(saved, node); |
| if (simpleCondition) updateIsChecks(tests, usePositive: false); |
| visit(node.elsePart); |
| saved.mergeDiamondFlow(thenLocals, locals); |
| locals = saved; |
| } |
| |
| void setupBreaksAndContinues(TargetElement element) { |
| if (element == null) return; |
| if (element.isContinueTarget) continuesFor[element] = <LocalsHandler>[]; |
| if (element.isBreakTarget) breaksFor[element] = <LocalsHandler>[]; |
| } |
| |
| void clearBreaksAndContinues(TargetElement element) { |
| continuesFor.remove(element); |
| breaksFor.remove(element); |
| } |
| |
| List<LocalsHandler<T>> getBreaks(TargetElement element) { |
| List<LocalsHandler<T>> list = <LocalsHandler<T>>[locals]; |
| if (element == null) return list; |
| if (!element.isBreakTarget) return list; |
| return list..addAll(breaksFor[element]); |
| } |
| |
| List<LocalsHandler<T>> getLoopBackEdges(TargetElement element) { |
| List<LocalsHandler<T>> list = <LocalsHandler<T>>[locals]; |
| if (element == null) return list; |
| if (!element.isContinueTarget) return list; |
| return list..addAll(continuesFor[element]); |
| } |
| |
| T handleLoop(Node node, void logic()) { |
| loopLevel++; |
| bool changed = false; |
| TargetElement target = elements[node]; |
| setupBreaksAndContinues(target); |
| locals.startLoop(node); |
| LocalsHandler<T> saved; |
| do { |
| saved = locals; |
| locals = new LocalsHandler<T>.from(locals, node); |
| logic(); |
| changed = saved.mergeAll(getLoopBackEdges(target)); |
| locals = saved; |
| } while (changed); |
| loopLevel--; |
| saved.mergeAll(getBreaks(target)); |
| locals = saved; |
| locals.endLoop(node); |
| clearBreaksAndContinues(target); |
| } |
| |
| T visitWhile(While node) { |
| return handleLoop(node, () { |
| List<Send> tests = <Send>[]; |
| handleCondition(node.condition, tests); |
| updateIsChecks(tests, usePositive: true); |
| visit(node.body); |
| }); |
| } |
| |
| T visitDoWhile(DoWhile node) { |
| return handleLoop(node, () { |
| visit(node.body); |
| List<Send> tests = <Send>[]; |
| handleCondition(node.condition, tests); |
| updateIsChecks(tests, usePositive: true); |
| }); |
| } |
| |
| T visitFor(For node) { |
| visit(node.initializer); |
| return handleLoop(node, () { |
| List<Send> tests = <Send>[]; |
| handleCondition(node.condition, tests); |
| updateIsChecks(tests, usePositive: true); |
| visit(node.body); |
| visit(node.update); |
| }); |
| } |
| |
| T visitTryStatement(TryStatement node) { |
| LocalsHandler<T> saved = locals; |
| locals = new LocalsHandler<T>.from( |
| locals, node, useOtherTryBlock: false); |
| visit(node.tryBlock); |
| saved.mergeDiamondFlow(locals, null); |
| locals = saved; |
| for (Node catchBlock in node.catchBlocks) { |
| saved = locals; |
| locals = new LocalsHandler<T>.from(locals, catchBlock); |
| visit(catchBlock); |
| saved.mergeDiamondFlow(locals, null); |
| locals = saved; |
| } |
| visit(node.finallyBlock); |
| } |
| |
| T visitThrow(Throw node) { |
| node.visitChildren(this); |
| locals.seenReturnOrThrow = true; |
| return types.dynamicType; |
| } |
| |
| T visitCatchBlock(CatchBlock node) { |
| Node exception = node.exception; |
| if (exception != null) { |
| DartType type = elements.getType(node.type); |
| T mask = type == null || type.treatAsDynamic |
| ? types.dynamicType |
| : types.nonNullSubtype(type.asRaw()); |
| locals.update(elements[exception], mask, node); |
| } |
| Node trace = node.trace; |
| if (trace != null) { |
| locals.update(elements[trace], types.dynamicType, node); |
| } |
| visit(node.block); |
| } |
| |
| T visitParenthesizedExpression(ParenthesizedExpression node) { |
| return visit(node.expression); |
| } |
| |
| T visitBlock(Block node) { |
| if (node.statements != null) { |
| for (Node statement in node.statements) { |
| visit(statement); |
| if (locals.aborts) break; |
| } |
| } |
| } |
| |
| T visitLabeledStatement(LabeledStatement node) { |
| Statement body = node.statement; |
| if (body is Loop |
| || body is SwitchStatement |
| || Elements.isUnusedLabel(node, elements)) { |
| // Loops and switches handle their own labels. |
| visit(body); |
| } else { |
| TargetElement targetElement = elements[body]; |
| setupBreaksAndContinues(targetElement); |
| visit(body); |
| locals.mergeAll(getBreaks(targetElement)); |
| clearBreaksAndContinues(targetElement); |
| } |
| } |
| |
| T visitBreakStatement(BreakStatement node) { |
| TargetElement target = elements[node]; |
| locals.seenBreakOrContinue = true; |
| // Do a deep-copy of the locals, because the code following the |
| // break will change them. |
| breaksFor[target].add(new LocalsHandler<T>.deepCopyOf(locals)); |
| } |
| |
| T visitContinueStatement(ContinueStatement node) { |
| TargetElement target = elements[node]; |
| locals.seenBreakOrContinue = true; |
| // Do a deep-copy of the locals, because the code following the |
| // continue will change them. |
| continuesFor[target].add(new LocalsHandler<T>.deepCopyOf(locals)); |
| } |
| |
| void internalError(String reason, {Node node}) { |
| compiler.internalError(reason, node: node); |
| } |
| |
| T visitSwitchStatement(SwitchStatement node) { |
| visit(node.parenthesizedExpression); |
| |
| setupBreaksAndContinues(elements[node]); |
| if (Elements.switchStatementHasContinue(node, elements)) { |
| void forEachLabeledCase(void action(TargetElement target)) { |
| for (SwitchCase switchCase in node.cases) { |
| for (Node labelOrCase in switchCase.labelsAndCases) { |
| if (labelOrCase.asLabel() == null) continue; |
| LabelElement labelElement = elements[labelOrCase]; |
| if (labelElement != null) { |
| action(labelElement.target); |
| } |
| } |
| } |
| } |
| |
| forEachLabeledCase((TargetElement target) { |
| setupBreaksAndContinues(target); |
| }); |
| |
| // If the switch statement has a continue, we conservatively |
| // visit all cases and update [locals] until we have reached a |
| // fixed point. |
| bool changed; |
| locals.startLoop(node); |
| do { |
| changed = false; |
| for (Node switchCase in node.cases) { |
| LocalsHandler<T> saved = locals; |
| locals = new LocalsHandler<T>.from(locals, switchCase); |
| visit(switchCase); |
| changed = saved.mergeAll([locals]) || changed; |
| locals = saved; |
| } |
| } while (changed); |
| locals.endLoop(node); |
| |
| forEachLabeledCase((TargetElement target) { |
| clearBreaksAndContinues(target); |
| }); |
| } else { |
| LocalsHandler<T> saved = locals; |
| List<LocalsHandler<T>> localsToMerge = <LocalsHandler<T>>[]; |
| |
| for (SwitchCase switchCase in node.cases) { |
| locals = new LocalsHandler<T>.from(saved, switchCase); |
| visit(switchCase); |
| localsToMerge.add(locals); |
| } |
| saved.mergeAll(localsToMerge); |
| locals = saved; |
| } |
| clearBreaksAndContinues(elements[node]); |
| // In case there is a default in the switch we discard the |
| // incoming localsHandler, because the types it holds do not need |
| // to be merged after the switch statement. This means that, if all |
| // cases, including the default, break or continue, the [result] |
| // handler may think it just aborts the current block. Therefore |
| // we set the current locals to not have any break or continue, so |
| // that the [visitBlock] method does not assume the code after the |
| // switch is dead code. |
| locals.seenBreakOrContinue = false; |
| } |
| |
| T visitCascadeReceiver(CascadeReceiver node) { |
| // TODO(ngeoffray): Implement. |
| node.visitChildren(this); |
| return types.dynamicType; |
| } |
| |
| T visitCascade(Cascade node) { |
| // TODO(ngeoffray): Implement. |
| node.visitChildren(this); |
| return types.dynamicType; |
| } |
| } |