Index: sdk/lib/_internal/compiler/implementation/inferrer/simple_types_inferrer.dart |
diff --git a/sdk/lib/_internal/compiler/implementation/inferrer/simple_types_inferrer.dart b/sdk/lib/_internal/compiler/implementation/inferrer/simple_types_inferrer.dart |
deleted file mode 100644 |
index f06b1ccafc9f8913e0e4797441cb8ba668394dca..0000000000000000000000000000000000000000 |
--- a/sdk/lib/_internal/compiler/implementation/inferrer/simple_types_inferrer.dart |
+++ /dev/null |
@@ -1,1344 +0,0 @@ |
-// 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. |
- |
-library simple_types_inferrer; |
- |
-import '../closure.dart' show ClosureClassMap, ClosureScope; |
-import '../dart_types.dart' |
- show DartType, InterfaceType, FunctionType, TypeKind; |
-import '../elements/elements.dart'; |
-import '../js_backend/js_backend.dart' as js; |
-import '../native/native.dart' as native; |
-import '../tree/tree.dart' as ast; |
-import '../cps_ir/cps_ir_nodes.dart' as cps_ir show Node; |
-import '../util/util.dart' show Link, Spannable, Setlet; |
-import '../types/types.dart' |
- show TypesInferrer, FlatTypeMask, TypeMask, ContainerTypeMask, |
- ElementTypeMask, ValueTypeMask, TypeSystem, MinimalInferrerEngine; |
-import 'inferrer_visitor.dart'; |
- |
-// BUG(8802): There's a bug in the analyzer that makes the re-export |
-// of Selector from dart2jslib.dart fail. For now, we work around that |
-// by importing universe.dart explicitly and disabling the re-export. |
-import '../dart2jslib.dart' hide Selector, TypedSelector; |
-import '../universe/universe.dart' show Selector, SideEffects, TypedSelector; |
- |
-/** |
- * An implementation of [TypeSystem] for [TypeMask]. |
- */ |
-class TypeMaskSystem implements TypeSystem<TypeMask> { |
- final Compiler compiler; |
- final ClassWorld classWorld; |
- TypeMaskSystem(Compiler compiler) |
- : this.compiler = compiler, |
- this.classWorld = compiler.world; |
- |
- TypeMask narrowType(TypeMask type, |
- DartType annotation, |
- {bool isNullable: true}) { |
- if (annotation.treatAsDynamic) return type; |
- if (annotation.element == compiler.objectClass) return type; |
- TypeMask otherType; |
- if (annotation.isTypedef || annotation.isFunctionType) { |
- otherType = functionType; |
- } else if (annotation.isTypeVariable) { |
- // TODO(ngeoffray): Narrow to bound. |
- return type; |
- } else if (annotation.isVoid) { |
- otherType = nullType; |
- } else { |
- assert(annotation.isInterfaceType); |
- otherType = new TypeMask.nonNullSubtype(annotation.element, classWorld); |
- } |
- if (isNullable) otherType = otherType.nullable(); |
- if (type == null) return otherType; |
- return type.intersection(otherType, classWorld); |
- } |
- |
- 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, classWorld); |
- // TODO(kasperl): If the union isn't nullable it seems wasteful |
- // to use dynamic. Fix that. |
- return union.containsAll(classWorld) ? 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 uint32Type => compiler.typesTask.uint32Type; |
- TypeMask get uint31Type => compiler.typesTask.uint31Type; |
- TypeMask get positiveIntType => compiler.typesTask.positiveIntType; |
- 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; |
- bool isNull(TypeMask mask) => mask.isEmpty && mask.isNullable; |
- |
- TypeMask stringLiteralType(ast.DartString value) => stringType; |
- |
- TypeMask nonNullSubtype(ClassElement type) |
- => new TypeMask.nonNullSubtype(type.declaration, classWorld); |
- TypeMask nonNullSubclass(ClassElement type) |
- => new TypeMask.nonNullSubclass(type.declaration, classWorld); |
- TypeMask nonNullExact(ClassElement type) |
- => new TypeMask.nonNullExact(type.declaration, classWorld); |
- TypeMask nonNullEmpty() => new TypeMask.nonNullEmpty(); |
- |
- TypeMask allocateList(TypeMask type, |
- ast.Node node, |
- Element enclosing, |
- [TypeMask elementType, int length]) { |
- return new ContainerTypeMask(type, node, enclosing, elementType, length); |
- } |
- |
- TypeMask allocateMap(TypeMask type, ast.Node node, Element element, |
- [List<TypeMask> keys, List<TypeMask> values]) { |
- return type; |
- } |
- |
- TypeMask allocateClosure(ast.Node node, Element element) { |
- return functionType; |
- } |
- |
- Selector newTypedSelector(TypeMask receiver, Selector selector) { |
- return new TypedSelector(receiver, selector, compiler.world); |
- } |
- |
- TypeMask addPhiInput(Local variable, |
- TypeMask phiType, |
- TypeMask newType) { |
- return computeLUB(phiType, newType); |
- } |
- |
- TypeMask allocatePhi(ast.Node node, |
- Local variable, |
- TypeMask inputType) { |
- return inputType; |
- } |
- |
- TypeMask allocateLoopPhi(ast.Node node, |
- Local variable, |
- TypeMask inputType) { |
- return inputType; |
- } |
- |
- TypeMask simplifyPhi(ast.Node node, |
- Local variable, |
- TypeMask phiType) { |
- return phiType; |
- } |
- |
- bool selectorNeedsUpdate(TypeMask type, Selector selector) { |
- return type != selector.mask; |
- } |
- |
- TypeMask refineReceiver(Selector selector, TypeMask receiverType) { |
- TypeMask newType = compiler.world.allFunctions.receiverType(selector); |
- return receiverType.intersection(newType, classWorld); |
- } |
- |
- TypeMask getConcreteTypeFor(TypeMask mask) => mask; |
-} |
- |
-/** |
- * Common super class used by [SimpleTypeInferrerVisitor] to propagate |
- * type information about visited nodes, as well as to request type |
- * information of elements. |
- */ |
-abstract class InferrerEngine<T, V extends TypeSystem> |
- implements MinimalInferrerEngine<T> { |
- final Compiler compiler; |
- final ClassWorld classWorld; |
- final V types; |
- final Map<ast.Node, T> concreteTypes = new Map<ast.Node, T>(); |
- final Set<Element> generativeConstructorsExposingThis = new Set<Element>(); |
- |
- InferrerEngine(Compiler compiler, this.types) |
- : this.compiler = compiler, |
- this.classWorld = compiler.world; |
- |
- /** |
- * Records the default type of parameter [parameter]. |
- */ |
- void setDefaultTypeOfParameter(ParameterElement parameter, T type); |
- |
- /** |
- * This helper breaks abstractions but is currently required to work around |
- * the wrong modelling of default values of optional parameters of |
- * synthetic constructors. |
- * |
- * TODO(johnniwinther): Remove once default values of synthetic parameters |
- * are fixed. |
- */ |
- bool hasAlreadyComputedTypeOfParameterDefault(ParameterElement paramemter); |
- |
- /** |
- * Returns the type of [element]. |
- */ |
- T typeOfElement(Element element); |
- |
- /** |
- * Returns the return type of [element]. |
- */ |
- T returnTypeOfElement(Element element); |
- |
- /** |
- * Records that [node] sets final field [element] to be of type [type]. |
- * |
- * [nodeHolder] is the element holder of [node]. |
- */ |
- void recordTypeOfFinalField(ast.Node node, |
- Element nodeHolder, |
- Element field, |
- T type); |
- |
- /** |
- * Records that [node] sets non-final field [element] to be of type |
- * [type]. |
- */ |
- void recordTypeOfNonFinalField(Spannable node, Element field, T type); |
- |
- /** |
- * Records that [element] is of type [type]. |
- */ |
- void recordType(Element element, T type); |
- |
- /** |
- * Records that the return type [element] is of type [type]. |
- */ |
- void recordReturnType(Element element, T type); |
- |
- /** |
- * Registers that [caller] calls [callee] at location [node], with |
- * [selector], and [arguments]. Note that [selector] is null for |
- * forwarding constructors. |
- * |
- * [sideEffects] will be updated to incorporate [callee]'s side |
- * effects. |
- * |
- * [inLoop] tells whether the call happens in a loop. |
- */ |
- T registerCalledElement(Spannable node, |
- Selector selector, |
- Element caller, |
- Element callee, |
- ArgumentsTypes<T> arguments, |
- SideEffects sideEffects, |
- bool inLoop); |
- |
- /** |
- * Registers that [caller] calls [selector] with [receiverType] as |
- * receiver, and [arguments]. |
- * |
- * [sideEffects] will be updated to incorporate the potential |
- * callees' side effects. |
- * |
- * [inLoop] tells whether the call happens in a loop. |
- */ |
- T registerCalledSelector(ast.Node node, |
- Selector selector, |
- T receiverType, |
- Element caller, |
- ArgumentsTypes<T> arguments, |
- SideEffects sideEffects, |
- bool inLoop); |
- |
- /** |
- * Registers that [caller] calls [closure] with [arguments]. |
- * |
- * [sideEffects] will be updated to incorporate the potential |
- * callees' side effects. |
- * |
- * [inLoop] tells whether the call happens in a loop. |
- */ |
- T registerCalledClosure(ast.Node node, |
- Selector selector, |
- T closure, |
- Element caller, |
- ArgumentsTypes<T> arguments, |
- SideEffects sideEffects, |
- bool inLoop); |
- |
- /** |
- * Notifies to the inferrer that [analyzedElement] can have return |
- * type [newType]. [currentType] is the type the [InferrerVisitor] |
- * currently found. |
- * |
- * Returns the new type for [analyzedElement]. |
- */ |
- T addReturnTypeFor(Element analyzedElement, T currentType, T newType); |
- |
- /** |
- * Applies [f] to all elements in the universe that match |
- * [selector]. If [f] returns false, aborts the iteration. |
- */ |
- void forEachElementMatching(Selector selector, bool f(Element element)) { |
- Iterable<Element> elements = compiler.world.allFunctions.filter(selector); |
- for (Element e in elements) { |
- if (!f(e.implementation)) return; |
- } |
- } |
- |
- /** |
- * Update [sideEffects] with the side effects of [callee] being |
- * called with [selector]. |
- */ |
- void updateSideEffects(SideEffects sideEffects, |
- Selector selector, |
- Element callee) { |
- if (callee.isField) { |
- if (callee.isInstanceMember) { |
- if (selector.isSetter) { |
- sideEffects.setChangesInstanceProperty(); |
- } else if (selector.isGetter) { |
- sideEffects.setDependsOnInstancePropertyStore(); |
- } else { |
- sideEffects.setAllSideEffects(); |
- sideEffects.setDependsOnSomething(); |
- } |
- } else { |
- if (selector.isSetter) { |
- sideEffects.setChangesStaticProperty(); |
- } else if (selector.isGetter) { |
- sideEffects.setDependsOnStaticPropertyStore(); |
- } else { |
- sideEffects.setAllSideEffects(); |
- sideEffects.setDependsOnSomething(); |
- } |
- } |
- } else if (callee.isGetter && !selector.isGetter) { |
- sideEffects.setAllSideEffects(); |
- sideEffects.setDependsOnSomething(); |
- } else { |
- sideEffects.add(compiler.world.getSideEffectsOfElement(callee)); |
- } |
- } |
- |
- /** |
- * Returns the type for [nativeBehavior]. See documentation on |
- * [native.NativeBehavior]. |
- */ |
- T typeOfNativeBehavior(native.NativeBehavior nativeBehavior) { |
- if (nativeBehavior == null) return types.dynamicType; |
- List typesReturned = nativeBehavior.typesReturned; |
- if (typesReturned.isEmpty) return types.dynamicType; |
- T returnType; |
- for (var type in typesReturned) { |
- T mappedType; |
- if (type == native.SpecialType.JsObject) { |
- mappedType = types.nonNullExact(compiler.objectClass); |
- } else if (type.element == compiler.stringClass) { |
- mappedType = types.stringType; |
- } else if (type.element == compiler.intClass) { |
- mappedType = types.intType; |
- } else if (type.element == compiler.doubleClass) { |
- mappedType = types.doubleType; |
- } else if (type.element == compiler.numClass) { |
- mappedType = types.numType; |
- } else if (type.element == compiler.boolClass) { |
- mappedType = types.boolType; |
- } else if (type.element == compiler.nullClass) { |
- mappedType = types.nullType; |
- } else if (type.isVoid) { |
- mappedType = types.nullType; |
- } else if (type.isDynamic) { |
- return types.dynamicType; |
- } else { |
- mappedType = types.nonNullSubtype(type.element); |
- } |
- returnType = types.computeLUB(returnType, mappedType); |
- if (returnType == types.dynamicType) { |
- break; |
- } |
- } |
- return returnType; |
- } |
- |
- void updateSelectorInTree( |
- AstElement owner, Spannable node, Selector selector) { |
- if (node is cps_ir.Node) { |
- // TODO(lry): update selector for IrInvokeDynamic. |
- throw "updateSelector for IR node $node"; |
- } |
- ast.Node astNode = node; |
- TreeElements elements = owner.resolvedAst.elements; |
- if (astNode.asSendSet() != null) { |
- if (selector.isSetter || selector.isIndexSet) { |
- elements.setSelector(node, selector); |
- } else if (selector.isGetter || selector.isIndex) { |
- elements.setGetterSelectorInComplexSendSet(node, selector); |
- } else { |
- assert(selector.isOperator); |
- elements.setOperatorSelectorInComplexSendSet(node, selector); |
- } |
- } else if (astNode.asSend() != null) { |
- elements.setSelector(node, selector); |
- } else { |
- assert(astNode.asForIn() != null); |
- if (selector.asUntyped == compiler.iteratorSelector) { |
- elements.setIteratorSelector(node, selector); |
- } else if (selector.asUntyped == compiler.currentSelector) { |
- elements.setCurrentSelector(node, selector); |
- } else { |
- assert(selector.asUntyped == compiler.moveNextSelector); |
- elements.setMoveNextSelector(node, selector); |
- } |
- } |
- } |
- |
- bool isNativeElement(Element element) { |
- if (element.isNative) return true; |
- return element.isClassMember |
- && element.enclosingClass.isNative |
- && element.isField; |
- } |
- |
- void analyze(Element element, ArgumentsTypes arguments); |
- |
- bool checkIfExposesThis(Element element) { |
- element = element.implementation; |
- return generativeConstructorsExposingThis.contains(element); |
- } |
- |
- void recordExposesThis(Element element, bool exposesThis) { |
- element = element.implementation; |
- if (exposesThis) { |
- generativeConstructorsExposingThis.add(element); |
- } |
- } |
-} |
- |
-class SimpleTypeInferrerVisitor<T> |
- extends InferrerVisitor<T, InferrerEngine<T, TypeSystem<T>>> { |
- T returnType; |
- bool visitingInitializers = false; |
- bool isConstructorRedirect = false; |
- bool seenSuperConstructorCall = false; |
- SideEffects sideEffects = new SideEffects.empty(); |
- final Element outermostElement; |
- final InferrerEngine<T, TypeSystem<T>> inferrer; |
- final Setlet<Element> capturedVariables = new Setlet<Element>(); |
- |
- SimpleTypeInferrerVisitor.internal(analyzedElement, |
- this.outermostElement, |
- inferrer, |
- compiler, |
- locals) |
- : super(analyzedElement, inferrer, inferrer.types, compiler, locals), |
- this.inferrer = inferrer { |
- assert(outermostElement != null); |
- } |
- |
- SimpleTypeInferrerVisitor(Element element, |
- Compiler compiler, |
- InferrerEngine<T, TypeSystem<T>> inferrer, |
- [LocalsHandler<T> handler]) |
- : this.internal(element, |
- element.outermostEnclosingMemberOrTopLevel.implementation, |
- inferrer, compiler, handler); |
- |
- void analyzeSuperConstructorCall(Element target, ArgumentsTypes arguments) { |
- inferrer.analyze(target, arguments); |
- isThisExposed = isThisExposed || inferrer.checkIfExposesThis(target); |
- } |
- |
- T run() { |
- var node = analyzedElement.node; |
- ast.Expression initializer; |
- if (analyzedElement.isField) { |
- VariableElement fieldElement = analyzedElement; |
- initializer = fieldElement.initializer; |
- if (initializer == null) { |
- // Eagerly bailout, because computing the closure data only |
- // works for functions and field assignments. |
- return types.nullType; |
- } |
- } |
- // Update the locals that are boxed in [locals]. These locals will |
- // be handled specially, in that we are computing their LUB at |
- // each update, and reading them yields the type that was found in a |
- // previous analysis of [outermostElement]. |
- ClosureClassMap closureData = |
- compiler.closureToClassMapper.computeClosureToClassMapping( |
- analyzedElement, node, elements); |
- closureData.forEachCapturedVariable((variable, field) { |
- locals.setCaptured(variable, field); |
- }); |
- closureData.forEachBoxedVariable((variable, field) { |
- locals.setCapturedAndBoxed(variable, field); |
- }); |
- if (analyzedElement.isField) { |
- return visit(initializer); |
- } |
- |
- FunctionElement function = analyzedElement; |
- FunctionSignature signature = function.functionSignature; |
- signature.forEachOptionalParameter((ParameterElement element) { |
- ast.Expression defaultValue = element.initializer; |
- // If this is a default value from a different context (because |
- // the current function is synthetic, e.g., a constructor from |
- // a mixin application), we have to start a new inferrer visitor |
- // with the correct context. |
- // TODO(johnniwinther): Remove once function signatures are fixed. |
- SimpleTypeInferrerVisitor visitor = this; |
- if (inferrer.hasAlreadyComputedTypeOfParameterDefault(element)) return; |
- if (element.functionDeclaration != analyzedElement) { |
- visitor = new SimpleTypeInferrerVisitor( |
- element.functionDeclaration, compiler, inferrer); |
- } |
- T type = |
- (defaultValue == null) ? types.nullType : visitor.visit(defaultValue); |
- inferrer.setDefaultTypeOfParameter(element, type); |
- }); |
- |
- if (analyzedElement.isNative) { |
- // Native methods do not have a body, and we currently just say |
- // they return dynamic. |
- return types.dynamicType; |
- } |
- |
- if (analyzedElement.isGenerativeConstructor) { |
- isThisExposed = false; |
- signature.forEachParameter((ParameterElement element) { |
- T parameterType = inferrer.typeOfElement(element); |
- if (element.isInitializingFormal) { |
- InitializingFormalElement initializingFormal = element; |
- if (initializingFormal.fieldElement.isFinal) { |
- inferrer.recordTypeOfFinalField( |
- node, |
- analyzedElement, |
- initializingFormal.fieldElement, |
- parameterType); |
- } else { |
- locals.updateField(initializingFormal.fieldElement, parameterType); |
- inferrer.recordTypeOfNonFinalField( |
- initializingFormal.node, |
- initializingFormal.fieldElement, |
- parameterType); |
- } |
- } |
- locals.update(element, parameterType, node); |
- }); |
- ClassElement cls = analyzedElement.enclosingClass; |
- if (analyzedElement.isSynthesized) { |
- node = analyzedElement; |
- ConstructorElement constructor = analyzedElement; |
- synthesizeForwardingCall(node, constructor.definingConstructor); |
- } else { |
- visitingInitializers = true; |
- visit(node.initializers); |
- visitingInitializers = false; |
- // For a generative constructor like: `Foo();`, we synthesize |
- // a call to the default super constructor (the one that takes |
- // no argument). Resolution ensures that such a constructor |
- // exists. |
- if (!isConstructorRedirect |
- && !seenSuperConstructorCall |
- && !cls.isObject) { |
- Selector selector = |
- new Selector.callDefaultConstructor(analyzedElement.library); |
- FunctionElement target = cls.superclass.lookupConstructor(selector); |
- analyzeSuperConstructorCall(target, new ArgumentsTypes([], {})); |
- synthesizeForwardingCall(analyzedElement, target); |
- } |
- visit(node.body); |
- inferrer.recordExposesThis(analyzedElement, isThisExposed); |
- } |
- if (!isConstructorRedirect) { |
- // Iterate over all instance fields, and give a null type to |
- // fields that we haven't initialized for sure. |
- cls.forEachInstanceField((_, FieldElement field) { |
- if (field.isFinal) return; |
- T type = locals.fieldScope.readField(field); |
- if (type == null && field.initializer == null) { |
- inferrer.recordTypeOfNonFinalField(node, field, types.nullType); |
- } |
- }); |
- } |
- returnType = types.nonNullExact(cls); |
- } else { |
- signature.forEachParameter((element) { |
- locals.update(element, inferrer.typeOfElement(element), node); |
- }); |
- visit(node.body); |
- if (returnType == null) { |
- // No return in the body. |
- returnType = locals.seenReturnOrThrow |
- ? types.nonNullEmpty() // Body always throws. |
- : types.nullType; |
- } else if (!locals.seenReturnOrThrow) { |
- // We haven't seen returns on all branches. So the method may |
- // also return null. |
- returnType = inferrer.addReturnTypeFor( |
- analyzedElement, returnType, types.nullType); |
- } |
- } |
- |
- compiler.world.registerSideEffects(analyzedElement, sideEffects); |
- assert(breaksFor.isEmpty); |
- assert(continuesFor.isEmpty); |
- return returnType; |
- } |
- |
- T visitFunctionExpression(ast.FunctionExpression node) { |
- // We loose track of [this] in closures (see issue 20840). To be on |
- // the safe side, we mark [this] as exposed here. We could do better by |
- // analyzing the closure. |
- // TODO(herhut): Analyze whether closure exposes this. |
- isThisExposed = true; |
- LocalFunctionElement element = elements.getFunctionDefinition(node); |
- // We don't put the closure in the work queue of the |
- // inferrer, because it will share information with its enclosing |
- // method, like for example the types of local variables. |
- LocalsHandler closureLocals = new LocalsHandler<T>.from( |
- locals, node, useOtherTryBlock: false); |
- SimpleTypeInferrerVisitor visitor = new SimpleTypeInferrerVisitor<T>( |
- element, compiler, inferrer, closureLocals); |
- visitor.run(); |
- inferrer.recordReturnType(element, visitor.returnType); |
- |
- // Record the types of captured non-boxed variables. Types of |
- // these variables may already be there, because of an analysis of |
- // a previous closure. |
- ClosureClassMap nestedClosureData = |
- compiler.closureToClassMapper.getMappingForNestedFunction(node); |
- nestedClosureData.forEachCapturedVariable((variable, field) { |
- if (!nestedClosureData.isVariableBoxed(variable)) { |
- if (variable == nestedClosureData.thisLocal) { |
- inferrer.recordType(field, thisType); |
- } |
- // The type is null for type parameters. |
- if (locals.locals[variable] == null) return; |
- inferrer.recordType(field, locals.locals[variable]); |
- } |
- capturedVariables.add(variable); |
- }); |
- |
- return inferrer.concreteTypes.putIfAbsent(node, () { |
- return types.allocateClosure(node, element); |
- }); |
- } |
- |
- T visitFunctionDeclaration(ast.FunctionDeclaration node) { |
- LocalFunctionElement element = elements.getFunctionDefinition(node.function); |
- T type = inferrer.concreteTypes.putIfAbsent(node.function, () { |
- return types.allocateClosure(node.function, element); |
- }); |
- locals.update(element, type, node); |
- visit(node.function); |
- return type; |
- } |
- |
- T visitStringInterpolation(ast.StringInterpolation node) { |
- // Interpolation could have any effects since it could call any toString() |
- // method. |
- // TODO(sra): This could be modelled by a call to toString() but with a |
- // guaranteed String return type. Interpolation of known types would get |
- // specialized effects. This would not currently be effective since the JS |
- // code in the toString methods for intercepted primitive types is assumed |
- // to have all effects. Effect annotations on JS code would be needed to |
- // get the benefit. |
- sideEffects.setAllSideEffects(); |
- return super.visitStringInterpolation(node); |
- } |
- |
- T visitLiteralList(ast.LiteralList node) { |
- // We only set the type once. We don't need to re-visit the children |
- // when re-analyzing the node. |
- return inferrer.concreteTypes.putIfAbsent(node, () { |
- T elementType; |
- int length = 0; |
- for (ast.Node element in node.elements.nodes) { |
- T type = visit(element); |
- elementType = elementType == null |
- ? types.allocatePhi(null, null, type) |
- : types.addPhiInput(null, elementType, type); |
- length++; |
- } |
- elementType = elementType == null |
- ? types.nonNullEmpty() |
- : types.simplifyPhi(null, null, elementType); |
- T containerType = node.isConst |
- ? types.constListType |
- : types.growableListType; |
- return types.allocateList( |
- containerType, |
- node, |
- outermostElement, |
- elementType, |
- length); |
- }); |
- } |
- |
- T visitLiteralMap(ast.LiteralMap node) { |
- return inferrer.concreteTypes.putIfAbsent(node, () { |
- ast.NodeList entries = node.entries; |
- List<T> keyTypes = []; |
- List<T> valueTypes = []; |
- |
- for (ast.LiteralMapEntry entry in entries) { |
- keyTypes.add(visit(entry.key)); |
- valueTypes.add(visit(entry.value)); |
- } |
- |
- T type = node.isConst ? types.constMapType : types.mapType; |
- return types.allocateMap(type, |
- node, |
- outermostElement, |
- keyTypes, |
- valueTypes); |
- }); |
- } |
- |
- bool isThisOrSuper(ast.Node node) => node.isThis() || node.isSuper(); |
- |
- bool isInClassOrSubclass(Element element) { |
- ClassElement cls = outermostElement.enclosingClass.declaration; |
- ClassElement enclosing = element.enclosingClass.declaration; |
- return compiler.world.isSubclassOf(enclosing, cls); |
- } |
- |
- void checkIfExposesThis(Selector selector) { |
- if (isThisExposed) return; |
- inferrer.forEachElementMatching(selector, (element) { |
- if (element.isField) { |
- if (!selector.isSetter |
- && isInClassOrSubclass(element) |
- && !element.modifiers.isFinal |
- && locals.fieldScope.readField(element) == null |
- && element.initializer == null) { |
- // If the field is being used before this constructor |
- // actually had a chance to initialize it, say it can be |
- // null. |
- inferrer.recordTypeOfNonFinalField( |
- analyzedElement.node, element, |
- types.nullType); |
- } |
- // Accessing a field does not expose [:this:]. |
- return true; |
- } |
- // TODO(ngeoffray): We could do better here if we knew what we |
- // are calling does not expose this. |
- isThisExposed = true; |
- return false; |
- }); |
- } |
- |
- bool get inInstanceContext { |
- return (outermostElement.isInstanceMember && !outermostElement.isField) |
- || outermostElement.isGenerativeConstructor; |
- } |
- |
- bool treatAsInstanceMember(Element element) { |
- return (Elements.isUnresolved(element) && inInstanceContext) |
- || (element != null && element.isInstanceMember); |
- } |
- |
- T visitSendSet(ast.SendSet node) { |
- Element element = elements[node]; |
- if (!Elements.isUnresolved(element) && element.impliesType) { |
- node.visitChildren(this); |
- return types.dynamicType; |
- } |
- |
- Selector getterSelector = |
- elements.getGetterSelectorInComplexSendSet(node); |
- Selector operatorSelector = |
- elements.getOperatorSelectorInComplexSendSet(node); |
- Selector setterSelector = elements.getSelector(node); |
- |
- String op = node.assignmentOperator.source; |
- bool isIncrementOrDecrement = op == '++' || op == '--'; |
- |
- T receiverType; |
- bool isCallOnThis = false; |
- if (node.receiver == null) { |
- if (treatAsInstanceMember(element)) { |
- receiverType = thisType; |
- isCallOnThis = true; |
- } |
- } else { |
- receiverType = visit(node.receiver); |
- isCallOnThis = isThisOrSuper(node.receiver); |
- } |
- |
- T rhsType; |
- T indexType; |
- |
- if (isIncrementOrDecrement) { |
- rhsType = types.uint31Type; |
- if (node.isIndex) indexType = visit(node.arguments.head); |
- } else if (node.isIndex) { |
- indexType = visit(node.arguments.head); |
- rhsType = visit(node.arguments.tail.head); |
- } else { |
- rhsType = visit(node.arguments.head); |
- } |
- |
- if (!visitingInitializers && !isThisExposed) { |
- for (ast.Node node in node.arguments) { |
- if (isThisOrSuper(node)) { |
- isThisExposed = true; |
- break; |
- } |
- } |
- if (!isThisExposed && isCallOnThis) { |
- checkIfExposesThis( |
- types.newTypedSelector(receiverType, setterSelector)); |
- if (getterSelector != null) { |
- checkIfExposesThis( |
- types.newTypedSelector(receiverType, getterSelector)); |
- } |
- } |
- } |
- |
- if (node.isIndex) { |
- if (op == '=') { |
- // [: foo[0] = 42 :] |
- handleDynamicSend( |
- node, |
- setterSelector, |
- receiverType, |
- new ArgumentsTypes<T>([indexType, rhsType], null)); |
- return rhsType; |
- } else { |
- // [: foo[0] += 42 :] or [: foo[0]++ :]. |
- T getterType = handleDynamicSend( |
- node, |
- getterSelector, |
- receiverType, |
- new ArgumentsTypes<T>([indexType], null)); |
- T returnType = handleDynamicSend( |
- node, |
- operatorSelector, |
- getterType, |
- new ArgumentsTypes<T>([rhsType], null)); |
- handleDynamicSend( |
- node, |
- setterSelector, |
- receiverType, |
- new ArgumentsTypes<T>([indexType, returnType], null)); |
- |
- if (node.isPostfix) { |
- return getterType; |
- } else { |
- return returnType; |
- } |
- } |
- } else if (op == '=') { |
- return handlePlainAssignment( |
- node, element, setterSelector, receiverType, rhsType, |
- node.arguments.head); |
- } else { |
- // [: foo++ :] or [: foo += 1 :]. |
- ArgumentsTypes operatorArguments = new ArgumentsTypes<T>([rhsType], null); |
- T getterType; |
- T newType; |
- if (Elements.isErroneousElement(element)) { |
- getterType = types.dynamicType; |
- newType = types.dynamicType; |
- } else if (Elements.isStaticOrTopLevelField(element)) { |
- Element getterElement = elements[node.selector]; |
- getterType = |
- handleStaticSend(node, getterSelector, getterElement, null); |
- newType = handleDynamicSend( |
- node, operatorSelector, getterType, operatorArguments); |
- handleStaticSend( |
- node, setterSelector, element, |
- new ArgumentsTypes<T>([newType], null)); |
- } else if (Elements.isUnresolved(element) |
- || element.isSetter |
- || element.isField) { |
- getterType = handleDynamicSend( |
- node, getterSelector, receiverType, null); |
- newType = handleDynamicSend( |
- node, operatorSelector, getterType, operatorArguments); |
- handleDynamicSend(node, setterSelector, receiverType, |
- new ArgumentsTypes<T>([newType], null)); |
- } else if (element.isLocal) { |
- LocalElement local = element; |
- getterType = locals.use(local); |
- newType = handleDynamicSend( |
- node, operatorSelector, getterType, operatorArguments); |
- locals.update(element, newType, node); |
- } else { |
- // Bogus SendSet, for example [: myMethod += 42 :]. |
- getterType = types.dynamicType; |
- newType = handleDynamicSend( |
- node, operatorSelector, getterType, operatorArguments); |
- } |
- |
- if (node.isPostfix) { |
- return getterType; |
- } else { |
- return newType; |
- } |
- } |
- } |
- |
- T handlePlainAssignment(ast.Node node, |
- Element element, |
- Selector setterSelector, |
- T receiverType, |
- T rhsType, |
- ast.Node rhs) { |
- ArgumentsTypes arguments = new ArgumentsTypes<T>([rhsType], null); |
- if (Elements.isErroneousElement(element)) { |
- // Code will always throw. |
- } else if (Elements.isStaticOrTopLevelField(element)) { |
- handleStaticSend(node, setterSelector, element, arguments); |
- } else if (Elements.isUnresolved(element) || element.isSetter) { |
- if (analyzedElement.isGenerativeConstructor |
- && (node.asSendSet() != null) |
- && (node.asSendSet().receiver != null) |
- && node.asSendSet().receiver.isThis()) { |
- Iterable<Element> targets = compiler.world.allFunctions.filter( |
- types.newTypedSelector(thisType, setterSelector)); |
- // We just recognized a field initialization of the form: |
- // `this.foo = 42`. If there is only one target, we can update |
- // its type. |
- if (targets.length == 1) { |
- Element single = targets.first; |
- if (single.isField) { |
- locals.updateField(single, rhsType); |
- } |
- } |
- } |
- handleDynamicSend( |
- node, setterSelector, receiverType, arguments); |
- } else if (element.isField) { |
- if (element.isFinal) { |
- inferrer.recordTypeOfFinalField( |
- node, outermostElement, element, rhsType); |
- } else { |
- if (analyzedElement.isGenerativeConstructor) { |
- locals.updateField(element, rhsType); |
- } |
- if (visitingInitializers) { |
- inferrer.recordTypeOfNonFinalField(node, element, rhsType); |
- } else { |
- handleDynamicSend( |
- node, setterSelector, receiverType, arguments); |
- } |
- } |
- } else if (element.isLocal) { |
- locals.update(element, rhsType, node); |
- } |
- return rhsType; |
- } |
- |
- T visitSuperSend(ast.Send node) { |
- Element element = elements[node]; |
- ArgumentsTypes arguments = node.isPropertyAccess |
- ? null |
- : analyzeArguments(node.arguments); |
- if (visitingInitializers) { |
- seenSuperConstructorCall = true; |
- analyzeSuperConstructorCall(element, arguments); |
- } |
- Selector selector = elements.getSelector(node); |
- // TODO(ngeoffray): We could do better here if we knew what we |
- // are calling does not expose this. |
- isThisExposed = true; |
- if (Elements.isUnresolved(element) |
- || !selector.applies(element, compiler.world)) { |
- // Ensure we create a node, to make explicit the call to the |
- // `noSuchMethod` handler. |
- return handleDynamicSend(node, selector, superType, arguments); |
- } else if (node.isPropertyAccess |
- || element.isFunction |
- || element.isGenerativeConstructor) { |
- return handleStaticSend(node, selector, element, arguments); |
- } else { |
- return inferrer.registerCalledClosure( |
- node, selector, inferrer.typeOfElement(element), |
- outermostElement, arguments, sideEffects, inLoop); |
- } |
- } |
- |
- // Try to find the length given to a fixed array constructor call. |
- int findLength(ast.Send node) { |
- ast.Node firstArgument = node.arguments.head; |
- Element element = elements[firstArgument]; |
- ast.LiteralInt length = firstArgument.asLiteralInt(); |
- if (length != null) { |
- return length.value; |
- } else if (element != null |
- && element.isField |
- && Elements.isStaticOrTopLevelField(element) |
- && compiler.world.fieldNeverChanges(element)) { |
- var constant = |
- compiler.backend.constants.getConstantForVariable(element); |
- if (constant != null && constant.value.isInt) { |
- return constant.value.primitiveValue; |
- } |
- } |
- return null; |
- } |
- |
- T visitStaticSend(ast.Send node) { |
- Element element = elements[node]; |
- if (elements.isAssert(node)) { |
- js.JavaScriptBackend backend = compiler.backend; |
- element = backend.assertMethod; |
- } |
- ArgumentsTypes arguments = analyzeArguments(node.arguments); |
- if (visitingInitializers) { |
- if (ast.Initializers.isConstructorRedirect(node)) { |
- isConstructorRedirect = true; |
- } else if (ast.Initializers.isSuperConstructorCall(node)) { |
- seenSuperConstructorCall = true; |
- analyzeSuperConstructorCall(element, arguments); |
- } |
- } |
- // If we are looking at a new expression on a forwarding factory, |
- // we have to forward the call to the effective target of the |
- // factory. |
- if (element.isFactoryConstructor) { |
- // TODO(herhut): Remove the while loop once effectiveTarget forwards to |
- // patches. |
- while (element.isFactoryConstructor) { |
- ConstructorElement constructor = element; |
- if (!constructor.isRedirectingFactory) break; |
- element = constructor.effectiveTarget.implementation; |
- } |
- } |
- if (element.isForeign(compiler.backend)) { |
- return handleForeignSend(node); |
- } |
- Selector selector = elements.getSelector(node); |
- // In erroneous code the number of arguments in the selector might not |
- // match the function element. |
- // TODO(polux): return nonNullEmpty and check it doesn't break anything |
- if (!selector.applies(element, compiler.world)) return types.dynamicType; |
- |
- T returnType = handleStaticSend(node, selector, element, arguments); |
- if (Elements.isGrowableListConstructorCall(element, node, compiler)) { |
- return inferrer.concreteTypes.putIfAbsent( |
- node, () => types.allocateList( |
- types.growableListType, node, outermostElement, |
- types.nonNullEmpty(), 0)); |
- } else if (Elements.isFixedListConstructorCall(element, node, compiler) |
- || Elements.isFilledListConstructorCall(element, node, compiler)) { |
- |
- int length = findLength(node); |
- T elementType = |
- Elements.isFixedListConstructorCall(element, node, compiler) |
- ? types.nullType |
- : arguments.positional[1]; |
- |
- return inferrer.concreteTypes.putIfAbsent( |
- node, () => types.allocateList( |
- types.fixedListType, node, outermostElement, |
- elementType, length)); |
- } else if (Elements.isConstructorOfTypedArraySubclass(element, compiler)) { |
- int length = findLength(node); |
- ConstructorElement constructor = element.implementation; |
- constructor = constructor.effectiveTarget; |
- T elementType = inferrer.returnTypeOfElement( |
- constructor.enclosingClass.lookupMember('[]')); |
- return inferrer.concreteTypes.putIfAbsent( |
- node, () => types.allocateList( |
- types.nonNullExact(constructor.enclosingClass), node, |
- outermostElement, elementType, length)); |
- } else if (element.isFunction || element.isConstructor) { |
- return returnType; |
- } else { |
- assert(element.isField || element.isGetter); |
- return inferrer.registerCalledClosure( |
- node, selector, inferrer.typeOfElement(element), |
- outermostElement, arguments, sideEffects, inLoop); |
- } |
- } |
- |
- T handleForeignSend(ast.Send node) { |
- ArgumentsTypes arguments = analyzeArguments(node.arguments); |
- Selector selector = elements.getSelector(node); |
- String name = selector.name; |
- handleStaticSend(node, selector, elements[node], arguments); |
- if (name == 'JS' || name == 'JS_EMBEDDED_GLOBAL') { |
- native.NativeBehavior nativeBehavior = |
- compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node); |
- sideEffects.add(nativeBehavior.sideEffects); |
- return inferrer.typeOfNativeBehavior(nativeBehavior); |
- } else if (name == 'JS_GET_NAME' |
- || name == 'JS_NULL_CLASS_NAME' |
- || name == 'JS_OBJECT_CLASS_NAME' |
- || name == 'JS_OPERATOR_IS_PREFIX' |
- || name == 'JS_OPERATOR_AS_PREFIX' |
- || name == 'JS_STRING_CONCAT') { |
- return types.stringType; |
- } else { |
- sideEffects.setAllSideEffects(); |
- return types.dynamicType; |
- } |
- } |
- |
- ArgumentsTypes analyzeArguments(Link<ast.Node> arguments) { |
- List<T> positional = []; |
- Map<String, T> named; |
- for (var argument in arguments) { |
- ast.NamedArgument namedArgument = argument.asNamedArgument(); |
- if (namedArgument != null) { |
- argument = namedArgument.expression; |
- if (named == null) named = new Map<String, T>(); |
- named[namedArgument.name.source] = argument.accept(this); |
- } else { |
- positional.add(argument.accept(this)); |
- } |
- // TODO(ngeoffray): We could do better here if we knew what we |
- // are calling does not expose this. |
- isThisExposed = isThisExposed || argument.isThis(); |
- } |
- return new ArgumentsTypes<T>(positional, named); |
- } |
- |
- T visitGetterSend(ast.Send node) { |
- Element element = elements[node]; |
- Selector selector = elements.getSelector(node); |
- if (Elements.isStaticOrTopLevelField(element)) { |
- return handleStaticSend(node, selector, element, null); |
- } else if (Elements.isInstanceSend(node, elements)) { |
- return visitDynamicSend(node); |
- } else if (Elements.isStaticOrTopLevelFunction(element)) { |
- return handleStaticSend(node, selector, element, null); |
- } else if (Elements.isErroneousElement(element)) { |
- return types.dynamicType; |
- } else if (element.isLocal) { |
- LocalElement local = element; |
- assert(locals.use(local) != null); |
- return locals.use(local); |
- } else { |
- assert(element is PrefixElement); |
- return null; |
- } |
- } |
- |
- T visitClosureSend(ast.Send node) { |
- assert(node.receiver == null); |
- T closure = node.selector.accept(this); |
- ArgumentsTypes arguments = analyzeArguments(node.arguments); |
- Element element = elements[node]; |
- Selector selector = elements.getSelector(node); |
- if (element != null && element.isFunction) { |
- assert(element.isLocal); |
- // This only works for function statements. We need a |
- // more sophisticated type system with function types to support |
- // more. |
- return inferrer.registerCalledElement( |
- node, selector, outermostElement, element, arguments, |
- sideEffects, inLoop); |
- } else { |
- return inferrer.registerCalledClosure( |
- node, selector, closure, outermostElement, arguments, |
- sideEffects, inLoop); |
- } |
- } |
- |
- T handleStaticSend(ast.Node node, |
- Selector selector, |
- Element element, |
- ArgumentsTypes arguments) { |
- assert(!element.isFactoryConstructor || |
- !(element as ConstructorElement).isRedirectingFactory); |
- // Erroneous elements may be unresolved, for example missing getters. |
- if (Elements.isUnresolved(element)) return types.dynamicType; |
- // TODO(herhut): should we follow redirecting constructors here? We would |
- // need to pay attention of the constructor is pointing to an erroneous |
- // element. |
- return inferrer.registerCalledElement( |
- node, selector, outermostElement, element, arguments, |
- sideEffects, inLoop); |
- } |
- |
- T handleDynamicSend(ast.Node node, |
- Selector selector, |
- T receiverType, |
- ArgumentsTypes arguments) { |
- assert(receiverType != null); |
- if (types.selectorNeedsUpdate(receiverType, selector)) { |
- selector = (receiverType == types.dynamicType) |
- ? selector.asUntyped |
- : types.newTypedSelector(receiverType, selector); |
- inferrer.updateSelectorInTree(analyzedElement, node, selector); |
- } |
- |
- // If the receiver of the call is a local, we may know more about |
- // its type by refining it with the potential targets of the |
- // calls. |
- if (node.asSend() != null) { |
- ast.Node receiver = node.asSend().receiver; |
- if (receiver != null) { |
- Element element = elements[receiver]; |
- if (Elements.isLocal(element) && !capturedVariables.contains(element)) { |
- T refinedType = types.refineReceiver(selector, receiverType); |
- locals.update(element, refinedType, node); |
- } |
- } |
- } |
- |
- return inferrer.registerCalledSelector( |
- node, selector, receiverType, outermostElement, arguments, |
- sideEffects, inLoop); |
- } |
- |
- T visitDynamicSend(ast.Send node) { |
- Element element = elements[node]; |
- T receiverType; |
- bool isCallOnThis = false; |
- if (node.receiver == null) { |
- if (treatAsInstanceMember(element)) { |
- isCallOnThis = true; |
- receiverType = thisType; |
- } |
- } else { |
- ast.Node receiver = node.receiver; |
- isCallOnThis = isThisOrSuper(receiver); |
- receiverType = visit(receiver); |
- } |
- |
- Selector selector = elements.getSelector(node); |
- if (!isThisExposed && isCallOnThis) { |
- checkIfExposesThis(types.newTypedSelector(receiverType, selector)); |
- } |
- |
- ArgumentsTypes arguments = node.isPropertyAccess |
- ? null |
- : analyzeArguments(node.arguments); |
- if (selector.name == '==' |
- || selector.name == '!=') { |
- if (types.isNull(receiverType)) { |
- potentiallyAddNullCheck(node, node.arguments.head); |
- return types.boolType; |
- } else if (types.isNull(arguments.positional[0])) { |
- potentiallyAddNullCheck(node, node.receiver); |
- return types.boolType; |
- } |
- } |
- return handleDynamicSend(node, selector, receiverType, arguments); |
- } |
- |
- void recordReturnType(T type) { |
- returnType = inferrer.addReturnTypeFor(analyzedElement, returnType, type); |
- } |
- |
- T synthesizeForwardingCall(Spannable node, FunctionElement element) { |
- element = element.implementation; |
- FunctionElement function = analyzedElement; |
- FunctionSignature signature = function.functionSignature; |
- FunctionSignature calleeSignature = element.functionSignature; |
- if (!calleeSignature.isCompatibleWith(signature)) { |
- return types.nonNullEmpty(); |
- } |
- |
- List<T> unnamed = <T>[]; |
- signature.forEachRequiredParameter((ParameterElement element) { |
- assert(locals.use(element) != null); |
- unnamed.add(locals.use(element)); |
- }); |
- |
- Map<String, T> named; |
- if (signature.optionalParametersAreNamed) { |
- named = new Map<String, T>(); |
- signature.forEachOptionalParameter((ParameterElement element) { |
- named[element.name] = locals.use(element); |
- }); |
- } else { |
- signature.forEachOptionalParameter((ParameterElement element) { |
- unnamed.add(locals.use(element)); |
- }); |
- } |
- |
- ArgumentsTypes arguments = new ArgumentsTypes<T>(unnamed, named); |
- return inferrer.registerCalledElement(node, |
- null, |
- outermostElement, |
- element, |
- arguments, |
- sideEffects, |
- inLoop); |
- } |
- T visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) { |
- Element element = elements.getRedirectingTargetConstructor(node); |
- if (Elements.isErroneousElement(element)) { |
- recordReturnType(types.dynamicType); |
- } else { |
- // We don't create a selector for redirecting factories, and |
- // the send is just a property access. Therefore we must |
- // manually create the [ArgumentsTypes] of the call, and |
- // manually register [analyzedElement] as a caller of [element]. |
- T mask = synthesizeForwardingCall(node.constructorReference, element); |
- recordReturnType(mask); |
- } |
- locals.seenReturnOrThrow = true; |
- return null; |
- } |
- |
- T visitReturn(ast.Return node) { |
- ast.Node expression = node.expression; |
- recordReturnType(expression == null |
- ? types.nullType |
- : expression.accept(this)); |
- locals.seenReturnOrThrow = true; |
- return null; |
- } |
- |
- T visitForIn(ast.ForIn node) { |
- T expressionType = visit(node.expression); |
- Selector iteratorSelector = elements.getIteratorSelector(node); |
- Selector currentSelector = elements.getCurrentSelector(node); |
- Selector moveNextSelector = elements.getMoveNextSelector(node); |
- |
- T iteratorType = |
- handleDynamicSend(node, iteratorSelector, expressionType, null); |
- handleDynamicSend(node, moveNextSelector, |
- iteratorType, new ArgumentsTypes<T>([], null)); |
- T currentType = |
- handleDynamicSend(node, currentSelector, iteratorType, null); |
- |
- if (node.expression.isThis()) { |
- // Any reasonable implementation of an iterator would expose |
- // this, so we play it safe and assume it will. |
- isThisExposed = true; |
- } |
- |
- ast.Node identifier = node.declaredIdentifier; |
- Element element = elements.getForInVariable(node); |
- Selector selector = elements.getSelector(identifier); |
- |
- T receiverType; |
- if (element != null && element.isInstanceMember) { |
- receiverType = thisType; |
- } else { |
- receiverType = types.dynamicType; |
- } |
- |
- handlePlainAssignment(identifier, element, selector, |
- receiverType, currentType, |
- node.expression); |
- return handleLoop(node, () { |
- visit(node.body); |
- }); |
- } |
-} |