| // Copyright (c) 2016, 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. |
| |
| import 'package:js_runtime/shared/embedded_names.dart'; |
| import 'package:kernel/ast.dart' as ir; |
| |
| import '../common.dart'; |
| import '../common/names.dart'; |
| import '../compiler.dart'; |
| import '../constants/expressions.dart'; |
| import '../constants/values.dart'; |
| import '../elements/resolution_types.dart'; |
| import '../elements/elements.dart'; |
| import '../elements/entities.dart'; |
| import '../elements/modelx.dart'; |
| import '../elements/types.dart'; |
| import '../js/js.dart' as js; |
| import '../js_backend/backend_helpers.dart'; |
| import '../js_backend/js_backend.dart'; |
| import '../kernel/kernel.dart'; |
| import '../kernel/kernel_debug.dart'; |
| import '../native/native.dart' as native; |
| import '../resolution/tree_elements.dart'; |
| import '../tree/tree.dart' as ast; |
| import '../types/masks.dart'; |
| import '../types/types.dart'; |
| import '../universe/call_structure.dart'; |
| import '../universe/selector.dart'; |
| import '../universe/side_effects.dart'; |
| import '../world.dart'; |
| import 'graph_builder.dart'; |
| import 'jump_handler.dart' show SwitchCaseJumpHandler; |
| import 'locals_handler.dart'; |
| import 'types.dart'; |
| |
| /// Interface that translates between Kernel IR nodes and entities. |
| abstract class KernelWorldBuilder { |
| /// Returns the [DartType] corresponding to [type]. |
| DartType getDartType(ir.DartType type); |
| |
| /// Returns the list of [DartType]s corresponding to [types]. |
| List<DartType> getDartTypes(List<ir.DartType> types); |
| |
| /// Returns the [InterfaceType] corresponding to [type]. |
| InterfaceType getInterfaceType(ir.InterfaceType type); |
| |
| /// Return the [InterfaceType] corresponding to the [cls] with the given |
| /// [typeArguments]. |
| InterfaceType createInterfaceType( |
| ir.Class cls, List<ir.DartType> typeArguments); |
| |
| /// Returns the [CallStructure] corresponding to the [arguments]. |
| CallStructure getCallStructure(ir.Arguments arguments); |
| |
| /// Returns the [Selector] corresponding to the invocation or getter/setter |
| /// access of [node]. |
| Selector getSelector(ir.Expression node); |
| |
| /// Returns the [FunctionEntity] corresponding to the generative or factory |
| /// constructor [node]. |
| FunctionEntity getConstructor(ir.Member node); |
| |
| /// Returns the [MemberEntity] corresponding to the member [node]. |
| MemberEntity getMember(ir.Member node); |
| |
| /// Returns the [FunctionEntity] corresponding to the procedure [node]. |
| FunctionEntity getMethod(ir.Procedure node); |
| |
| /// Returns the [FieldEntity] corresponding to the field [node]. |
| FieldEntity getField(ir.Field node); |
| |
| /// Returns the [ClassEntity] corresponding to the class [node]. |
| ClassEntity getClass(ir.Class node); |
| |
| /// Returns the [Local] corresponding to the [node]. The node must be either |
| /// a [ir.FunctionDeclaration] or [ir.FunctionExpression]. |
| Local getLocalFunction(ir.Node node); |
| |
| /// Returns the [Name] corresponding to [name]. |
| Name getName(ir.Name name); |
| |
| /// Returns `true` is [node] has a `@Native(...)` annotation. |
| bool isNativeClass(ir.Class node); |
| |
| /// Return `true` if [node] is the `dart:_foreign_helper` library. |
| bool isForeignLibrary(ir.Library node); |
| |
| /// Computes the native behavior for reading the native [field]. |
| native.NativeBehavior getNativeBehaviorForFieldLoad(ir.Field field); |
| |
| /// Computes the native behavior for writing to the native [field]. |
| native.NativeBehavior getNativeBehaviorForFieldStore(ir.Field field); |
| |
| /// Computes the native behavior for calling [procedure]. |
| native.NativeBehavior getNativeBehaviorForMethod(ir.Procedure procedure); |
| |
| /// Computes the [native.NativeBehavior] for a call to the [JS] function. |
| native.NativeBehavior getNativeBehaviorForJsCall(ir.StaticInvocation node); |
| |
| /// Computes the [native.NativeBehavior] for a call to the [JS_BUILTIN] |
| /// function. |
| native.NativeBehavior getNativeBehaviorForJsBuiltinCall( |
| ir.StaticInvocation node); |
| |
| /// Computes the [native.NativeBehavior] for a call to the |
| /// [JS_EMBEDDED_GLOBAL] function. |
| native.NativeBehavior getNativeBehaviorForJsEmbeddedGlobalCall( |
| ir.StaticInvocation node); |
| |
| /// Compute the kind of foreign helper function called by [node], if any. |
| ForeignKind getForeignKind(ir.StaticInvocation node); |
| |
| /// Computes the [InterfaceType] referenced by a call to the |
| /// [JS_INTERCEPTOR_CONSTANT] function, if any. |
| InterfaceType getInterfaceTypeForJsInterceptorCall(ir.StaticInvocation node); |
| } |
| |
| /// A helper class that abstracts all accesses of the AST from Kernel nodes. |
| /// |
| /// The goal is to remove all need for the AST from the Kernel SSA builder. |
| class KernelAstAdapter implements KernelWorldBuilder { |
| final Kernel kernel; |
| final JavaScriptBackend _backend; |
| final Map<ir.Node, ast.Node> _nodeToAst; |
| final Map<ir.Node, Element> _nodeToElement; |
| final Map<ir.VariableDeclaration, SyntheticLocal> _syntheticLocals = |
| <ir.VariableDeclaration, SyntheticLocal>{}; |
| // TODO(efortuna): In an ideal world the TreeNodes should be some common |
| // interface we create for both ir.Statements and ir.SwitchCase (the |
| // ContinueSwitchStatement's target is a SwitchCase) rather than general |
| // TreeNode. Talking to Asger about this. |
| final Map<ir.TreeNode, KernelJumpTarget> _jumpTargets = |
| <ir.TreeNode, KernelJumpTarget>{}; |
| DartTypeConverter _typeConverter; |
| ResolvedAst _resolvedAst; |
| |
| /// Sometimes for resolution the resolved AST element needs to change (for |
| /// example, if we're inlining, or if we're in a constructor, but then also |
| /// constructing the field values). We keep track of this with a stack. |
| final List<ResolvedAst> _resolvedAstStack = <ResolvedAst>[]; |
| |
| KernelAstAdapter(this.kernel, this._backend, this._resolvedAst, |
| this._nodeToAst, this._nodeToElement) { |
| KernelJumpTarget.index = 0; |
| // TODO(het): Maybe just use all of the kernel maps directly? |
| for (FieldElement fieldElement in kernel.fields.keys) { |
| _nodeToElement[kernel.fields[fieldElement]] = fieldElement; |
| } |
| for (FunctionElement functionElement in kernel.functions.keys) { |
| _nodeToElement[kernel.functions[functionElement]] = functionElement; |
| } |
| for (ClassElement classElement in kernel.classes.keys) { |
| _nodeToElement[kernel.classes[classElement]] = classElement; |
| } |
| for (LibraryElement libraryElement in kernel.libraries.keys) { |
| _nodeToElement[kernel.libraries[libraryElement]] = libraryElement; |
| } |
| for (LocalFunctionElement localFunction in kernel.localFunctions.keys) { |
| _nodeToElement[kernel.localFunctions[localFunction]] = localFunction; |
| } |
| for (TypeVariableElement typeVariable in kernel.typeParameters.keys) { |
| _nodeToElement[kernel.typeParameters[typeVariable]] = typeVariable; |
| } |
| _typeConverter = new DartTypeConverter(this); |
| } |
| |
| /// Push the existing resolved AST on the stack and shift the current resolved |
| /// AST to the AST that this kernel node points to. |
| void pushResolvedAst(ir.Node node) { |
| _resolvedAstStack.add(_resolvedAst); |
| _resolvedAst = (getElement(node) as AstElement).resolvedAst; |
| } |
| |
| /// Pop the resolved AST stack to reset it to the previous resolved AST node. |
| void popResolvedAstStack() { |
| assert(_resolvedAstStack.isNotEmpty); |
| _resolvedAst = _resolvedAstStack.removeLast(); |
| } |
| |
| Compiler get _compiler => _backend.compiler; |
| TreeElements get elements => _resolvedAst.elements; |
| DiagnosticReporter get reporter => _compiler.reporter; |
| Element get _target => _resolvedAst.element; |
| |
| GlobalTypeInferenceResults get _globalInferenceResults => |
| _compiler.globalInference.results; |
| |
| GlobalTypeInferenceElementResult _resultOf(Element e) => |
| _globalInferenceResults |
| .resultOf(e is ConstructorBodyElementX ? e.constructor : e); |
| |
| ConstantValue getConstantForSymbol(ir.SymbolLiteral node) { |
| if (kernel.syntheticNodes.contains(node)) { |
| return _backend.constantSystem.createSymbol(_compiler, node.value); |
| } |
| ast.Node astNode = getNode(node); |
| ConstantValue constantValue = _backend.constants |
| .getConstantValueForNode(astNode, _resolvedAst.elements); |
| assert(invariant(astNode, constantValue != null, |
| message: 'No constant computed for $node')); |
| return constantValue; |
| } |
| |
| // TODO(johnniwinther): Use the more precise functions below. |
| Element getElement(ir.Node node) { |
| Element result = _nodeToElement[node]; |
| assert(invariant(CURRENT_ELEMENT_SPANNABLE, result != null, |
| message: "No element found for $node.")); |
| return result; |
| } |
| |
| ConstructorElement getConstructor(ir.Member node) => |
| getElement(node).declaration; |
| |
| MemberElement getMember(ir.Member node) => getElement(node).declaration; |
| |
| MethodElement getMethod(ir.Procedure node) => getElement(node).declaration; |
| |
| FieldElement getField(ir.Field node) => getElement(node).declaration; |
| |
| ClassElement getClass(ir.Class node) => getElement(node).declaration; |
| |
| LocalFunctionElement getLocalFunction(ir.Node node) => getElement(node); |
| |
| ast.Node getNode(ir.Node node) { |
| ast.Node result = _nodeToAst[node]; |
| assert(invariant(CURRENT_ELEMENT_SPANNABLE, result != null, |
| message: "No node found for $node")); |
| return result; |
| } |
| |
| ast.Node getNodeOrNull(ir.Node node) { |
| return _nodeToAst[node]; |
| } |
| |
| void assertNodeIsSynthetic(ir.Node node) { |
| assert(invariant( |
| CURRENT_ELEMENT_SPANNABLE, kernel.syntheticNodes.contains(node), |
| message: "No synthetic marker found for $node")); |
| } |
| |
| Local getLocal(ir.VariableDeclaration variable) { |
| // If this is a synthetic local, return the synthetic local |
| if (variable.name == null) { |
| return _syntheticLocals.putIfAbsent( |
| variable, () => new SyntheticLocal("x", null)); |
| } |
| return getElement(variable) as LocalElement; |
| } |
| |
| bool getCanThrow(ir.Node procedure, ClosedWorld closedWorld) { |
| FunctionElement function = getElement(procedure); |
| return !closedWorld.getCannotThrow(function); |
| } |
| |
| TypeMask returnTypeOf(ir.Member node) { |
| return TypeMaskFactory.inferredReturnTypeForElement( |
| getElement(node), _globalInferenceResults); |
| } |
| |
| SideEffects getSideEffects(ir.Node node, ClosedWorld closedWorld) { |
| return closedWorld.getSideEffectsOfElement(getElement(node)); |
| } |
| |
| CallStructure getCallStructure(ir.Arguments arguments) { |
| int argumentCount = arguments.positional.length + arguments.named.length; |
| List<String> namedArguments = arguments.named.map((e) => e.name).toList(); |
| return new CallStructure(argumentCount, namedArguments); |
| } |
| |
| FunctionSignature getFunctionSignature(ir.FunctionNode function) { |
| return getElement(function).asFunctionElement().functionSignature; |
| } |
| |
| Name getName(ir.Name name) { |
| return new Name( |
| name.name, name.isPrivate ? getElement(name.library) : null); |
| } |
| |
| ir.Field getFieldFromElement(FieldElement field) { |
| return kernel.fields[field]; |
| } |
| |
| Selector getSelector(ir.Expression node) { |
| if (node is ir.PropertyGet) return getGetterSelector(node); |
| if (node is ir.PropertySet) return getSetterSelector(node); |
| if (node is ir.InvocationExpression) return getInvocationSelector(node); |
| _compiler.reporter.internalError(getNode(node), |
| "Can only get the selector for a property get or an invocation."); |
| return null; |
| } |
| |
| Selector getInvocationSelector(ir.InvocationExpression invocation) { |
| Name name = getName(invocation.name); |
| SelectorKind kind; |
| if (Elements.isOperatorName(invocation.name.name)) { |
| if (name == Names.INDEX_NAME || name == Names.INDEX_SET_NAME) { |
| kind = SelectorKind.INDEX; |
| } else { |
| kind = SelectorKind.OPERATOR; |
| } |
| } else { |
| kind = SelectorKind.CALL; |
| } |
| |
| CallStructure callStructure = getCallStructure(invocation.arguments); |
| return new Selector(kind, name, callStructure); |
| } |
| |
| Selector getGetterSelector(ir.PropertyGet getter) { |
| ir.Name irName = getter.name; |
| Name name = new Name( |
| irName.name, irName.isPrivate ? getElement(irName.library) : null); |
| return new Selector.getter(name); |
| } |
| |
| Selector getSetterSelector(ir.PropertySet setter) { |
| ir.Name irName = setter.name; |
| Name name = new Name( |
| irName.name, irName.isPrivate ? getElement(irName.library) : null); |
| return new Selector.setter(name); |
| } |
| |
| TypeMask typeOfInvocation(ir.MethodInvocation send, ClosedWorld closedWorld) { |
| ast.Node operatorNode = kernel.nodeToAstOperator[send]; |
| if (operatorNode != null) { |
| return _resultOf(_target).typeOfOperator(operatorNode); |
| } |
| if (send.name.name == '[]=') { |
| return closedWorld.commonMasks.dynamicType; |
| } |
| return _resultOf(_target).typeOfSend(getNode(send)); |
| } |
| |
| TypeMask typeOfGet(ir.PropertyGet getter) { |
| return _resultOf(_target).typeOfSend(getNode(getter)); |
| } |
| |
| TypeMask typeOfSet(ir.PropertySet setter, ClosedWorld closedWorld) { |
| return closedWorld.commonMasks.dynamicType; |
| } |
| |
| TypeMask typeOfSend(ir.Expression send) { |
| assert(send is ir.InvocationExpression || send is ir.PropertyGet); |
| return _resultOf(_target).typeOfSend(getNode(send)); |
| } |
| |
| TypeMask typeOfListLiteral( |
| Element owner, ir.ListLiteral listLiteral, ClosedWorld closedWorld) { |
| ast.Node node = getNodeOrNull(listLiteral); |
| if (node == null) { |
| assertNodeIsSynthetic(listLiteral); |
| return closedWorld.commonMasks.growableListType; |
| } |
| return _resultOf(owner).typeOfListLiteral(getNode(listLiteral)) ?? |
| closedWorld.commonMasks.dynamicType; |
| } |
| |
| TypeMask typeOfIterator(ir.ForInStatement forInStatement) { |
| return _resultOf(_target).typeOfIterator(getNode(forInStatement)); |
| } |
| |
| TypeMask typeOfIteratorCurrent(ir.ForInStatement forInStatement) { |
| return _resultOf(_target).typeOfIteratorCurrent(getNode(forInStatement)); |
| } |
| |
| TypeMask typeOfIteratorMoveNext(ir.ForInStatement forInStatement) { |
| return _resultOf(_target).typeOfIteratorMoveNext(getNode(forInStatement)); |
| } |
| |
| bool isJsIndexableIterator( |
| ir.ForInStatement forInStatement, ClosedWorld closedWorld) { |
| TypeMask mask = typeOfIterator(forInStatement); |
| return mask != null && |
| mask.satisfies(_backend.helpers.jsIndexableClass, closedWorld) && |
| // String is indexable but not iterable. |
| !mask.satisfies(_backend.helpers.jsStringClass, closedWorld); |
| } |
| |
| bool isFixedLength(TypeMask mask, ClosedWorld closedWorld) { |
| if (mask.isContainer && (mask as ContainerTypeMask).length != null) { |
| // A container on which we have inferred the length. |
| return true; |
| } |
| // TODO(sra): Recognize any combination of fixed length indexables. |
| if (mask.containsOnly(closedWorld.backendClasses.fixedListImplementation) || |
| mask.containsOnly(closedWorld.backendClasses.constListImplementation) || |
| mask.containsOnlyString(closedWorld) || |
| closedWorld.commonMasks.isTypedArray(mask)) { |
| return true; |
| } |
| return false; |
| } |
| |
| TypeMask inferredIndexType(ir.ForInStatement forInStatement) { |
| return TypeMaskFactory.inferredTypeForSelector(new Selector.index(), |
| typeOfIterator(forInStatement), _globalInferenceResults); |
| } |
| |
| TypeMask inferredTypeOf(ir.Member node) { |
| return TypeMaskFactory.inferredTypeForElement( |
| getElement(node), _globalInferenceResults); |
| } |
| |
| TypeMask selectorTypeOf(Selector selector, TypeMask mask) { |
| return TypeMaskFactory.inferredTypeForSelector( |
| selector, mask, _globalInferenceResults); |
| } |
| |
| TypeMask typeFromNativeBehavior( |
| native.NativeBehavior nativeBehavior, ClosedWorld closedWorld) { |
| return TypeMaskFactory.fromNativeBehavior(nativeBehavior, closedWorld); |
| } |
| |
| ConstantValue getConstantFor(ir.Node node) { |
| // Some `null`s are not mapped when they correspond to errors, e.g. missing |
| // `const` initializers. |
| if (node is ir.NullLiteral) return new NullConstantValue(); |
| |
| ConstantValue constantValue = |
| _backend.constants.getConstantValueForNode(getNode(node), elements); |
| assert(invariant(getNode(node), constantValue != null, |
| message: 'No constant computed for $node')); |
| return constantValue; |
| } |
| |
| ConstantValue getConstantForParameterDefaultValue(ir.Node defaultExpression) { |
| // TODO(27394): Evaluate constant expressions in ir.Node domain. |
| // In the interim, expand the Constantifier and do this: |
| // |
| // ConstantExpression constantExpression = |
| // defaultExpression.accept(new Constantifier(this)); |
| // assert(constantExpression != null); |
| ConstantExpression constantExpression = |
| kernel.parameterInitializerNodeToConstant[defaultExpression]; |
| if (constantExpression == null) return null; |
| return _backend.constants.getConstantValue(constantExpression); |
| } |
| |
| ConstantValue getConstantForType(ir.DartType irType) { |
| ResolutionDartType type = getDartType(irType); |
| return _backend.constantSystem.createType(_compiler, type.asRaw()); |
| } |
| |
| bool isIntercepted(ir.Node node) { |
| Selector selector = getSelector(node); |
| return _backend.isInterceptedSelector(selector); |
| } |
| |
| bool isInterceptedSelector(Selector selector) { |
| return _backend.isInterceptedSelector(selector); |
| } |
| |
| // Is the member a lazy initialized static or top-level member? |
| bool isLazyStatic(ir.Member member) { |
| if (member is ir.Field) { |
| FieldElement field = _nodeToElement[member]; |
| return field.constant == null; |
| } |
| return false; |
| } |
| |
| LibraryElement get jsHelperLibrary => _backend.helpers.jsHelperLibrary; |
| |
| KernelJumpTarget getJumpTarget(ir.TreeNode node, |
| {bool isContinueTarget: false}) { |
| return _jumpTargets.putIfAbsent(node, () { |
| if (node is ir.LabeledStatement && |
| _jumpTargets.containsKey((node as ir.LabeledStatement).body)) { |
| return _jumpTargets[(node as ir.LabeledStatement).body]; |
| } |
| return new KernelJumpTarget(node, this, |
| makeContinueLabel: isContinueTarget); |
| }); |
| } |
| |
| ir.Class get mapLiteralClass => |
| kernel.classes[_backend.helpers.mapLiteralClass]; |
| |
| ir.Procedure get mapLiteralConstructor => |
| kernel.functions[_backend.helpers.mapLiteralConstructor]; |
| |
| ir.Procedure get mapLiteralConstructorEmpty => |
| kernel.functions[_backend.helpers.mapLiteralConstructorEmpty]; |
| |
| ir.Procedure get mapLiteralUntypedEmptyMaker => |
| kernel.functions[_backend.helpers.mapLiteralUntypedEmptyMaker]; |
| |
| ir.Procedure get exceptionUnwrapper => |
| kernel.functions[_backend.helpers.exceptionUnwrapper]; |
| |
| TypeMask get exceptionUnwrapperType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.exceptionUnwrapper, _globalInferenceResults); |
| |
| ir.Procedure get traceFromException => |
| kernel.functions[_backend.helpers.traceFromException]; |
| |
| TypeMask get traceFromExceptionType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.traceFromException, _globalInferenceResults); |
| |
| ir.Procedure get streamIteratorConstructor => |
| kernel.functions[_backend.helpers.streamIteratorConstructor]; |
| |
| TypeMask get streamIteratorConstructorType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.streamIteratorConstructor, _globalInferenceResults); |
| |
| ir.Procedure get fallThroughError => |
| kernel.functions[_backend.helpers.fallThroughError]; |
| |
| TypeMask get fallThroughErrorType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.fallThroughError, _globalInferenceResults); |
| |
| ir.Procedure get mapLiteralUntypedMaker => |
| kernel.functions[_backend.helpers.mapLiteralUntypedMaker]; |
| |
| MemberElement get jsIndexableLength => _backend.helpers.jsIndexableLength; |
| |
| ir.Procedure get checkConcurrentModificationError => |
| kernel.functions[_backend.helpers.checkConcurrentModificationError]; |
| |
| TypeMask get checkConcurrentModificationErrorReturnType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.checkConcurrentModificationError, |
| _globalInferenceResults); |
| |
| ir.Procedure get checkSubtype => |
| kernel.functions[_backend.helpers.checkSubtype]; |
| |
| ir.Procedure get checkSubtypeOfRuntimeType => |
| kernel.functions[_backend.helpers.checkSubtypeOfRuntimeType]; |
| |
| ir.Procedure get throwTypeError => |
| kernel.functions[_backend.helpers.throwTypeError]; |
| |
| TypeMask get throwTypeErrorType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.throwTypeError, _globalInferenceResults); |
| |
| ir.Procedure get assertHelper => |
| kernel.functions[_backend.helpers.assertHelper]; |
| |
| TypeMask get assertHelperReturnType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.assertHelper, _globalInferenceResults); |
| |
| ir.Procedure get assertTest => kernel.functions[_backend.helpers.assertTest]; |
| |
| TypeMask get assertTestReturnType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.assertTest, _globalInferenceResults); |
| |
| ir.Procedure get assertThrow => |
| kernel.functions[_backend.helpers.assertThrow]; |
| |
| ir.Procedure get setRuntimeTypeInfo => |
| kernel.functions[_backend.helpers.setRuntimeTypeInfo]; |
| |
| TypeMask get assertThrowReturnType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.assertThrow, _globalInferenceResults); |
| |
| ir.Procedure get runtimeTypeToString => |
| kernel.functions[_backend.helpers.runtimeTypeToString]; |
| |
| ir.Procedure get createRuntimeType => |
| kernel.functions[_backend.helpers.createRuntimeType]; |
| |
| TypeMask get createRuntimeTypeReturnType => |
| TypeMaskFactory.inferredReturnTypeForElement( |
| _backend.helpers.createRuntimeType, _globalInferenceResults); |
| |
| ir.Class get objectClass => |
| kernel.classes[_compiler.commonElements.objectClass]; |
| |
| ir.Class get futureClass => |
| kernel.classes[_compiler.commonElements.futureClass]; |
| |
| TypeMask makeSubtypeOfObject(ClosedWorld closedWorld) => |
| new TypeMask.subclass(_compiler.commonElements.objectClass, closedWorld); |
| |
| ir.Procedure get currentIsolate => |
| kernel.functions[_backend.helpers.currentIsolate]; |
| |
| ir.Procedure get callInIsolate => |
| kernel.functions[_backend.helpers.callInIsolate]; |
| |
| bool isInForeignLibrary(ir.Member member) => |
| _backend.isForeign(getElement(member)); |
| |
| native.NativeBehavior getNativeBehavior(ir.Node node) { |
| return elements.getNativeData(getNode(node)); |
| } |
| |
| js.Name getNameForJsGetName(ir.Node argument, ConstantValue constant) { |
| int index = _extractEnumIndexFromConstantValue( |
| constant, _backend.helpers.jsGetNameEnum); |
| if (index == null) return null; |
| return _backend.namer |
| .getNameForJsGetName(getNode(argument), JsGetName.values[index]); |
| } |
| |
| js.Template getJsBuiltinTemplate(ConstantValue constant) { |
| int index = _extractEnumIndexFromConstantValue( |
| constant, _backend.helpers.jsBuiltinEnum); |
| if (index == null) return null; |
| return _backend.emitter.builtinTemplateFor(JsBuiltin.values[index]); |
| } |
| |
| int _extractEnumIndexFromConstantValue( |
| ConstantValue constant, Element classElement) { |
| if (constant is ConstructedConstantValue) { |
| if (constant.type.element == classElement) { |
| assert(constant.fields.length == 1); |
| ConstantValue indexConstant = constant.fields.values.single; |
| if (indexConstant is IntConstantValue) { |
| return indexConstant.primitiveValue; |
| } |
| } |
| } |
| return null; |
| } |
| |
| ResolutionDartType getDartType(ir.DartType type) { |
| return _typeConverter.convert(type); |
| } |
| |
| ResolutionDartType getDartTypeIfValid(ir.DartType type) { |
| if (type is ir.InvalidType) return null; |
| return _typeConverter.convert(type); |
| } |
| |
| List<ResolutionDartType> getDartTypes(List<ir.DartType> types) { |
| return types.map(getDartType).toList(); |
| } |
| |
| ResolutionInterfaceType getDartTypeOfListLiteral(ir.ListLiteral list) { |
| ast.Node node = getNodeOrNull(list); |
| if (node != null) return elements.getType(node); |
| assertNodeIsSynthetic(list); |
| return _compiler.commonElements.listType(getDartType(list.typeArgument)); |
| } |
| |
| ResolutionInterfaceType getDartTypeOfMapLiteral(ir.MapLiteral literal) { |
| ast.Node node = getNodeOrNull(literal); |
| if (node != null) return elements.getType(node); |
| assertNodeIsSynthetic(literal); |
| return _compiler.commonElements |
| .mapType(getDartType(literal.keyType), getDartType(literal.valueType)); |
| } |
| |
| ResolutionDartType getFunctionReturnType(ir.FunctionNode node) { |
| if (node.returnType is ir.InvalidType) return const ResolutionDynamicType(); |
| return getDartType(node.returnType); |
| } |
| |
| /// Computes the function type corresponding the signature of [node]. |
| ResolutionFunctionType getFunctionType(ir.FunctionNode node) { |
| ResolutionDartType returnType = getFunctionReturnType(node); |
| List<ResolutionDartType> parameterTypes = <ResolutionDartType>[]; |
| List<ResolutionDartType> optionalParameterTypes = <ResolutionDartType>[]; |
| for (ir.VariableDeclaration variable in node.positionalParameters) { |
| if (parameterTypes.length == node.requiredParameterCount) { |
| optionalParameterTypes.add(getDartType(variable.type)); |
| } else { |
| parameterTypes.add(getDartType(variable.type)); |
| } |
| } |
| List<String> namedParameters = <String>[]; |
| List<ResolutionDartType> namedParameterTypes = <ResolutionDartType>[]; |
| List<ir.VariableDeclaration> sortedNamedParameters = |
| node.namedParameters.toList()..sort((a, b) => a.name.compareTo(b.name)); |
| for (ir.VariableDeclaration variable in sortedNamedParameters) { |
| namedParameters.add(variable.name); |
| namedParameterTypes.add(getDartType(variable.type)); |
| } |
| return new ResolutionFunctionType.synthesized(returnType, parameterTypes, |
| optionalParameterTypes, namedParameters, namedParameterTypes); |
| } |
| |
| ResolutionInterfaceType getInterfaceType(ir.InterfaceType type) => |
| getDartType(type); |
| |
| ResolutionInterfaceType createInterfaceType( |
| ir.Class cls, List<ir.DartType> typeArguments) { |
| return new ResolutionInterfaceType( |
| getClass(cls), getDartTypes(typeArguments)); |
| } |
| |
| /// Converts [annotations] into a list of [ConstantExpression]s. |
| List<ConstantExpression> getMetadata(List<ir.Expression> annotations) { |
| List<ConstantExpression> metadata = <ConstantExpression>[]; |
| annotations.forEach((ir.Expression node) { |
| ConstantExpression constant = node.accept(new Constantifier(this)); |
| if (constant == null) { |
| throw new UnsupportedError( |
| 'No constant for ${DebugPrinter.prettyPrint(node)}'); |
| } |
| metadata.add(constant); |
| }); |
| return metadata; |
| } |
| |
| /// Compute the kind of foreign helper function called by [node], if any. |
| ForeignKind getForeignKind(ir.StaticInvocation node) { |
| if (isForeignLibrary(node.target.enclosingLibrary)) { |
| switch (node.target.name.name) { |
| case BackendHelpers.JS: |
| return ForeignKind.JS; |
| case BackendHelpers.JS_BUILTIN: |
| return ForeignKind.JS_BUILTIN; |
| case BackendHelpers.JS_EMBEDDED_GLOBAL: |
| return ForeignKind.JS_EMBEDDED_GLOBAL; |
| case BackendHelpers.JS_INTERCEPTOR_CONSTANT: |
| return ForeignKind.JS_INTERCEPTOR_CONSTANT; |
| } |
| } |
| return ForeignKind.NONE; |
| } |
| |
| /// Return `true` if [node] is the `dart:_foreign_helper` library. |
| bool isForeignLibrary(ir.Library node) { |
| return node.importUri == BackendHelpers.DART_FOREIGN_HELPER; |
| } |
| |
| /// Looks up [typeName] for use in the spec-string of a `JS` called. |
| // TODO(johnniwinther): Use this in [native.NativeBehavior] instead of calling |
| // the `ForeignResolver`. |
| // TODO(johnniwinther): Cache the result to avoid redundant lookups? |
| native.TypeLookup _typeLookup({bool resolveAsRaw: true}) { |
| return (String typeName) { |
| ResolutionDartType findIn(Uri uri) { |
| LibraryElement library = _compiler.libraryLoader.lookupLibrary(uri); |
| if (library != null) { |
| Element element = library.find(typeName); |
| if (element != null && element.isClass) { |
| ClassElement cls = element; |
| // TODO(johnniwinther): Align semantics. |
| return resolveAsRaw ? cls.rawType : cls.thisType; |
| } |
| } |
| return null; |
| } |
| |
| ResolutionDartType type = findIn(Uris.dart_core); |
| type ??= findIn(BackendHelpers.DART_JS_HELPER); |
| type ??= findIn(BackendHelpers.DART_INTERCEPTORS); |
| type ??= findIn(BackendHelpers.DART_ISOLATE_HELPER); |
| type ??= findIn(Uris.dart_collection); |
| type ??= findIn(Uris.dart_html); |
| type ??= findIn(Uris.dart_svg); |
| type ??= findIn(Uris.dart_web_audio); |
| type ??= findIn(Uris.dart_web_gl); |
| return type; |
| }; |
| } |
| |
| String _getStringArgument(ir.StaticInvocation node, int index) { |
| return node.arguments.positional[index].accept(new Stringifier()); |
| } |
| |
| /// Computes the [native.NativeBehavior] for a call to the [JS] function. |
| // TODO(johnniwinther): Cache this for later use. |
| native.NativeBehavior getNativeBehaviorForJsCall(ir.StaticInvocation node) { |
| if (node.arguments.positional.length < 2 || |
| node.arguments.named.isNotEmpty) { |
| reporter.reportErrorMessage( |
| CURRENT_ELEMENT_SPANNABLE, MessageKind.WRONG_ARGUMENT_FOR_JS); |
| return new native.NativeBehavior(); |
| } |
| String specString = _getStringArgument(node, 0); |
| if (specString == null) { |
| reporter.reportErrorMessage( |
| CURRENT_ELEMENT_SPANNABLE, MessageKind.WRONG_ARGUMENT_FOR_JS_FIRST); |
| return new native.NativeBehavior(); |
| } |
| |
| String codeString = _getStringArgument(node, 1); |
| if (codeString == null) { |
| reporter.reportErrorMessage( |
| CURRENT_ELEMENT_SPANNABLE, MessageKind.WRONG_ARGUMENT_FOR_JS_SECOND); |
| return new native.NativeBehavior(); |
| } |
| |
| return native.NativeBehavior.ofJsCall( |
| specString, |
| codeString, |
| _typeLookup(resolveAsRaw: true), |
| CURRENT_ELEMENT_SPANNABLE, |
| reporter, |
| _compiler.commonElements); |
| } |
| |
| /// Computes the [native.NativeBehavior] for a call to the [JS_BUILTIN] |
| /// function. |
| // TODO(johnniwinther): Cache this for later use. |
| native.NativeBehavior getNativeBehaviorForJsBuiltinCall( |
| ir.StaticInvocation node) { |
| if (node.arguments.positional.length < 1) { |
| reporter.internalError( |
| CURRENT_ELEMENT_SPANNABLE, "JS builtin expression has no type."); |
| return new native.NativeBehavior(); |
| } |
| if (node.arguments.positional.length < 2) { |
| reporter.internalError( |
| CURRENT_ELEMENT_SPANNABLE, "JS builtin is missing name."); |
| return new native.NativeBehavior(); |
| } |
| String specString = _getStringArgument(node, 0); |
| if (specString == null) { |
| reporter.internalError( |
| CURRENT_ELEMENT_SPANNABLE, "Unexpected first argument."); |
| return new native.NativeBehavior(); |
| } |
| return native.NativeBehavior.ofJsBuiltinCall( |
| specString, |
| _typeLookup(resolveAsRaw: true), |
| CURRENT_ELEMENT_SPANNABLE, |
| reporter, |
| _compiler.commonElements); |
| } |
| |
| /// Computes the [native.NativeBehavior] for a call to the |
| /// [JS_EMBEDDED_GLOBAL] function. |
| // TODO(johnniwinther): Cache this for later use. |
| native.NativeBehavior getNativeBehaviorForJsEmbeddedGlobalCall( |
| ir.StaticInvocation node) { |
| if (node.arguments.positional.length < 1) { |
| reporter.internalError(CURRENT_ELEMENT_SPANNABLE, |
| "JS embedded global expression has no type."); |
| return new native.NativeBehavior(); |
| } |
| if (node.arguments.positional.length < 2) { |
| reporter.internalError( |
| CURRENT_ELEMENT_SPANNABLE, "JS embedded global is missing name."); |
| return new native.NativeBehavior(); |
| } |
| if (node.arguments.positional.length > 2 || |
| node.arguments.named.isNotEmpty) { |
| reporter.internalError(CURRENT_ELEMENT_SPANNABLE, |
| "JS embedded global has more than 2 arguments."); |
| return new native.NativeBehavior(); |
| } |
| String specString = _getStringArgument(node, 0); |
| if (specString == null) { |
| reporter.internalError( |
| CURRENT_ELEMENT_SPANNABLE, "Unexpected first argument."); |
| return new native.NativeBehavior(); |
| } |
| return native.NativeBehavior.ofJsEmbeddedGlobalCall( |
| specString, |
| _typeLookup(resolveAsRaw: true), |
| CURRENT_ELEMENT_SPANNABLE, |
| reporter, |
| _compiler.commonElements); |
| } |
| |
| /// Computes the [InterfaceType] referenced by a call to the |
| /// [JS_INTERCEPTOR_CONSTANT] function, if any. |
| InterfaceType getInterfaceTypeForJsInterceptorCall(ir.StaticInvocation node) { |
| if (node.arguments.positional.length != 1 || |
| node.arguments.named.isNotEmpty) { |
| reporter.reportErrorMessage(CURRENT_ELEMENT_SPANNABLE, |
| MessageKind.WRONG_ARGUMENT_FOR_JS_INTERCEPTOR_CONSTANT); |
| } |
| ir.Node argument = node.arguments.positional.first; |
| if (argument is ir.TypeLiteral && argument.type is ir.InterfaceType) { |
| return getInterfaceType(argument.type); |
| } |
| return null; |
| } |
| |
| /// Returns `true` is [node] has a `@Native(...)` annotation. |
| // TODO(johnniwinther): Cache this for later use. |
| bool isNativeClass(ir.Class node) { |
| for (ir.Expression annotation in node.annotations) { |
| if (annotation is ir.ConstructorInvocation) { |
| ConstructorElement target = getElement(annotation.target).declaration; |
| if (target.enclosingClass == |
| _compiler.commonElements.nativeAnnotationClass) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /// Computes the native behavior for reading the native [field]. |
| // TODO(johnniwinther): Cache this for later use. |
| native.NativeBehavior getNativeBehaviorForFieldLoad(ir.Field field) { |
| ResolutionDartType type = getDartType(field.type); |
| List<ConstantExpression> metadata = getMetadata(field.annotations); |
| return native.NativeBehavior.ofFieldLoad(CURRENT_ELEMENT_SPANNABLE, type, |
| metadata, _typeLookup(resolveAsRaw: false), _compiler, |
| isJsInterop: false); |
| } |
| |
| /// Computes the native behavior for writing to the native [field]. |
| // TODO(johnniwinther): Cache this for later use. |
| native.NativeBehavior getNativeBehaviorForFieldStore(ir.Field field) { |
| ResolutionDartType type = getDartType(field.type); |
| return native.NativeBehavior.ofFieldStore(type, _compiler.resolution); |
| } |
| |
| /// Computes the native behavior for calling [procedure]. |
| // TODO(johnniwinther): Cache this for later use. |
| native.NativeBehavior getNativeBehaviorForMethod(ir.Procedure procedure) { |
| ResolutionDartType type = getFunctionType(procedure.function); |
| List<ConstantExpression> metadata = getMetadata(procedure.annotations); |
| return native.NativeBehavior.ofMethod(CURRENT_ELEMENT_SPANNABLE, type, |
| metadata, _typeLookup(resolveAsRaw: false), _compiler, |
| isJsInterop: false); |
| } |
| |
| MemberEntity getConstructorBodyEntity(ir.Constructor constructor) { |
| AstElement element = getElement(constructor); |
| MemberEntity constructorBody = |
| ConstructorBodyElementX.createFromResolvedAst(element.resolvedAst); |
| assert(constructorBody != null); |
| return constructorBody; |
| } |
| } |
| |
| /// Kinds of foreign functions. |
| enum ForeignKind { |
| JS, |
| JS_BUILTIN, |
| JS_EMBEDDED_GLOBAL, |
| JS_INTERCEPTOR_CONSTANT, |
| NONE, |
| } |
| |
| /// Visitor that converts kernel dart types into [ResolutionDartType]. |
| class DartTypeConverter extends ir.DartTypeVisitor<ResolutionDartType> { |
| final KernelAstAdapter astAdapter; |
| bool topLevel = true; |
| |
| DartTypeConverter(this.astAdapter); |
| |
| ResolutionDartType convert(ir.DartType type) { |
| topLevel = true; |
| return type.accept(this); |
| } |
| |
| /// Visit a inner type. |
| ResolutionDartType visitType(ir.DartType type) { |
| topLevel = false; |
| return type.accept(this); |
| } |
| |
| List<ResolutionDartType> visitTypes(List<ir.DartType> types) { |
| topLevel = false; |
| return new List.generate( |
| types.length, (int index) => types[index].accept(this)); |
| } |
| |
| @override |
| ResolutionDartType visitTypeParameterType(ir.TypeParameterType node) { |
| if (node.parameter.parent is ir.Class) { |
| ir.Class cls = node.parameter.parent; |
| int index = cls.typeParameters.indexOf(node.parameter); |
| ClassElement classElement = astAdapter.getElement(cls); |
| return classElement.typeVariables[index]; |
| } else if (node.parameter.parent is ir.FunctionNode) { |
| ir.FunctionNode func = node.parameter.parent; |
| int index = func.typeParameters.indexOf(node.parameter); |
| Element element = astAdapter.getElement(func); |
| if (element.isConstructor) { |
| ClassElement classElement = element.enclosingClass; |
| return classElement.typeVariables[index]; |
| } else { |
| GenericElement genericElement = element; |
| return genericElement.typeVariables[index]; |
| } |
| } |
| throw new UnsupportedError('Unsupported type parameter type node $node.'); |
| } |
| |
| @override |
| ResolutionDartType visitFunctionType(ir.FunctionType node) { |
| return new ResolutionFunctionType.synthesized( |
| visitType(node.returnType), |
| visitTypes(node.positionalParameters |
| .take(node.requiredParameterCount) |
| .toList()), |
| visitTypes(node.positionalParameters |
| .skip(node.requiredParameterCount) |
| .toList()), |
| node.namedParameters.map((n) => n.name).toList(), |
| node.namedParameters.map((n) => visitType(n.type)).toList()); |
| } |
| |
| @override |
| ResolutionDartType visitInterfaceType(ir.InterfaceType node) { |
| ClassElement cls = astAdapter.getElement(node.classNode); |
| return new ResolutionInterfaceType(cls, visitTypes(node.typeArguments)); |
| } |
| |
| @override |
| ResolutionDartType visitVoidType(ir.VoidType node) { |
| return const ResolutionVoidType(); |
| } |
| |
| @override |
| ResolutionDartType visitDynamicType(ir.DynamicType node) { |
| return const ResolutionDynamicType(); |
| } |
| |
| @override |
| ResolutionDartType visitInvalidType(ir.InvalidType node) { |
| if (topLevel) { |
| throw new UnimplementedError( |
| "Outermost invalid types not currently supported"); |
| } |
| // Nested invalid types are treated as `dynamic`. |
| return const ResolutionDynamicType(); |
| } |
| } |
| |
| /// Visitor that converts string literals and concatenations of string literals |
| /// into the string value. |
| class Stringifier extends ir.ExpressionVisitor<String> { |
| @override |
| String visitStringLiteral(ir.StringLiteral node) => node.value; |
| |
| @override |
| String visitStringConcatenation(ir.StringConcatenation node) { |
| StringBuffer sb = new StringBuffer(); |
| for (ir.Expression expression in node.expressions) { |
| String value = expression.accept(this); |
| if (value == null) return null; |
| sb.write(value); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| /// Visitor that converts a kernel constant expression into a |
| /// [ConstantExpression]. |
| class Constantifier extends ir.ExpressionVisitor<ConstantExpression> { |
| final KernelAstAdapter astAdapter; |
| |
| Constantifier(this.astAdapter); |
| |
| @override |
| ConstantExpression visitConstructorInvocation(ir.ConstructorInvocation node) { |
| ConstructorElement constructor = |
| astAdapter.getElement(node.target).declaration; |
| List<ResolutionDartType> typeArguments = <ResolutionDartType>[]; |
| for (ir.DartType type in node.arguments.types) { |
| typeArguments.add(astAdapter.getDartType(type)); |
| } |
| List<ConstantExpression> arguments = <ConstantExpression>[]; |
| List<String> argumentNames = <String>[]; |
| for (ir.Expression argument in node.arguments.positional) { |
| ConstantExpression constant = argument.accept(this); |
| if (constant == null) return null; |
| arguments.add(constant); |
| } |
| for (ir.NamedExpression argument in node.arguments.named) { |
| argumentNames.add(argument.name); |
| ConstantExpression constant = argument.value.accept(this); |
| if (constant == null) return null; |
| arguments.add(constant); |
| } |
| return new ConstructedConstantExpression( |
| constructor.enclosingClass.thisType.createInstantiation(typeArguments), |
| constructor, |
| new CallStructure( |
| node.arguments.positional.length + argumentNames.length, |
| argumentNames), |
| arguments); |
| } |
| |
| @override |
| ConstantExpression visitStaticGet(ir.StaticGet node) { |
| Element element = astAdapter.getMember(node.target); |
| if (element.isField) { |
| return new VariableConstantExpression(element); |
| } |
| astAdapter.reporter.internalError( |
| CURRENT_ELEMENT_SPANNABLE, "Unexpected constant target: $element."); |
| return null; |
| } |
| |
| @override |
| ConstantExpression visitStringLiteral(ir.StringLiteral node) { |
| return new StringConstantExpression(node.value); |
| } |
| } |
| |
| class KernelJumpTarget extends JumpTarget { |
| static int index = 0; |
| |
| /// Pointer to the actual executable statements that a jump target refers to. |
| /// If this jump target was not initially constructed with a LabeledStatement, |
| /// this value is identical to originalStatement. |
| // TODO(efortuna): In an ideal world the Node should be some common |
| // interface we create for both ir.Statements and ir.SwitchCase (the |
| // ContinueSwitchStatement's target is a SwitchCase) rather than general |
| // Node. Talking to Asger about this. |
| ir.Node targetStatement; |
| |
| /// The original statement used to construct this jump target. |
| /// If this jump target was not initially constructed with a LabeledStatement, |
| /// this value is identical to targetStatement. |
| ir.Node originalStatement; |
| |
| @override |
| bool isBreakTarget = false; |
| |
| @override |
| bool isContinueTarget = false; |
| |
| KernelJumpTarget(this.targetStatement, KernelAstAdapter adapter, |
| {bool makeContinueLabel = false}) { |
| originalStatement = targetStatement; |
| this.labels = <LabelDefinition>[]; |
| if (targetStatement is ir.WhileStatement || |
| targetStatement is ir.DoStatement || |
| targetStatement is ir.ForStatement || |
| targetStatement is ir.ForInStatement) { |
| // Currently these labels are set at resolution on the element itself. |
| // Once that gets updated, this logic can change downstream. |
| JumpTarget target = adapter.elements |
| .getTargetDefinition(adapter.getNode(targetStatement)); |
| if (target != null) { |
| labels.addAll(target.labels); |
| isBreakTarget = target.isBreakTarget; |
| isContinueTarget = target.isContinueTarget; |
| } |
| } else if (targetStatement is ir.LabeledStatement) { |
| targetStatement = (targetStatement as ir.LabeledStatement).body; |
| labels.add( |
| new LabelDefinitionX(null, 'L${index++}', this)..setBreakTarget()); |
| isBreakTarget = true; |
| } |
| |
| if (makeContinueLabel) { |
| labels.add( |
| new LabelDefinitionX(null, 'L${index++}', this)..setContinueTarget()); |
| isContinueTarget = true; |
| } |
| } |
| |
| @override |
| LabelDefinition addLabel(ast.Label label, String labelName) { |
| LabelDefinition result = new LabelDefinitionX(label, labelName, this); |
| labels.add(result); |
| return result; |
| } |
| |
| @override |
| ExecutableElement get executableContext => null; |
| |
| @override |
| bool get isSwitch => targetStatement is ir.SwitchStatement; |
| |
| @override |
| bool get isTarget => isBreakTarget || isContinueTarget; |
| |
| @override |
| List<LabelDefinition> labels; |
| |
| @override |
| String get name => 'target'; |
| |
| // TODO(efortuna): In the original version, this nesting level is specified at |
| // jump target construction time, by the resolver. Because these are |
| // instantiated later, we don't have that information. When we move fully over |
| // to the kernel model, we can pass the nesting level in KernelJumpTarget's |
| // constructor. |
| @override |
| int get nestingLevel => 0; |
| |
| @override |
| ast.Node get statement => null; |
| |
| String toString() => 'Target:$targetStatement'; |
| } |
| |
| /// Special [JumpHandler] implementation used to handle continue statements |
| /// targeting switch cases. |
| class KernelSwitchCaseJumpHandler extends SwitchCaseJumpHandler { |
| KernelSwitchCaseJumpHandler(GraphBuilder builder, JumpTarget target, |
| ir.SwitchStatement switchStatement, KernelAstAdapter astAdapter) |
| : super(builder, target) { |
| // The switch case indices must match those computed in |
| // [KernelSsaBuilder.buildSwitchCaseConstants]. |
| // Switch indices are 1-based so we can bypass the synthetic loop when no |
| // cases match simply by branching on the index (which defaults to null). |
| // TODO |
| int switchIndex = 1; |
| for (ir.SwitchCase switchCase in switchStatement.cases) { |
| JumpTarget continueTarget = |
| astAdapter.getJumpTarget(switchCase, isContinueTarget: true); |
| assert(continueTarget is KernelJumpTarget); |
| targetIndexMap[continueTarget] = switchIndex; |
| assert(builder.jumpTargets[continueTarget] == null); |
| builder.jumpTargets[continueTarget] = this; |
| switchIndex++; |
| } |
| } |
| } |