Index: frog/analyze.dart |
diff --git a/frog/analyze.dart b/frog/analyze.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6b4c21bd7e254014de4ef10fef1018752c71da57 |
--- /dev/null |
+++ b/frog/analyze.dart |
@@ -0,0 +1,748 @@ |
+// Copyright (c) 2012, 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. |
+ |
+/** |
+ * A simple code analyzer for Dart. |
+ * |
+ * Currently used to ensure all concrete generic types are visited. |
+ * Also performs all static type checks - so these don't need to be |
+ * done in later phases. |
+ * |
+ * Ultimately, this should include abstract interpreter work. This will |
+ * result in an interesting split beteen this class and MethodGenerator |
+ * which should be turned into nothing more than a code generator. |
+ */ |
+// TODO(jimhug): This class shares too much code with MethodGenerator. |
+class MethodAnalyzer implements TreeVisitor { |
+ MethodMember method; |
+ Statement body; |
+ |
+ CallFrame _frame; |
+ |
+ /** |
+ * Track whether or not [body] refers to any type parameters from the |
+ * enclosing type to advise future code generation and analysis. |
+ */ |
+ bool hasTypeParams = false; |
+ |
+ MethodAnalyzer(this.method, this.body); |
+ |
+ // TODO(jimhug): Type issue with requiring CallFrame here... |
+ void analyze(CallFrame context) { |
+ var thisValue; |
+ // TODO(jimhug): Move Constructor analysis to here and below. |
+ |
+ if (context != null) { |
+ thisValue = context.thisValue; |
+ } else { |
+ thisValue = new PureStaticValue(method.declaringType, null); |
+ } |
+ var values = []; |
+ for (var p in method.parameters) { |
+ values.add(new PureStaticValue(p.type, null)); |
+ } |
+ var args = new Arguments(null, values); |
+ |
+ _frame = new CallFrame(this, method, thisValue, args, context); |
+ _bindArguments(_frame.args); |
+ body.visit(this); |
+ } |
+ |
+ /* Checks whether or not a particular TypeReference Node includes references |
+ * to type parameters. */ |
+ bool _hasTypeParams(node) { |
+ if (node is NameTypeReference) { |
+ var name = node.name.name; |
+ return (method.declaringType.lookupTypeParam(name) != null); |
+ } else if (node is GenericTypeReference) { |
+ for (var typeArg in node.typeArguments) { |
+ if (_hasTypeParams(typeArg)) return true; |
+ } |
+ return false; |
+ } else { |
+ // TODO(jimhug): Do we need to include FunctionTypeReference here? |
+ return false; |
+ } |
+ } |
+ |
+ Type resolveType(TypeReference node, bool typeErrors, |
+ bool allowTypeParams) { |
+ if (!hasTypeParams && _hasTypeParams(node)) { |
+ hasTypeParams = true; |
+ } |
+ return method.resolveType(node, typeErrors, allowTypeParams); |
+ } |
+ |
+ |
+ Value makeNeutral(Value inValue, SourceSpan span) { |
+ return new PureStaticValue(inValue.type, span); |
+ } |
+ |
+ void _bindArguments(Arguments args) { |
+ for (int i = 0; i < method.parameters.length; i++) { |
+ var p = method.parameters[i]; |
+ Value currentArg = null; |
+ if (i < args.bareCount) { |
+ currentArg = args.values[i]; |
+ } else { |
+ // Handle named or missing arguments |
+ currentArg = args.getValue(p.name); |
+ if (currentArg === null) { |
+ // Ensure default value for param has been generated |
+ p.genValue(method, _frame); // TODO(jimhug): Needed here? |
+ if (p.value === null) { |
+ world.warning('missing argument at call - does not match'); |
+ } |
+ currentArg = p.value; |
+ } |
+ } |
+ |
+ currentArg = makeNeutral(currentArg, p.definition.span); |
+ |
+ // TODO(jimhug): Add checks for constructor initializers. |
+ _frame.declareParameter(p, currentArg); |
+ // TODO(jimhug): Add type check? |
+ } |
+ } |
+ |
+ visitBool(Expression node) { |
+ return visitTypedValue(node, world.boolType); |
+ } |
+ |
+ visitValue(Expression node) { |
+ if (node == null) return null; |
+ |
+ var value = node.visit(this); |
+ value.checkFirstClass(node.span); |
+ return value; |
+ } |
+ |
+ /** |
+ * Visit [node] and ensure statically or with an runtime check that it has |
+ * the expected type (if specified). |
+ */ |
+ visitTypedValue(Expression node, Type expectedType) { |
+ final val = visitValue(node); |
+ return val; |
+ // TODO(jimhug): val.convertTo(this, expectedType); |
+ } |
+ |
+ visitVoid(Expression node) { |
+ // TODO(jimhug): Add some helpful diagnostics for silly void uses. |
+ return visitValue(node); |
+ } |
+ |
+ |
+ Arguments _visitArgs(List<ArgumentNode> arguments) { |
+ var args = []; |
+ bool seenLabel = false; |
+ for (var arg in arguments) { |
+ if (arg.label != null) { |
+ seenLabel = true; |
+ } else if (seenLabel) { |
+ // TODO(jimhug): Move this into parser? |
+ world.error('bare argument can not follow named arguments', arg.span); |
+ } |
+ args.add(visitValue(arg.value)); |
+ } |
+ |
+ return new Arguments(arguments, args); |
+ } |
+ |
+ MethodMember _makeLambdaMethod(String name, FunctionDefinition func) { |
+ var meth = new MethodMember(name, method.declaringType, func); |
+ meth.isLambda = true; |
+ meth.enclosingElement = method; |
+ meth._methodData = new MethodData(meth, _frame); |
+ meth.resolve(); |
+ return meth; |
+ } |
+ |
+ void _pushBlock(Node node) { |
+ _frame.pushBlock(node); |
+ } |
+ void _popBlock(Node node) { |
+ _frame.popBlock(node); |
+ } |
+ |
+ Member _resolveBare(String name, Node node) { |
+ var type = _frame.method.declaringType; |
+ var member = type.getMember(name); |
+ if (member == null || member.declaringType != type) { |
+ var libMember = _frame.library.lookup(name, node.span); |
+ if (libMember !== null) return libMember; |
+ } |
+ |
+ if (member !== null && !member.isStatic && _frame.isStatic) { |
+ world.error('can not refer to instance member from static method', |
+ node.span); |
+ } |
+ return member; |
+ } |
+ |
+ // ******************* Statements ******************* |
+ |
+ void visitDietStatement(DietStatement node) { |
+ var parser = new Parser(node.span.file, startOffset: node.span.start); |
+ parser.block().visit(this); |
+ } |
+ |
+ void visitVariableDefinition(VariableDefinition node) { |
+ var isFinal = false; |
+ // TODO(jimhug): Clean this up and share modifier parsing somewhere. |
+ if (node.modifiers != null && node.modifiers[0].kind == TokenKind.FINAL) { |
+ isFinal = true; |
+ } |
+ var type = resolveType(node.type, false, true); |
+ for (int i=0; i < node.names.length; i++) { |
+ final name = node.names[i].name; |
+ var value = visitValue(node.values[i]); |
+ _frame.create(name, type, node.names[i], isFinal, value); |
+ } |
+ } |
+ |
+ void visitFunctionDefinition(FunctionDefinition node) { |
+ var meth = _makeLambdaMethod(node.name.name, node); |
+ // TODO(jimhug): Better FunctionValue that tracks actual function |
+ var funcValue = _frame.create(meth.name, meth.functionType, |
+ method.definition, true, null); |
+ |
+ meth.methodData.analyze(); |
+ } |
+ |
+ |
+ void visitReturnStatement(ReturnStatement node) { |
+ if (node.value == null) { |
+ _frame.returns(Value.fromNull(node.span)); |
+ } else { |
+ _frame.returns(visitValue(node.value)); |
+ } |
+ } |
+ |
+ void visitThrowStatement(ThrowStatement node) { |
+ // Dart allows throwing anything, just like JS |
+ if (node.value != null) { |
+ var value = visitValue(node.value); |
+ } else { |
+ // skip |
+ } |
+ } |
+ |
+ void visitAssertStatement(AssertStatement node) { |
+ // be sure to walk test for static checking even is asserts disabled |
+ var test = visitValue(node.test); // TODO(jimhug): check bool or callable. |
+ } |
+ |
+ void visitBreakStatement(BreakStatement node) { |
+ } |
+ |
+ void visitContinueStatement(ContinueStatement node) { |
+ } |
+ |
+ void visitIfStatement(IfStatement node) { |
+ var test = visitBool(node.test); |
+ node.trueBranch.visit(this); |
+ if (node.falseBranch != null) { |
+ node.falseBranch.visit(this); |
+ } |
+ } |
+ |
+ void visitWhileStatement(WhileStatement node) { |
+ var test = visitBool(node.test); |
+ node.body.visit(this); |
+ } |
+ |
+ void visitDoStatement(DoStatement node) { |
+ node.body.visit(this); |
+ var test = visitBool(node.test); |
+ } |
+ |
+ void visitForStatement(ForStatement node) { |
+ _pushBlock(node); |
+ if (node.init != null) node.init.visit(this); |
+ |
+ if (node.test != null) { |
+ var test = visitBool(node.test); |
+ } |
+ for (var s in node.step) { |
+ var sv = visitVoid(s); |
+ } |
+ |
+ _pushBlock(node.body); |
+ node.body.visit(this); |
+ _popBlock(node.body); |
+ |
+ |
+ _popBlock(node); |
+ } |
+ |
+ void visitForInStatement(ForInStatement node) { |
+ // TODO(jimhug): visitValue and other cleanups here. |
+ var itemType = resolveType(node.item.type, false, true); |
+ var list = node.list.visit(this); |
+ _visitForInBody(node, itemType, list); |
+ } |
+ |
+ |
+ bool _isFinal(typeRef) { |
+ if (typeRef is GenericTypeReference) { |
+ typeRef = typeRef.baseType; |
+ } else if (typeRef is SimpleTypeReference) { |
+ return false; |
+ } |
+ return typeRef != null && typeRef.isFinal; |
+ } |
+ |
+ void _visitForInBody(ForInStatement node, Type itemType, Value list) { |
+ // TODO(jimhug): Check that itemType matches list members... |
+ _pushBlock(node); |
+ |
+ bool isFinal = _isFinal(node.item.type); |
+ var itemName = node.item.name.name; |
+ var item = |
+ _frame.create(itemName, itemType, node.item.name, isFinal, null); |
+ |
+ var iterator = |
+ list.invoke(_frame, 'iterator', node.list, Arguments.EMPTY); |
+ |
+ node.body.visit(this); |
+ _popBlock(node); |
+ } |
+ |
+ _createDI(DeclaredIdentifier di) { |
+ _frame.create(di.name.name, resolveType(di.type, false, true), di.name, |
+ true, null); |
+ } |
+ |
+ void visitTryStatement(TryStatement node) { |
+ _pushBlock(node.body); |
+ node.body.visit(this); |
+ _popBlock(node.body); |
+ if (node.catches.length > 0) { |
+ for (int i = 0; i < node.catches.length; i++) { |
+ var catch_ = node.catches[i]; |
+ _pushBlock(catch_); |
+ _createDI(catch_.exception); |
+ if (catch_.trace !== null) { |
+ _createDI(catch_.trace); |
+ } |
+ catch_.body.visit(this); |
+ _popBlock(catch_); |
+ } |
+ } |
+ |
+ if (node.finallyBlock != null) { |
+ node.finallyBlock.visit(this); |
+ } |
+ } |
+ |
+ void visitSwitchStatement(SwitchStatement node) { |
+ var test = visitValue(node.test); |
+ for (var case_ in node.cases) { |
+ _pushBlock(case_); |
+ |
+ for (int i=0; i < case_.cases.length; i++) { |
+ var expr = case_.cases[i]; |
+ if (expr == null) { |
+ //skip |
+ } else { |
+ var value = visitValue(expr); |
+ } |
+ } |
+ _visitAllStatements(case_.statements); |
+ _popBlock(case_); |
+ } |
+ } |
+ |
+ _visitAllStatements(statementList) { |
+ for (int i = 0; i < statementList.length; i++) { |
+ var stmt = statementList[i]; |
+ stmt.visit(this); |
+ } |
+ } |
+ |
+ void visitBlockStatement(BlockStatement node) { |
+ _pushBlock(node); |
+ _visitAllStatements(node.body); |
+ _popBlock(node); |
+ } |
+ |
+ void visitLabeledStatement(LabeledStatement node) { |
+ node.body.visit(this); |
+ } |
+ |
+ void visitExpressionStatement(ExpressionStatement node) { |
+ var value = visitVoid(node.body); |
+ } |
+ |
+ void visitEmptyStatement(EmptyStatement node) { |
+ } |
+ |
+ |
+ |
+ // ******************* Expressions ******************* |
+ visitLambdaExpression(LambdaExpression node) { |
+ var name = (node.func.name != null) ? node.func.name.name : ''; |
+ |
+ MethodMember meth = _makeLambdaMethod(name, node.func); |
+ // TODO(jimhug): Worry about proper scope for recursive lambda. |
+ meth.methodData.analyze(); |
+ |
+ return _frame._makeValue(world.functionType, node); |
+ } |
+ |
+ Value visitCallExpression(CallExpression node) { |
+ var target; |
+ var position = node.target; |
+ var name = ':call'; |
+ if (node.target is DotExpression) { |
+ DotExpression dot = node.target; |
+ target = dot.self.visit(this); |
+ name = dot.name.name; |
+ position = dot.name; |
+ } else if (node.target is VarExpression) { |
+ VarExpression varExpr = node.target; |
+ name = varExpr.name.name; |
+ // First check in block scopes. |
+ target = _frame.lookup(name); |
+ if (target != null) { |
+ return target.get().invoke(_frame, ':call', node, |
+ _visitArgs(node.arguments)); |
+ } |
+ |
+ var member = _resolveBare(name, varExpr.name); |
+ if (member !== null) { |
+ return member.invoke(_frame, node, _frame.makeThisValue(node), |
+ _visitArgs(node.arguments)); |
+ } else { |
+ world.warning('can not find "$name"', node.span); |
+ return _frame._makeValue(world.varType, node); |
+ } |
+ } else { |
+ target = node.target.visit(this); |
+ } |
+ |
+ return target.invoke(_frame, name, position, _visitArgs(node.arguments)); |
+ } |
+ |
+ Value visitIndexExpression(IndexExpression node) { |
+ var target = visitValue(node.target); |
+ var index = visitValue(node.index); |
+ |
+ return target.invoke(_frame, ':index', node, |
+ new Arguments(null, [index])); |
+ } |
+ |
+ |
+ Value visitBinaryExpression(BinaryExpression node, [bool isVoid = false]) { |
+ final kind = node.op.kind; |
+ |
+ if (kind == TokenKind.AND || kind == TokenKind.OR) { |
+ var xb = visitBool(node.x); |
+ var yb = visitBool(node.y); |
+ return xb.binop(kind, yb, _frame, node); |
+ } |
+ |
+ final assignKind = TokenKind.kindFromAssign(node.op.kind); |
+ if (assignKind == -1) { |
+ final x = visitValue(node.x); |
+ final y = visitValue(node.y); |
+ return x.binop(kind, y, _frame, node); |
+ } else { |
+ return _visitAssign(assignKind, node.x, node.y, node); |
+ } |
+ } |
+ |
+ /** |
+ * Visits an assignment expression. |
+ */ |
+ Value _visitAssign(int kind, Expression xn, Expression yn, Node position) { |
+ if (xn is VarExpression) { |
+ return _visitVarAssign(kind, xn, yn, position); |
+ } else if (xn is IndexExpression) { |
+ return _visitIndexAssign(kind, xn, yn, position); |
+ } else if (xn is DotExpression) { |
+ return _visitDotAssign(kind, xn, yn, position); |
+ } else { |
+ world.error('illegal lhs', xn.span); |
+ } |
+ } |
+ |
+ _visitVarAssign(int kind, VarExpression xn, Expression yn, Node node) { |
+ final value = visitValue(yn); |
+ final name = xn.name.name; |
+ |
+ // First check in block scopes. |
+ var slot = _frame.lookup(name); |
+ if (slot != null) { |
+ slot.set(value); |
+ } else { |
+ var member = _resolveBare(name, xn.name); |
+ if (member !== null) { |
+ member._set(_frame, node, _frame.makeThisValue(node), value); |
+ } else { |
+ world.warning('can not find "$name"', node.span); |
+ } |
+ } |
+ return _frame._makeValue(value.type, node); |
+ } |
+ |
+ _visitIndexAssign(int kind, IndexExpression xn, Expression yn, |
+ Node position) { |
+ var target = visitValue(xn.target); |
+ var index = visitValue(xn.index); |
+ var y = visitValue(yn); |
+ |
+ return target.setIndex(_frame, index, position, y, kind: kind); |
+ } |
+ |
+ _visitDotAssign(int kind, DotExpression xn, Expression yn, Node position) { |
+ // This is not visitValue because types members are assignable. |
+ var target = xn.self.visit(this); |
+ var y = visitValue(yn); |
+ |
+ return target.set_(_frame, xn.name.name, xn.name, y, kind: kind); |
+ } |
+ |
+ visitUnaryExpression(UnaryExpression node) { |
+ var value = visitValue(node.self); |
+ switch (node.op.kind) { |
+ case TokenKind.INCR: |
+ case TokenKind.DECR: |
+ return value.binop(TokenKind.ADD, |
+ _frame._makeValue(world.intType, node), _frame, node); |
+ } |
+ return value.unop(node.op.kind, _frame, node); |
+ } |
+ |
+ visitDeclaredIdentifier(DeclaredIdentifier node) { |
+ world.error('Expected expression', node.span); |
+ } |
+ |
+ visitAwaitExpression(AwaitExpression node) { |
+ world.internalError( |
+ 'Await expressions should have been eliminated before code generation', |
+ node.span); |
+ } |
+ |
+ Value visitPostfixExpression(PostfixExpression node, |
+ [bool isVoid = false]) { |
+ var value = visitValue(node.body); |
+ |
+ return _frame._makeValue(value.type, node); |
+ } |
+ |
+ Value visitNewExpression(NewExpression node) { |
+ var typeRef = node.type; |
+ |
+ var constructorName = ''; |
+ if (node.name != null) { |
+ constructorName = node.name.name; |
+ } |
+ |
+ // Named constructors and library prefixes, oh my! |
+ // At last, we can collapse the ambiguous wave function... |
+ if (constructorName == '' && typeRef is NameTypeReference && |
+ typeRef.names != null) { |
+ |
+ // Pull off the last name from the type, guess it's the constructor name. |
+ var names = new List.from(typeRef.names); |
+ constructorName = names.removeLast().name; |
+ if (names.length == 0) names = null; |
+ |
+ typeRef = new NameTypeReference( |
+ typeRef.isFinal, typeRef.name, names, typeRef.span); |
+ } |
+ |
+ var type = resolveType(typeRef, true, true); |
+ if (type.isTop) { |
+ type = type.library.findTypeByName(constructorName); |
+ constructorName = ''; |
+ } |
+ |
+ if (type is ParameterType) { |
+ world.error('cannot instantiate a type parameter', node.span); |
+ return _frame._makeValue(world.varType, node); |
+ } |
+ |
+ var m = type.getConstructor(constructorName); |
+ if (m == null) { |
+ var name = type.jsname; |
+ if (type.isVar) { |
+ name = typeRef.name.name; |
+ } |
+ world.warning('no matching constructor for $name', node.span); |
+ return _frame._makeValue(type, node); |
+ } |
+ |
+ if (node.isConst) { |
+ if (!m.isConst) { |
+ world.error('can\'t use const on a non-const constructor', node.span); |
+ } |
+ for (var arg in node.arguments) { |
+ // TODO(jimhug): Remove this double walk of arguments. |
+ if (!visitValue(arg.value).isConst) { |
+ world.error('const constructor expects const arguments', arg.span); |
+ } |
+ } |
+ } |
+ |
+ |
+ var args = _visitArgs(node.arguments); |
+ var target = new TypeValue(type, typeRef.span); |
+ return new PureStaticValue(type, node.span, node.isConst); |
+ } |
+ |
+ Value visitListExpression(ListExpression node) { |
+ var argValues = []; |
+ var listType = world.listType; |
+ var type = world.varType; |
+ if (node.itemType != null) { |
+ type = resolveType(node.itemType, true, !node.isConst); |
+ if (node.isConst && (type is ParameterType || type.hasTypeParams)) { |
+ world.error('type parameter cannot be used in const list literals'); |
+ } |
+ listType = listType.getOrMakeConcreteType([type]); |
+ } |
+ for (var item in node.values) { |
+ var arg = visitTypedValue(item, type); |
+ argValues.add(arg); |
+ // TODO(jimhug): Reenable these checks here - and remove from MethodGen |
+ //if (node.isConst && !arg.isConst) { |
+ // world.error('const list can only contain const values', arg.span); |
+ //} |
+ } |
+ |
+ world.listFactoryType.markUsed(); |
+ |
+ return new PureStaticValue(listType, node.span, node.isConst); |
+ } |
+ |
+ |
+ Value visitMapExpression(MapExpression node) { |
+ var values = <Value>[]; |
+ var valueType = world.varType, keyType = world.stringType; |
+ var mapType = world.mapType; // TODO(jimhug): immutable type? |
+ if (node.valueType !== null) { |
+ if (node.keyType !== null) { |
+ keyType = method.resolveType(node.keyType, true, !node.isConst); |
+ // TODO(jimhug): Would be nice to allow arbitrary keys here (this is |
+ // currently not allowed by the spec). |
+ if (!keyType.isString) { |
+ world.error('the key type of a map literal must be "String"', |
+ keyType.span); |
+ } |
+ if (node.isConst && |
+ (keyType is ParameterType || keyType.hasTypeParams)) { |
+ world.error('type parameter cannot be used in const map literals'); |
+ } |
+ } |
+ |
+ valueType = resolveType(node.valueType, true, !node.isConst); |
+ if (node.isConst && |
+ (valueType is ParameterType || valueType.hasTypeParams)) { |
+ world.error('type parameter cannot be used in const map literals'); |
+ } |
+ |
+ mapType = mapType.getOrMakeConcreteType([keyType, valueType]); |
+ } |
+ |
+ for (int i = 0; i < node.items.length; i += 2) { |
+ var key = visitTypedValue(node.items[i], keyType); |
+ // TODO(jimhug): Reenable these checks here - and remove from MethodGen |
+ //if (node.isConst && !key.isConst) { |
+ // world.error('const map can only contain const keys', key.span); |
+ //} |
+ values.add(key); |
+ |
+ var value = visitTypedValue(node.items[i + 1], valueType); |
+ if (node.isConst && !value.isConst) { |
+ world.error('const map can only contain const values', value.span); |
+ } |
+ values.add(value); |
+ } |
+ |
+ return new PureStaticValue(mapType, node.span, node.isConst); |
+ } |
+ |
+ |
+ Value visitConditionalExpression(ConditionalExpression node) { |
+ var test = visitBool(node.test); |
+ var trueBranch = visitValue(node.trueBranch); |
+ var falseBranch = visitValue(node.falseBranch); |
+ |
+ // TODO(jimhug): Should be unioning values, not just types. |
+ return _frame._makeValue(Type.union(trueBranch.type, falseBranch.type), |
+ node); |
+ } |
+ |
+ Value visitIsExpression(IsExpression node) { |
+ var value = visitValue(node.x); |
+ var type = resolveType(node.type, false, true); |
+ return _frame._makeValue(world.boolType, node); |
+ } |
+ |
+ Value visitParenExpression(ParenExpression node) { |
+ return visitValue(node.body); |
+ } |
+ |
+ Value visitDotExpression(DotExpression node) { |
+ // Types are legal targets of . |
+ var target = node.self.visit(this); |
+ return target.get_(_frame, node.name.name, node); |
+ } |
+ |
+ |
+ Value visitVarExpression(VarExpression node) { |
+ final name = node.name.name; |
+ |
+ // First check in block scopes. |
+ var slot = _frame.lookup(name); |
+ if (slot != null) { |
+ return slot.get(); |
+ } |
+ |
+ var member = _resolveBare(name, node.name); |
+ if (member !== null) { |
+ if (member is TypeMember) { |
+ return new PureStaticValue(member.dynamic.type, node.span, true, |
+ true); |
+ } else { |
+ return member._get(_frame, node, _frame.makeThisValue(node)); |
+ } |
+ } else { |
+ world.warning('can not find "$name"', node.span); |
+ return _frame._makeValue(world.varType, node); |
+ } |
+ } |
+ |
+ Value visitThisExpression(ThisExpression node) { |
+ return _frame.makeThisValue(node); |
+ } |
+ |
+ Value visitSuperExpression(SuperExpression node) { |
+ return _frame.makeSuperValue(node); |
+ } |
+ |
+ Value visitLiteralExpression(LiteralExpression node) { |
+ return new PureStaticValue(node.value.type, node.span, true); |
+ } |
+ |
+ Value visitStringInterpExpression(StringInterpExpression node) { |
+ return _frame._makeValue(world.stringType, node); |
+ |
+ // TODO(jimhug): Do we need this more elaborate analysis? |
+ /* |
+ var ret = Value.fromString('', node.span); |
+ |
+ for (var item in node.pieces) { |
+ var val = visitValue(item); |
+ var sval = val.invoke(_frame, 'toString', item, Arguments.EMPTY); |
+ ret = ret.binop(TokenKind.ADD, sval, _frame, item); |
+ } |
+ return _frame._makeValue(world.stringType, node); //???ret; |
+ */ |
+ } |
+} |
+ |