| // 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 dart_tree; |
| |
| import '../dart2jslib.dart' as dart2js; |
| import '../dart_types.dart'; |
| import '../util/util.dart'; |
| import '../elements/elements.dart' show FunctionElement, FunctionSignature; |
| import '../ir/ir_nodes.dart' as ir; |
| import '../tree/tree.dart' as ast; |
| import '../scanner/scannerlib.dart'; |
| |
| // The Tree language is the target of translation out of the CPS-based IR. |
| // |
| // The translation from CPS to Dart consists of several stages. Among the |
| // stages are translation to direct style, translation out of SSA, eliminating |
| // unnecessary names, recognizing high-level control constructs. Combining |
| // these separate concerns is complicated and the constraints of the CPS-based |
| // language do not permit a multi-stage translation. |
| // |
| // For that reason, CPS is translated to the direct-style language Tree. |
| // Translation out of SSA, unnaming, and control-flow, as well as 'instruction |
| // selection' are performed on the Tree language. |
| // |
| // In contrast to the CPS-based IR, non-primitive expressions can be named and |
| // arguments (to calls, primitives, and blocks) can be arbitrary expressions. |
| |
| /** |
| * The base class of all Tree nodes. |
| */ |
| abstract class Node { |
| accept(Visitor visitor); |
| } |
| |
| /** |
| * The base class of [Expression]s. |
| */ |
| abstract class Expression extends Node { |
| bool get isPure; |
| } |
| |
| /** |
| * Variables are [Expression]s. |
| */ |
| class Variable extends Expression { |
| // A counter used to generate names. The counter is reset to 0 for each |
| // function emitted. |
| static int counter = 0; |
| static String _newName() => 'v${counter++}'; |
| |
| ast.Identifier identifier = null; |
| |
| ast.Identifier assignIdentifier() { |
| assert(identifier == null); |
| String name = _newName(); |
| identifier = Emitter.makeIdentifier(name); |
| return identifier; |
| } |
| |
| final bool isPure = true; |
| |
| accept(Visitor visitor) => visitor.visitVariable(this); |
| } |
| |
| /** |
| * A sequence of expressions. |
| */ |
| class Sequence extends Expression { |
| final List<Expression> expressions; |
| |
| Sequence(this.expressions); |
| |
| bool get isPure => expressions.every((e) => e.isPure); |
| |
| accept(Visitor visitor) => visitor.visitSequence(this); |
| } |
| |
| /** |
| * A local binding of a [Variable] to an [Expression]. |
| * |
| * In contrast to the CPS-based IR, non-primitive expressions can be named |
| * with let. |
| */ |
| class LetVal extends Expression { |
| final Variable variable; |
| final Expression definition; |
| final Expression body; |
| |
| LetVal(this.variable, this.definition, this.body); |
| |
| bool get isPure => definition.isPure && body.isPure; |
| |
| accept(Visitor visitor) => visitor.visitLetVal(this); |
| } |
| |
| /** |
| * A call to a static target. |
| * |
| * In contrast to the CPS-based IR, the arguments can be arbitrary expressions. |
| */ |
| class InvokeStatic extends Expression { |
| final FunctionElement target; |
| final List<Expression> arguments; |
| |
| InvokeStatic(this.target, this.arguments); |
| |
| final bool isPure = false; |
| |
| accept(Visitor visitor) => visitor.visitInvokeStatic(this); |
| } |
| |
| /** |
| * A return exit from the function. |
| * |
| * In contrast to the CPS-based IR, the return value is an arbitrary |
| * expression. |
| */ |
| class Return extends Expression { |
| Expression value; |
| |
| Return(this.value); |
| |
| final bool isPure = true; |
| |
| accept(Visitor visitor) => visitor.visitReturn(this); |
| } |
| |
| /** |
| * A constant. |
| */ |
| class Constant extends Expression { |
| final dart2js.Constant value; |
| |
| Constant(this.value); |
| |
| final bool isPure = true; |
| |
| accept(Visitor visitor) => visitor.visitConstant(this); |
| } |
| |
| abstract class Visitor<T> { |
| // Abstract classes. |
| T visitNode(Node node) => node.accept(this); |
| T visitExpression(Expression node) => visitNode(node); |
| |
| // Concrete classes. |
| T visitVariable(Variable node) => visitExpression(node); |
| T visitSequence(Sequence node) => visitExpression(node); |
| T visitLetVal(LetVal node) => visitExpression(node); |
| T visitInvokeStatic(InvokeStatic node) => visitExpression(node); |
| T visitReturn(Return node) => visitExpression(node); |
| T visitConstant(Constant node) => visitExpression(node); |
| } |
| |
| /** |
| * Builder translates from CPS-based IR to direct-style Tree. |
| * |
| * A call `Invoke(fun, cont, args)`, where cont is a singly-referenced |
| * non-exit continuation `Cont(v, body)` is translated into a direct-style call |
| * whose value is bound in the continuation body: |
| * |
| * `LetVal(v, Invoke(fun, args), body)` |
| * |
| * and the continuation definition is eliminated. A similar translation is |
| * applied to continuation invocations where the continuation is |
| * singly-referenced, though such invocations should not appear in optimized |
| * IR. |
| * |
| * A call `Invoke(fun, cont, args)`, where cont is multiply referenced, is |
| * translated into a call followed by a jump with an argument: |
| * |
| * `Jump L(Invoke(fun, args))` |
| * |
| * and the continuation is translated into a named block that takes an |
| * argument: |
| * |
| * `LetLabel(L, v, body)` |
| * |
| * Block arguments are later replaced with data flow during the Tree-to-Tree |
| * translation out of SSA. Jumps are eliminated during the Tree-to-Tree |
| * control-flow recognition. |
| * |
| * Otherwise, the output of Builder looks very much like the input. In |
| * particular, intermediate values and blocks used for local control flow are |
| * still all named. |
| */ |
| class Builder extends ir.Visitor<Expression> { |
| final dart2js.Compiler compiler; |
| |
| // Uses of IR definitions are replaced with Tree variables. This is the |
| // mapping from definitions to variables. |
| final Map<ir.Definition, Variable> variables = {}; |
| |
| ir.Continuation returnContinuation; |
| |
| Builder(this.compiler); |
| |
| List<Expression> translateArguments(List<ir.Reference> args) { |
| return new List.generate(args.length, |
| (int index) => variables[args[index].definition]); |
| } |
| |
| Expression visitFunction(ir.Function node) { |
| // Functions are simplistically translated to their bodies. For now this |
| // is good enough. |
| returnContinuation = node.returnContinuation; |
| return node.body.accept(this); |
| } |
| |
| Expression visitLetPrim(ir.LetPrim node) { |
| // LetPrim is translated to LetVal. |
| Expression definition = node.primitive.accept(this); |
| if (node.primitive.hasAtLeastOneUse) { |
| Variable variable = new Variable(); |
| variables[node.primitive] = variable; |
| return new LetVal(variable, definition, node.body.accept(this)); |
| } else { |
| return new Sequence([definition, node.body.accept(this)]); |
| } |
| } |
| |
| Expression visitLetCont(ir.LetCont node) { |
| // TODO(kmillikin): Allow continuations to have multiple uses. This could |
| // arise due to the representation of local control flow or due to |
| // optimization. |
| assert(node.continuation.hasAtMostOneUse); |
| return node.body.accept(this); |
| } |
| |
| Expression visitInvokeStatic(ir.InvokeStatic node) { |
| // Calls are translated to direct style. |
| List<Expression> arguments = translateArguments(node.arguments); |
| Expression invoke = new InvokeStatic(node.target, arguments); |
| ir.Continuation cont = node.continuation.definition; |
| if (cont == returnContinuation) { |
| return new Return(invoke); |
| } else { |
| assert(cont.hasExactlyOneUse); |
| if (cont.parameter.hasAtLeastOneUse) { |
| Variable variable = new Variable(); |
| variables[cont.parameter] = variable; |
| return new LetVal(variable, invoke, cont.body.accept(this)); |
| } else { |
| return new Sequence([invoke, cont.body.accept(this)]); |
| } |
| } |
| } |
| |
| Expression visitInvokeContinuation(ir.InvokeContinuation node) { |
| // TODO(kmillikin): Support non-return continuations. These could arise |
| // due to local control flow or due to inlining or other optimization. |
| assert(node.continuation.definition == returnContinuation); |
| return new Return(variables[node.argument.definition]); |
| } |
| |
| Expression visitConstant(ir.Constant node) { |
| return new Constant(node.value); |
| } |
| |
| Expression visitParameter(ir.Parameter node) { |
| // Continuation parameters are not visited (continuations themselves are |
| // not visited yet). |
| compiler.internalError(compiler.currentElement, 'Unexpected IR node.'); |
| return null; |
| } |
| |
| Expression visitContinuation(ir.Continuation node) { |
| // Until continuations with multiple uses are supported, they are not |
| // visited. |
| compiler.internalError(compiler.currentElement, 'Unexpected IR node.'); |
| return null; |
| } |
| } |
| |
| /** |
| * Unnamer propagates single-use definitions to their use site when possible. |
| * |
| * After translating out of CPS, all intermediate values are bound by [LetVal]. |
| * This transformation propagates such definitions to their uses when it is |
| * safe and profitable. Bindings are processed "on demand" when their uses are |
| * seen, but are only processed once to keep this transformation linear in |
| * the size of the tree. |
| * |
| * The transformation builds an environment containing [LetVal] bindings that |
| * are in scope. These bindings have yet-untranslated definitions. When a use |
| * is encountered the transformation determines if it is safe and profitable |
| * to propagate the definition to its use. If so, it is removed from the |
| * environment and the definition is recursively processed (in the |
| * new environment at the use site) before being propagated. |
| * |
| * See [visitVariable] for the implementation of the heuristic for propagating |
| * a definition. |
| */ |
| class Unnamer extends Visitor<Expression> { |
| // The binding environment. The rightmost element of the list is the nearest |
| // enclosing binding. |
| List<LetVal> environment; |
| |
| Expression unname(Expression body) { |
| environment = <LetVal>[]; |
| body = body.accept(this); |
| |
| // TODO(kmillikin): Allow definitions that are not propagated. Here, |
| // this means rebuilding the binding with a recursively unnamed definition, |
| // or else introducing a variable definition and an assignment. |
| assert(environment.isEmpty); |
| return body; |
| } |
| |
| Expression visitVariable(Variable node) { |
| // Propagate a variable's definition to its use site if: |
| // 1. It has a single use, to avoid code growth and potential duplication |
| // of side effects, AND |
| // 2a. It is pure (i.e., does not have side effects that prevent it from |
| // being moved), OR |
| // 2b. There are only pure expressions between the definition and use. |
| |
| // TODO(kmillikin): It's not always beneficial to propagate pure |
| // definitions---it can prevent propagation of their inputs. Implement |
| // a heuristic to avoid this. |
| |
| // TODO(kmillikin): Replace linear search with something faster in |
| // practice. |
| bool seenImpure = false; |
| for (int i = environment.length - 1; i >= 0; --i) { |
| if (environment[i].variable == node) { |
| if (!seenImpure || environment[i].definition.isPure) { |
| // Use the definition if it is pure or if it is the first impure |
| // definition (i.e., propagating past only pure expressions). |
| return environment.removeAt(i).definition.accept(this); |
| } |
| break; |
| } else if (!environment[i].definition.isPure) { |
| // Once the first impure definition is seen, impure definitions should |
| // no longer be propagated. Continue searching for a pure definition. |
| seenImpure = true; |
| } |
| } |
| // If the definition could not be propagated, leave the variable use. |
| return node; |
| } |
| |
| Expression visitSequence(Sequence node) { |
| for (int i = 0; i < node.expressions.length; ++i) { |
| node.expressions[i] = node.expressions[i].accept(this); |
| } |
| return node; |
| } |
| |
| Expression visitLetVal(LetVal node) { |
| environment.add(node); |
| Expression body = node.body.accept(this); |
| |
| // TODO(kmillikin): Allow definitions that are not propagated. Currently, |
| // the only bindings are anonymous intermediate values (which only have one |
| // use in the absence of optimizations) and they are not reordered. |
| assert(!environment.contains(node)); |
| return body; |
| } |
| |
| Expression visitInvokeStatic(InvokeStatic node) { |
| // Process arguments right-to-left, the opposite of evaluation order. |
| for (int i = node.arguments.length - 1; i >= 0; --i) { |
| node.arguments[i] = node.arguments[i].accept(this); |
| } |
| return node; |
| } |
| |
| Expression visitReturn(Return node) { |
| node.value = node.value.accept(this); |
| return node; |
| } |
| |
| visitConstant(Constant node) { |
| return node; |
| } |
| } |
| |
| /** |
| * [Emitter] translates Tree to a Dart AST. |
| * |
| * The AST is handed off to the Dart backend for renaming and to emit Dart |
| * code. Generating an AST is a temporary approach to integrating Tree into |
| * the Dart backend. Ultimately, either Dart code directly will be emitted or |
| * the translation will be to a backend-specific Dart AST and not the same |
| * one used by the front end. |
| * |
| * The front end's AST is an unwieldy interface for constructing and generating |
| * Dart code. AST nodes require references to tokens and the tokens will be |
| * used by the unparser. This means that constructing an AST also requires |
| * constructing (redundant) tokens. Unparsing AST nodes also requires a |
| * mapping from nodes to elements --- so this mapping must be constructed by |
| * the emitter. |
| */ |
| class Emitter extends Visitor<ast.Node> { |
| ConstantEmitter constantEmitter = new ConstantEmitter(); |
| |
| // Accumulate a list of variables used, these are hoisted and declared on |
| // entry to the function. |
| List<ast.Identifier> variables = <ast.Identifier>[]; |
| |
| // A mapping from nodes to elements, constructed while walking the input |
| // tree. |
| dart2js.TreeElementMapping treeElements; |
| |
| // Tokens needed in the AST. |
| final Token openParen = new BeginGroupToken(OPEN_PAREN_INFO, -1); |
| final Token closeParen = new SymbolToken(CLOSE_PAREN_INFO, -1); |
| final Token openBrace = new BeginGroupToken(OPEN_CURLY_BRACKET_INFO, -1); |
| final Token closeBrace = new SymbolToken(CLOSE_CURLY_BRACKET_INFO, -1); |
| final Token semicolon = new SymbolToken(SEMICOLON_INFO, -1); |
| |
| // Helper methods to construct ASTs. |
| static ast.Identifier makeIdentifier(String name) { |
| return new ast.Identifier( |
| new StringToken.fromString(IDENTIFIER_INFO, name, -1)); |
| } |
| |
| ast.NodeList makeArgumentList(List<ast.Node> arguments) { |
| return new ast.NodeList(openParen, |
| new Link<ast.Node>.fromList(arguments), |
| closeParen, |
| ','); |
| } |
| |
| ast.Block makeBlock(List<ast.Node> statements) { |
| return new ast.Block(new ast.NodeList( |
| openBrace, new Link<ast.Node>.fromList(statements), closeBrace)); |
| } |
| |
| static ast.SendSet makeAssignment(ast.Identifier identifier, |
| ast.Expression expression) { |
| return new ast.SendSet( |
| null, |
| identifier, |
| new ast.Operator(new SymbolToken(EQ_INFO, -1)), |
| new ast.NodeList.singleton(expression)); |
| } |
| |
| /** |
| * Translate the body of a function to an AST FunctionExpression. |
| */ |
| ast.FunctionExpression emit(FunctionElement element, |
| dart2js.TreeElementMapping treeElements, |
| Expression expr) { |
| // Reset the variable index. This function is not reentrant. |
| Variable.counter = 0; |
| this.treeElements = treeElements; |
| ast.Identifier name = makeIdentifier(element.name); |
| |
| TypeEmitter typeEmitter = new TypeEmitter(); |
| FunctionSignature signature = element.functionSignature; |
| ast.TypeAnnotation returnType; |
| if (!signature.type.returnType.isDynamic) { |
| returnType = |
| signature.type.returnType.accept(typeEmitter, treeElements); |
| } |
| |
| List<ast.VariableDefinitions> parameterList = <ast.VariableDefinitions>[]; |
| signature.orderedForEachParameter((parameter) { |
| ast.TypeAnnotation type; |
| if (!parameter.type.isDynamic) { |
| type = parameter.type.accept(typeEmitter, treeElements); |
| } |
| parameterList.add(new ast.VariableDefinitions( |
| type, |
| ast.Modifiers.EMPTY, |
| new ast.NodeList.singleton(makeIdentifier(parameter.name)))); |
| }); |
| ast.NodeList parameters = |
| new ast.NodeList(openParen, |
| new Link<ast.Node>.fromList(parameterList), |
| closeParen, |
| ','); |
| |
| ast.Node body = expr.accept(this); |
| |
| if (!variables.isEmpty) { |
| // Introduce hoisted definitions for all variables. |
| ast.Identifier modifier = |
| new ast.Identifier(new KeywordToken(Keyword.keywords['var'], -1)); |
| ast.VariableDefinitions definitions = new ast.VariableDefinitions( |
| null, |
| new ast.Modifiers(new ast.NodeList( |
| null, |
| new Link<ast.Node>.fromList([modifier]), |
| null, |
| ' ')), |
| new ast.NodeList( |
| null, |
| new Link<ast.Node>.fromList(variables), |
| semicolon, |
| ',')); |
| body = concatenate(definitions, body); |
| } |
| |
| if (body is ast.Return) { |
| // Use a short form for bodies that are a single return. |
| ast.Expression value = (body as ast.Return).expression; |
| if (value is ast.LiteralNull) { |
| // '{ return null; }' is '{}'. |
| body = makeBlock([]); |
| } else { |
| // '{ return e; }' is '=> e;'. |
| body = new ast.Return(new SymbolToken(FUNCTION_INFO, -1), |
| semicolon, |
| value); |
| } |
| } else if (body is ast.Block) { |
| // Remove a final 'return null' that ends the body block. |
| Link<ast.Node> nodes = (body as ast.Block).statements.nodes; |
| ast.Node last; |
| for (var n in nodes) { |
| last = n; |
| } |
| if (last is ast.Return |
| && (last as ast.Return).expression is ast.LiteralNull) { |
| List<ast.Node> statements = |
| (body as ast.Block).statements.nodes.toList(); |
| statements.removeLast(); |
| body = makeBlock(statements); |
| } |
| } |
| |
| return new ast.FunctionExpression(name, parameters, body, returnType, |
| ast.Modifiers.EMPTY, null, null); |
| } |
| |
| /** |
| * Translate a list of arguments to an AST NodeList. |
| */ |
| ast.NodeList translateArguments(List<Expression> args) { |
| List<ast.Expression> arguments = |
| args.map((e) => e.accept(this)).toList(growable: false); |
| return makeArgumentList(arguments); |
| } |
| |
| /** |
| * Concatenate a pair of AST expressions or statements into a single Block |
| * statement. |
| */ |
| ast.Node concatenate(ast.Node first, ast.Node second) { |
| // This is a convenient but very inefficient way to accumulate statements. |
| // The Block and NodeList nodes are not mutable so we can't simply use a |
| // Block or NodeList as an accumulator. Using a List<Node> requires |
| // special casing and extra state to handle the expression/statement |
| // distinction. |
| // TODO(kmillikin): If we don't get rid of this Emitter, use a more |
| // efficient way to accumulate nodes. |
| LinkBuilder<ast.Node> statements = new LinkBuilder<ast.Node>(); |
| |
| addStatements(ast.Node node) { |
| if (node is ast.Block) { |
| for (var n in node.statements.nodes) { |
| statements.addLast(n); |
| } |
| } else if (node is ast.Expression) { |
| statements.addLast(new ast.ExpressionStatement(node, semicolon)); |
| } else { |
| statements.addLast(node); |
| } |
| } |
| |
| addStatements(first); |
| addStatements(second); |
| |
| return new ast.Block( |
| new ast.NodeList(openBrace, statements.toLink(), closeBrace)); |
| } |
| |
| ast.Node visitVariable(Variable node) { |
| // The scope of variables is the body of their binding, so a name has |
| // already been generated when we visit a variable. |
| assert(node.identifier != null); |
| return new ast.Send(null, node.identifier); |
| } |
| |
| ast.Node visitSequence(Sequence node) { |
| return node.expressions.map((e) => e.accept(this)).reduce(concatenate); |
| } |
| |
| ast.Node visitLetVal(LetVal node) { |
| // Let bindings translate into assignments. |
| ast.Identifier identifier = node.variable.assignIdentifier(); |
| variables.add(identifier); |
| |
| ast.Expression expression = node.definition.accept(this); |
| ast.Expression assignment = makeAssignment(identifier, expression); |
| |
| ast.Node rest = node.body.accept(this); |
| return concatenate(assignment, rest); |
| } |
| |
| ast.Node visitInvokeStatic(InvokeStatic node) { |
| ast.Identifier name = makeIdentifier(node.target.name); |
| ast.Send send = |
| new ast.Send(null, name, translateArguments(node.arguments)); |
| treeElements[send] = node.target; |
| return send; |
| } |
| |
| ast.Node visitReturn(Return node) { |
| ast.Expression expression = node.value.accept(this); |
| return new ast.Return( |
| new KeywordToken(Keyword.keywords['return'], -1), |
| semicolon, |
| expression); |
| } |
| |
| ast.Node visitConstant(Constant node) { |
| return node.value.accept(constantEmitter); |
| } |
| } |
| |
| class TypeEmitter extends |
| DartTypeVisitor<ast.TypeAnnotation, dart2js.TreeElementMapping> { |
| |
| // Supported types are verified at IR construction time. The unimplemented |
| // emit methods should be unreachable. |
| ast.TypeAnnotation unimplemented() => throw new UnimplementedError(); |
| |
| ast.TypeAnnotation makeSimpleAnnotation( |
| DartType type, |
| dart2js.TreeElementMapping treeElements) { |
| ast.TypeAnnotation annotation = |
| new ast.TypeAnnotation(Emitter.makeIdentifier(type.toString()), null); |
| treeElements.setType(annotation, type); |
| return annotation; |
| } |
| |
| ast.TypeAnnotation visitType(DartType type, |
| dart2js.TreeElementMapping treeElements) { |
| return unimplemented(); |
| } |
| |
| ast.TypeAnnotation visitVoidType(VoidType type, |
| dart2js.TreeElementMapping treeElements) { |
| return makeSimpleAnnotation(type, treeElements); |
| } |
| |
| ast.TypeAnnotation visitInterfaceType( |
| InterfaceType type, |
| dart2js.TreeElementMapping treeElements) { |
| assert(!type.isGeneric); |
| return makeSimpleAnnotation(type, treeElements); |
| } |
| |
| ast.TypeAnnotation visitTypedefType( |
| TypedefType type, |
| dart2js.TreeElementMapping treeElements) { |
| assert(!type.isGeneric); |
| return makeSimpleAnnotation(type, treeElements); |
| } |
| |
| ast.TypeAnnotation visitDynamicType( |
| DynamicType type, |
| dart2js.TreeElementMapping treeElements) { |
| return unimplemented(); |
| } |
| } |
| |
| class ConstantEmitter extends dart2js.ConstantVisitor<ast.Expression> { |
| ast.Expression unimplemented() => throw new UnimplementedError(); |
| |
| ast.Expression visitFunction(dart2js.FunctionConstant constant) { |
| return unimplemented(); |
| } |
| |
| ast.Expression visitNull(dart2js.NullConstant constant) { |
| return new ast.LiteralNull( |
| new KeywordToken(Keyword.keywords['null'], -1)); |
| } |
| |
| ast.Expression visitInt(dart2js.IntConstant constant) { |
| return new ast.LiteralInt( |
| new StringToken.fromString(INT_INFO, constant.value.toString(), -1), |
| null); |
| } |
| |
| ast.Expression visitDouble(dart2js.DoubleConstant constant) { |
| return new ast.LiteralDouble( |
| new StringToken.fromString(DOUBLE_INFO, constant.value.toString(), -1), |
| null); |
| } |
| |
| ast.Expression visitTrue(dart2js.TrueConstant constant) { |
| return new ast.LiteralBool( |
| new KeywordToken(Keyword.keywords['true'], -1), null); |
| } |
| |
| ast.Expression visitFalse(dart2js.FalseConstant constant) { |
| return new ast.LiteralBool( |
| new KeywordToken(Keyword.keywords['false'], -1), null); |
| } |
| |
| ast.Expression visitString(dart2js.StringConstant constant) { |
| return unimplemented(); |
| } |
| |
| ast.Expression visitList(dart2js.ListConstant constant) { |
| return unimplemented(); |
| } |
| |
| ast.Expression visitMap(dart2js.MapConstant constant) { |
| return unimplemented(); |
| } |
| |
| ast.Expression visitConstructed(dart2js.ConstructedConstant constant) { |
| return unimplemented(); |
| } |
| |
| ast.Expression visitType(dart2js.TypeConstant constant) { |
| return unimplemented(); |
| } |
| |
| ast.Expression visitInterceptor(dart2js.InterceptorConstant constant) { |
| return unimplemented(); |
| } |
| |
| ast.Expression visitDummy(dart2js.DummyConstant constant) { |
| return unimplemented(); |
| } |
| } |