| // Copyright (c) 2014, 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 analyzer2dart.cps_generator; |
| |
| import 'package:analyzer/analyzer.dart'; |
| |
| import 'package:compiler/src/dart_types.dart' as dart2js; |
| import 'package:compiler/src/elements/elements.dart' as dart2js; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/generated/element.dart' as analyzer; |
| |
| import 'package:compiler/src/constant_system_dart.dart' |
| show DART_CONSTANT_SYSTEM; |
| import 'package:compiler/src/cps_ir/cps_ir_nodes.dart' as ir; |
| import 'package:compiler/src/cps_ir/cps_ir_builder.dart'; |
| import 'package:compiler/src/universe/universe.dart'; |
| |
| import 'semantic_visitor.dart'; |
| import 'element_converter.dart'; |
| import 'util.dart'; |
| import 'identifier_semantics.dart'; |
| |
| /// Visitor that converts the AST node of an analyzer element into a CPS ir |
| /// node. |
| class CpsElementVisitor extends analyzer.SimpleElementVisitor<ir.Node> { |
| final ElementConverter converter; |
| final AstNode node; |
| |
| CpsElementVisitor(this.converter, this.node); |
| |
| @override |
| ir.FunctionDefinition visitFunctionElement(analyzer.FunctionElement element) { |
| CpsGeneratingVisitor visitor = new CpsGeneratingVisitor(converter, element); |
| FunctionDeclaration functionDeclaration = node; |
| return visitor.handleFunctionDeclaration( |
| element, functionDeclaration.functionExpression.body); |
| } |
| |
| @override |
| ir.FunctionDefinition visitMethodElement(analyzer.MethodElement element) { |
| CpsGeneratingVisitor visitor = new CpsGeneratingVisitor(converter, element); |
| MethodDeclaration methodDeclaration = node; |
| return visitor.handleFunctionDeclaration(element, methodDeclaration.body); |
| } |
| |
| @override |
| ir.FieldDefinition visitTopLevelVariableElement( |
| analyzer.TopLevelVariableElement element) { |
| CpsGeneratingVisitor visitor = new CpsGeneratingVisitor(converter, element); |
| VariableDeclaration variableDeclaration = node; |
| return visitor.handleFieldDeclaration(element, variableDeclaration); |
| } |
| |
| @override |
| ir.RootNode visitConstructorElement(analyzer.ConstructorElement element) { |
| CpsGeneratingVisitor visitor = new CpsGeneratingVisitor(converter, element); |
| if (!element.isFactory) { |
| ConstructorDeclaration constructorDeclaration = node; |
| FunctionBody body; |
| if (constructorDeclaration != null) { |
| body = constructorDeclaration.body; |
| } else { |
| assert(element.isSynthetic); |
| } |
| return visitor.handleConstructorDeclaration(element, body); |
| } |
| // TODO(johnniwinther): Support factory constructors. |
| return null; |
| } |
| } |
| |
| /// Visitor that converts analyzer AST nodes into CPS ir nodes. |
| class CpsGeneratingVisitor extends SemanticVisitor<ir.Node> |
| with IrBuilderMixin<AstNode> { |
| /// Promote the type of [irBuilder] to [DartIrBuilder]. |
| /// The JS backend requires closure conversion which we do not support yet. |
| DartIrBuilder get irBuilder => super.irBuilder; |
| final analyzer.Element element; |
| final ElementConverter converter; |
| |
| CpsGeneratingVisitor(this.converter, this.element); |
| |
| Source get currentSource => element.source; |
| |
| analyzer.LibraryElement get currentLibrary => element.library; |
| |
| ir.Node visit(AstNode node) => node.accept(this); |
| |
| ir.ConstructorDefinition handleConstructorDeclaration( |
| analyzer.ConstructorElement constructor, FunctionBody body) { |
| dart2js.ConstructorElement element = converter.convertElement(constructor); |
| return withBuilder( |
| new DartIrBuilder(DART_CONSTANT_SYSTEM, |
| element, |
| // TODO(johnniwinther): Support closure variables. |
| new Set<dart2js.Local>()), |
| () { |
| irBuilder.buildFunctionHeader( |
| constructor.parameters.map(converter.convertElement)); |
| // Visit the body directly to avoid processing the signature as |
| // expressions. |
| // Call to allow for `body == null` in case of synthesized constructors. |
| build(body); |
| return irBuilder.makeConstructorDefinition(const [], const []); |
| }); |
| } |
| |
| ir.FieldDefinition handleFieldDeclaration( |
| analyzer.PropertyInducingElement field, VariableDeclaration node) { |
| dart2js.FieldElement element = converter.convertElement(field); |
| return withBuilder( |
| new DartIrBuilder(DART_CONSTANT_SYSTEM, |
| element, |
| // TODO(johnniwinther): Support closure variables. |
| new Set<dart2js.Local>()), |
| () { |
| irBuilder.buildFieldInitializerHeader(); |
| ir.Primitive initializer = build(node.initializer); |
| return irBuilder.makeFieldDefinition(initializer); |
| }); |
| } |
| |
| ir.FunctionDefinition handleFunctionDeclaration( |
| analyzer.ExecutableElement function, FunctionBody body) { |
| dart2js.FunctionElement element = converter.convertElement(function); |
| return withBuilder( |
| new DartIrBuilder(DART_CONSTANT_SYSTEM, |
| element, |
| // TODO(johnniwinther): Support closure variables. |
| new Set<dart2js.Local>()), |
| () { |
| irBuilder.buildFunctionHeader( |
| function.parameters.map(converter.convertElement)); |
| // Visit the body directly to avoid processing the signature as |
| // expressions. |
| visit(body); |
| return irBuilder.makeFunctionDefinition(const []); |
| }); |
| } |
| |
| @override |
| ir.Primitive visitFunctionExpression(FunctionExpression node) { |
| return irBuilder.buildFunctionExpression( |
| handleFunctionDeclaration(node.element, node.body)); |
| } |
| |
| @override |
| ir.FunctionDefinition visitFunctionDeclaration(FunctionDeclaration node) { |
| return handleFunctionDeclaration( |
| node.element, node.functionExpression.body); |
| } |
| |
| @override |
| visitFunctionDeclarationStatement(FunctionDeclarationStatement node) { |
| FunctionDeclaration functionDeclaration = node.functionDeclaration; |
| analyzer.FunctionElement function = functionDeclaration.element; |
| dart2js.FunctionElement element = converter.convertElement(function); |
| ir.FunctionDefinition definition = handleFunctionDeclaration( |
| function, functionDeclaration.functionExpression.body); |
| irBuilder.declareLocalFunction(element, definition); |
| } |
| |
| List<ir.Primitive> visitArguments(ArgumentList argumentList) { |
| List<ir.Primitive> arguments = <ir.Primitive>[]; |
| for (Expression argument in argumentList.arguments) { |
| ir.Primitive value = build(argument); |
| if (value == null) { |
| giveUp(argument, |
| 'Unsupported argument: $argument (${argument.runtimeType}).'); |
| } |
| arguments.add(value); |
| } |
| return arguments; |
| } |
| |
| @override |
| ir.Node visitMethodInvocation(MethodInvocation node) { |
| // Overridden to avoid eager visits of the receiver and arguments. |
| return handleMethodInvocation(node); |
| } |
| |
| @override |
| ir.Primitive visitDynamicInvocation(MethodInvocation node, |
| AccessSemantics semantics) { |
| // TODO(johnniwinther): Handle implicit `this`. |
| ir.Primitive receiver = build(semantics.target); |
| List<ir.Primitive> arguments = visitArguments(node.argumentList); |
| return irBuilder.buildDynamicInvocation( |
| receiver, |
| createSelectorFromMethodInvocation( |
| node.argumentList, node.methodName.name), |
| arguments); |
| } |
| |
| @override |
| ir.Primitive visitStaticMethodInvocation(MethodInvocation node, |
| AccessSemantics semantics) { |
| analyzer.Element staticElement = semantics.element; |
| dart2js.Element element = converter.convertElement(staticElement); |
| List<ir.Primitive> arguments = visitArguments(node.argumentList); |
| return irBuilder.buildStaticFunctionInvocation( |
| element, |
| createCallStructureFromMethodInvocation(node.argumentList), |
| arguments); |
| } |
| |
| @override |
| ir.Node visitLocalFunctionAccess(AstNode node, AccessSemantics semantics) { |
| return handleLocalAccess(node, semantics); |
| } |
| |
| ir.Primitive handleLocalInvocation(MethodInvocation node, |
| AccessSemantics semantics) { |
| analyzer.Element staticElement = semantics.element; |
| dart2js.Element element = converter.convertElement(staticElement); |
| List<ir.Definition> arguments = visitArguments(node.argumentList); |
| CallStructure callStructure = createCallStructureFromMethodInvocation( |
| node.argumentList); |
| if (semantics.kind == AccessKind.LOCAL_FUNCTION) { |
| return irBuilder.buildLocalFunctionInvocation( |
| element, callStructure, arguments); |
| } else { |
| return irBuilder.buildLocalVariableInvocation( |
| element, callStructure, arguments); |
| } |
| } |
| |
| @override |
| ir.Node visitLocalVariableInvocation(MethodInvocation node, |
| AccessSemantics semantics) { |
| return handleLocalInvocation(node, semantics); |
| } |
| |
| @override |
| ir.Primitive visitLocalFunctionInvocation(MethodInvocation node, |
| AccessSemantics semantics) { |
| return handleLocalInvocation(node, semantics); |
| } |
| |
| @override |
| ir.Primitive visitFunctionExpressionInvocation( |
| FunctionExpressionInvocation node) { |
| ir.Primitive target = build(node.function); |
| List<ir.Definition> arguments = visitArguments(node.argumentList); |
| return irBuilder.buildCallInvocation( |
| target, |
| createCallStructureFromMethodInvocation(node.argumentList), |
| arguments); |
| } |
| |
| @override |
| ir.Primitive visitInstanceCreationExpression( |
| InstanceCreationExpression node) { |
| analyzer.Element staticElement = node.staticElement; |
| if (staticElement != null) { |
| dart2js.Element element = converter.convertElement(staticElement); |
| dart2js.DartType type = converter.convertType(node.staticType); |
| List<ir.Primitive> arguments = visitArguments(node.argumentList); |
| return irBuilder.buildConstructorInvocation( |
| element, |
| createCallStructureFromMethodInvocation(node.argumentList), |
| type, |
| arguments); |
| } |
| return giveUp(node, "Unresolved constructor invocation."); |
| } |
| |
| @override |
| ir.Constant visitNullLiteral(NullLiteral node) { |
| return irBuilder.buildNullConstant(); |
| } |
| |
| @override |
| ir.Constant visitBooleanLiteral(BooleanLiteral node) { |
| return irBuilder.buildBooleanConstant(node.value); |
| } |
| |
| @override |
| ir.Constant visitDoubleLiteral(DoubleLiteral node) { |
| return irBuilder.buildDoubleConstant(node.value); |
| } |
| |
| @override |
| ir.Constant visitIntegerLiteral(IntegerLiteral node) { |
| return irBuilder.buildIntegerConstant(node.value); |
| } |
| |
| @override |
| visitAdjacentStrings(AdjacentStrings node) { |
| String value = node.stringValue; |
| if (value != null) { |
| return irBuilder.buildStringConstant(value); |
| } |
| giveUp(node, "Non constant adjacent strings."); |
| } |
| |
| @override |
| ir.Constant visitSimpleStringLiteral(SimpleStringLiteral node) { |
| return irBuilder.buildStringConstant(node.value); |
| } |
| |
| @override |
| visitStringInterpolation(StringInterpolation node) { |
| giveUp(node, "String interpolation."); |
| } |
| |
| @override |
| visitReturnStatement(ReturnStatement node) { |
| irBuilder.buildReturn(build(node.expression)); |
| } |
| |
| @override |
| ir.Node visitPropertyAccess(PropertyAccess node) { |
| // Overridden to avoid eager visits of the receiver. |
| return handlePropertyAccess(node); |
| } |
| |
| @override |
| ir.Node visitLocalVariableAccess(AstNode node, AccessSemantics semantics) { |
| return handleLocalAccess(node, semantics); |
| } |
| |
| @override |
| ir.Node visitParameterAccess(AstNode node, AccessSemantics semantics) { |
| return handleLocalAccess(node, semantics); |
| } |
| |
| @override |
| visitVariableDeclaration(VariableDeclaration node) { |
| // TODO(johnniwinther): Handle constant local variables. |
| ir.Node initialValue = build(node.initializer); |
| irBuilder.declareLocalVariable( |
| converter.convertElement(node.element), |
| initialValue: initialValue); |
| } |
| |
| dart2js.Element getLocal(AstNode node, AccessSemantics semantics) { |
| analyzer.Element element = semantics.element; |
| dart2js.Element target = converter.convertElement(element); |
| assert(invariant(node, target.isLocal, '$target expected to be local.')); |
| return target; |
| } |
| |
| ir.Primitive handleLocalAccess(AstNode node, AccessSemantics semantics) { |
| dart2js.Element local = getLocal(node, semantics); |
| if (semantics.kind == AccessKind.LOCAL_FUNCTION) { |
| return irBuilder.buildLocalFunctionGet(local); |
| } else { |
| return irBuilder.buildLocalVariableGet(local); |
| } |
| } |
| |
| ir.Primitive handleLocalAssignment(AssignmentExpression node, |
| AccessSemantics semantics) { |
| if (node.operator.lexeme != '=') { |
| return giveUp(node, 'Assignment operator: ${node.operator.lexeme}'); |
| } |
| return irBuilder.buildLocalVariableSet( |
| getLocal(node, semantics), |
| build(node.rightHandSide)); |
| } |
| |
| @override |
| ir.Node visitAssignmentExpression(AssignmentExpression node) { |
| // Avoid eager visiting of left and right hand side. |
| return handleAssignmentExpression(node); |
| } |
| |
| @override |
| ir.Node visitLocalVariableAssignment(AssignmentExpression node, |
| AccessSemantics semantics) { |
| return handleLocalAssignment(node, semantics); |
| } |
| |
| @override |
| ir.Node visitParameterAssignment(AssignmentExpression node, |
| AccessSemantics semantics) { |
| return handleLocalAssignment(node, semantics); |
| } |
| |
| @override |
| ir.Node visitStaticFieldAssignment(AssignmentExpression node, |
| AccessSemantics semantics) { |
| if (node.operator.lexeme != '=') { |
| return giveUp(node, 'Assignment operator: ${node.operator.lexeme}'); |
| } |
| analyzer.Element element = semantics.element; |
| dart2js.Element target = converter.convertElement(element); |
| // TODO(johnniwinther): Selector information should be computed in the |
| // [TreeShaker] and shared with the [CpsGeneratingVisitor]. |
| assert(invariant(node, target.isTopLevel || target.isStatic, |
| '$target expected to be top-level or static.')); |
| return irBuilder.buildStaticFieldSet(target, build(node.rightHandSide)); |
| } |
| |
| @override |
| ir.Node visitDynamicAccess(AstNode node, AccessSemantics semantics) { |
| // TODO(johnniwinther): Handle implicit `this`. |
| ir.Primitive receiver = build(semantics.target); |
| return irBuilder.buildDynamicGet(receiver, |
| new Selector.getter(semantics.identifier.name, |
| converter.convertElement(element.library))); |
| } |
| |
| @override |
| ir.Node visitStaticFieldAccess(AstNode node, AccessSemantics semantics) { |
| analyzer.Element element = semantics.element; |
| dart2js.Element target = converter.convertElement(element); |
| // TODO(johnniwinther): Selector information should be computed in the |
| // [TreeShaker] and shared with the [CpsGeneratingVisitor]. |
| assert(invariant(node, target.isTopLevel || target.isStatic, |
| '$target expected to be top-level or static.')); |
| return irBuilder.buildStaticFieldLazyGet(target, null); |
| } |
| |
| ir.Primitive handleBinaryExpression(BinaryExpression node, |
| String op) { |
| ir.Primitive left = build(node.leftOperand); |
| ir.Primitive right = build(node.rightOperand); |
| Selector selector = new Selector.binaryOperator(op); |
| return irBuilder.buildDynamicInvocation( |
| left, selector, <ir.Primitive>[right]); |
| } |
| |
| ir.Node handleLazyOperator(BinaryExpression node, {bool isLazyOr: false}) { |
| return irBuilder.buildLogicalOperator( |
| build(node.leftOperand), |
| subbuild(node.rightOperand), |
| isLazyOr: isLazyOr); |
| } |
| |
| @override |
| ir.Node visitBinaryExpression(BinaryExpression node) { |
| // TODO(johnniwinther,paulberry,brianwilkerson): The operator should be |
| // available through an enum. |
| String op = node.operator.lexeme; |
| switch (op) { |
| case '||': |
| case '&&': |
| return handleLazyOperator(node, isLazyOr: op == '||'); |
| case '!=': |
| return irBuilder.buildNegation(handleBinaryExpression(node, '==')); |
| default: |
| return handleBinaryExpression(node, op); |
| } |
| } |
| |
| @override |
| ir.Node visitConditionalExpression(ConditionalExpression node) { |
| return irBuilder.buildConditional( |
| build(node.condition), |
| subbuild(node.thenExpression), |
| subbuild(node.elseExpression)); |
| } |
| |
| @override |
| visitIfStatement(IfStatement node) { |
| irBuilder.buildIf( |
| build(node.condition), |
| subbuild(node.thenStatement), |
| subbuild(node.elseStatement)); |
| } |
| |
| @override |
| visitBlock(Block node) { |
| irBuilder.buildBlock(node.statements, build); |
| } |
| |
| @override |
| ir.Node visitListLiteral(ListLiteral node) { |
| dart2js.InterfaceType type = converter.convertType(node.staticType); |
| // TODO(johnniwinther): Use `build` instead of `(e) => build(e)` when issue |
| // 18630 has been resolved. |
| Iterable<ir.Primitive> values = node.elements.map((e) => build(e)); |
| return irBuilder.buildListLiteral(type, values); |
| } |
| |
| @override |
| ir.Node visitMapLiteral(MapLiteral node) { |
| dart2js.InterfaceType type = converter.convertType(node.staticType); |
| return irBuilder.buildMapLiteral( |
| type, |
| node.entries.map((e) => e.key), |
| node.entries.map((e) => e.value), |
| build); |
| } |
| |
| @override |
| visitForStatement(ForStatement node) { |
| // TODO(johnniwinther): Support `for` as a jump target. |
| List<dart2js.LocalElement> loopVariables = <dart2js.LocalElement>[]; |
| SubbuildFunction buildInitializer; |
| if (node.variables != null) { |
| buildInitializer = subbuild(node.variables); |
| for (VariableDeclaration variable in node.variables.variables) { |
| loopVariables.add(converter.convertElement(variable.element)); |
| } |
| } else { |
| buildInitializer = subbuild(node.initialization); |
| } |
| irBuilder.buildFor(buildInitializer: buildInitializer, |
| buildCondition: subbuild(node.condition), |
| buildBody: subbuild(node.body), |
| buildUpdate: subbuildSequence(node.updaters), |
| loopVariables: loopVariables); |
| } |
| |
| @override |
| visitWhileStatement(WhileStatement node) { |
| // TODO(johnniwinther): Support `while` as a jump target. |
| irBuilder.buildWhile(buildCondition: subbuild(node.condition), |
| buildBody: subbuild(node.body)); |
| } |
| |
| @override |
| visitDeclaredIdentifier(DeclaredIdentifier node) { |
| giveUp(node, "Unexpected node: DeclaredIdentifier"); |
| } |
| |
| @override |
| visitForEachStatement(ForEachStatement node) { |
| SubbuildFunction buildVariableDeclaration; |
| dart2js.Element variableElement; |
| Selector variableSelector; |
| if (node.identifier != null) { |
| AccessSemantics accessSemantics = |
| node.identifier.accept(ACCESS_SEMANTICS_VISITOR); |
| if (accessSemantics.kind == AccessKind.DYNAMIC) { |
| variableSelector = new Selector.setter( |
| node.identifier.name, converter.convertElement(currentLibrary)); |
| } else if (accessSemantics.element != null) { |
| variableElement = converter.convertElement(accessSemantics.element); |
| variableSelector = new Selector.setter( |
| variableElement.name, |
| converter.convertElement(accessSemantics.element.library)); |
| } else { |
| giveUp(node, 'For-in of unresolved variable: $accessSemantics'); |
| } |
| } else { |
| assert(invariant( |
| node, node.loopVariable != null, "Loop variable expected")); |
| variableElement = converter.convertElement(node.loopVariable.element); |
| buildVariableDeclaration = (IrBuilder builder) { |
| builder.declareLocalVariable(variableElement); |
| }; |
| } |
| // TODO(johnniwinther): Support `for-in` as a jump target. |
| irBuilder.buildForIn( |
| buildExpression: subbuild(node.iterable), |
| buildVariableDeclaration: buildVariableDeclaration, |
| variableElement: variableElement, |
| variableSelector: variableSelector, |
| buildBody: subbuild(node.body)); |
| } |
| @override |
| ir.Primitive visitIsExpression(IsExpression node) { |
| return irBuilder.buildTypeOperator( |
| visit(node.expression), |
| converter.convertType(node.type.type), |
| isTypeTest: true, |
| isNotCheck: node.notOperator != null); |
| } |
| |
| @override |
| ir.Primitive visitAsExpression(AsExpression node) { |
| return irBuilder.buildTypeOperator( |
| visit(node.expression), |
| converter.convertType(node.type.type), |
| isTypeTest: false); |
| } |
| |
| @override |
| visitTryStatement(TryStatement node) { |
| List<CatchClauseInfo> catchClauseInfos = <CatchClauseInfo>[]; |
| for (CatchClause catchClause in node.catchClauses) { |
| catchClauseInfos.add(new CatchClauseInfo( |
| exceptionVariable: converter.convertElement( |
| catchClause.exceptionParameter.staticElement), |
| buildCatchBlock: subbuild(catchClause.body))); |
| |
| } |
| irBuilder.buildTry( |
| tryStatementInfo: new TryStatementInfo(), |
| buildTryBlock: subbuild(node.body), |
| catchClauseInfos: catchClauseInfos); |
| } |
| } |