/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.engine.internal.constant; import com.google.dart.engine.ast.AdjacentStrings; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.BinaryExpression; import com.google.dart.engine.ast.BooleanLiteral; import com.google.dart.engine.ast.ConditionalExpression; import com.google.dart.engine.ast.DoubleLiteral; import com.google.dart.engine.ast.Expression; import com.google.dart.engine.ast.InstanceCreationExpression; import com.google.dart.engine.ast.IntegerLiteral; import com.google.dart.engine.ast.InterpolationElement; import com.google.dart.engine.ast.InterpolationExpression; import com.google.dart.engine.ast.InterpolationString; import com.google.dart.engine.ast.ListLiteral; import com.google.dart.engine.ast.MapLiteral; import com.google.dart.engine.ast.MapLiteralEntry; import com.google.dart.engine.ast.MethodInvocation; import com.google.dart.engine.ast.NamedExpression; import com.google.dart.engine.ast.NodeList; import com.google.dart.engine.ast.NullLiteral; import com.google.dart.engine.ast.ParenthesizedExpression; import com.google.dart.engine.ast.PrefixExpression; import com.google.dart.engine.ast.PrefixedIdentifier; import com.google.dart.engine.ast.PropertyAccess; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.SimpleStringLiteral; import com.google.dart.engine.ast.StringInterpolation; import com.google.dart.engine.ast.StringLiteral; import com.google.dart.engine.ast.SymbolLiteral; import com.google.dart.engine.ast.visitor.UnifyingAstVisitor; import com.google.dart.engine.element.ClassElement; import com.google.dart.engine.element.CompilationUnitElement; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.ExecutableElement; import com.google.dart.engine.element.FunctionElement; import com.google.dart.engine.element.FunctionTypeAliasElement; import com.google.dart.engine.element.LibraryElement; import com.google.dart.engine.element.PrefixElement; import com.google.dart.engine.element.PropertyAccessorElement; import com.google.dart.engine.error.CompileTimeErrorCode; import com.google.dart.engine.error.ErrorCode; import com.google.dart.engine.internal.element.VariableElementImpl; import com.google.dart.engine.internal.error.ErrorReporter; import com.google.dart.engine.internal.object.BoolState; import com.google.dart.engine.internal.object.DartObjectComputer; import com.google.dart.engine.internal.object.DartObjectImpl; import com.google.dart.engine.internal.object.DoubleState; import com.google.dart.engine.internal.object.FunctionState; import com.google.dart.engine.internal.object.GenericState; import com.google.dart.engine.internal.object.IntState; import com.google.dart.engine.internal.object.ListState; import com.google.dart.engine.internal.object.MapState; import com.google.dart.engine.internal.object.NullState; import com.google.dart.engine.internal.object.StringState; import com.google.dart.engine.internal.object.SymbolState; import com.google.dart.engine.internal.object.TypeState; import com.google.dart.engine.internal.resolver.TypeProvider; import com.google.dart.engine.scanner.Token; import com.google.dart.engine.scanner.TokenType; import com.google.dart.engine.type.InterfaceType; import java.util.ArrayList; import java.util.HashMap; /** * Instances of the class {@code ConstantVisitor} evaluate constant expressions to produce their * compile-time value. According to the Dart Language Specification: <blockquote> A constant * expression is one of the following: * <ul> * <li>A literal number.</li> * <li>A literal boolean.</li> * <li>A literal string where any interpolated expression is a compile-time constant that evaluates * to a numeric, string or boolean value or to <b>null</b>.</li> * <li>A literal symbol.</li> * <li><b>null</b>.</li> * <li>A qualified reference to a static constant variable.</li> * <li>An identifier expression that denotes a constant variable, class or type alias.</li> * <li>A constant constructor invocation.</li> * <li>A constant list literal.</li> * <li>A constant map literal.</li> * <li>A simple or qualified identifier denoting a top-level function or a static method.</li> * <li>A parenthesized expression <i>(e)</i> where <i>e</i> is a constant expression.</li> * <li>An expression of the form <i>identical(e<sub>1</sub>, e<sub>2</sub>)</i> where * <i>e<sub>1</sub></i> and <i>e<sub>2</sub></i> are constant expressions and <i>identical()</i> is * statically bound to the predefined dart function <i>identical()</i> discussed above.</li> * <li>An expression of one of the forms <i>e<sub>1</sub> == e<sub>2</sub></i> or <i>e<sub>1</sub> * != e<sub>2</sub></i> where <i>e<sub>1</sub></i> and <i>e<sub>2</sub></i> are constant expressions * that evaluate to a numeric, string or boolean value.</li> * <li>An expression of one of the forms <i>!e</i>, <i>e<sub>1</sub> && e<sub>2</sub></i> or * <i>e<sub>1</sub> || e<sub>2</sub></i>, where <i>e</i>, <i>e1</sub></i> and <i>e2</sub></i> are * constant expressions that evaluate to a boolean value.</li> * <li>An expression of one of the forms <i>~e</i>, <i>e<sub>1</sub> ^ e<sub>2</sub></i>, * <i>e<sub>1</sub> & e<sub>2</sub></i>, <i>e<sub>1</sub> | e<sub>2</sub></i>, <i>e<sub>1</sub> * >> e<sub>2</sub></i> or <i>e<sub>1</sub> << e<sub>2</sub></i>, where <i>e</i>, * <i>e<sub>1</sub></i> and <i>e<sub>2</sub></i> are constant expressions that evaluate to an * integer value or to <b>null</b>.</li> * <li>An expression of one of the forms <i>-e</i>, <i>e<sub>1</sub> + e<sub>2</sub></i>, * <i>e<sub>1</sub> - e<sub>2</sub></i>, <i>e<sub>1</sub> * e<sub>2</sub></i>, <i>e<sub>1</sub> / * e<sub>2</sub></i>, <i>e<sub>1</sub> ~/ e<sub>2</sub></i>, <i>e<sub>1</sub> > * e<sub>2</sub></i>, <i>e<sub>1</sub> < e<sub>2</sub></i>, <i>e<sub>1</sub> >= * e<sub>2</sub></i>, <i>e<sub>1</sub> <= e<sub>2</sub></i> or <i>e<sub>1</sub> % * e<sub>2</sub></i>, where <i>e</i>, <i>e<sub>1</sub></i> and <i>e<sub>2</sub></i> are constant * expressions that evaluate to a numeric value or to <b>null</b>.</li> * <li>An expression of the form <i>e<sub>1</sub> ? e<sub>2</sub> : e<sub>3</sub></i> where * <i>e<sub>1</sub></i>, <i>e<sub>2</sub></i> and <i>e<sub>3</sub></i> are constant expressions, and * <i>e<sub>1</sub></i> evaluates to a boolean value.</li> * </ul> * </blockquote> */ public class ConstantVisitor extends UnifyingAstVisitor<DartObjectImpl> { /** * The type provider used to access the known types. */ private TypeProvider typeProvider; /** * An shared object representing the value 'null'. */ private DartObjectImpl nullObject; private final HashMap<String, DartObjectImpl> lexicalEnvironment; /** * Error reporter that we use to report errors accumulated while computing the constant. */ private final ErrorReporter errorReporter; /** * Helper class used to compute constant values. */ private final DartObjectComputer dartObjectComputer; /** * Initialize a newly created constant visitor. * * @param typeProvider the type provider used to access known types * @param lexicalEnvironment values which should override simpleIdentifiers, or null if no * overriding is necessary. */ public ConstantVisitor(TypeProvider typeProvider, ErrorReporter errorReporter) { this.typeProvider = typeProvider; this.lexicalEnvironment = null; this.errorReporter = errorReporter; this.dartObjectComputer = new DartObjectComputer(errorReporter, typeProvider); } /** * Initialize a newly created constant visitor. * * @param typeProvider the type provider used to access known types * @param lexicalEnvironment values which should override simpleIdentifiers, or null if no * overriding is necessary. */ public ConstantVisitor(TypeProvider typeProvider, HashMap<String, DartObjectImpl> lexicalEnvironment, ErrorReporter errorReporter) { this.typeProvider = typeProvider; this.lexicalEnvironment = lexicalEnvironment; this.errorReporter = errorReporter; this.dartObjectComputer = new DartObjectComputer(errorReporter, typeProvider); } @Override public DartObjectImpl visitAdjacentStrings(AdjacentStrings node) { DartObjectImpl result = null; for (StringLiteral string : node.getStrings()) { if (result == null) { result = string.accept(this); } else { result = dartObjectComputer.concatenate(node, result, string.accept(this)); } } return result; } @Override public DartObjectImpl visitBinaryExpression(BinaryExpression node) { DartObjectImpl leftResult = node.getLeftOperand().accept(this); DartObjectImpl rightResult = node.getRightOperand().accept(this); TokenType operatorType = node.getOperator().getType(); // 'null' is almost never good operand if (operatorType != TokenType.BANG_EQ && operatorType != TokenType.EQ_EQ) { if (leftResult != null && leftResult.isNull() || rightResult != null && rightResult.isNull()) { error(node, CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION); return null; } } // evaluate operator switch (operatorType) { case AMPERSAND: return dartObjectComputer.bitAnd(node, leftResult, rightResult); case AMPERSAND_AMPERSAND: return dartObjectComputer.logicalAnd(node, leftResult, rightResult); case BANG_EQ: return dartObjectComputer.notEqual(node, leftResult, rightResult); case BAR: return dartObjectComputer.bitOr(node, leftResult, rightResult); case BAR_BAR: return dartObjectComputer.logicalOr(node, leftResult, rightResult); case CARET: return dartObjectComputer.bitXor(node, leftResult, rightResult); case EQ_EQ: return dartObjectComputer.equalEqual(node, leftResult, rightResult); case GT: return dartObjectComputer.greaterThan(node, leftResult, rightResult); case GT_EQ: return dartObjectComputer.greaterThanOrEqual(node, leftResult, rightResult); case GT_GT: return dartObjectComputer.shiftRight(node, leftResult, rightResult); case LT: return dartObjectComputer.lessThan(node, leftResult, rightResult); case LT_EQ: return dartObjectComputer.lessThanOrEqual(node, leftResult, rightResult); case LT_LT: return dartObjectComputer.shiftLeft(node, leftResult, rightResult); case MINUS: return dartObjectComputer.minus(node, leftResult, rightResult); case PERCENT: return dartObjectComputer.remainder(node, leftResult, rightResult); case PLUS: return dartObjectComputer.add(node, leftResult, rightResult); case STAR: return dartObjectComputer.times(node, leftResult, rightResult); case SLASH: return dartObjectComputer.divide(node, leftResult, rightResult); case TILDE_SLASH: return dartObjectComputer.integerDivide(node, leftResult, rightResult); default: // TODO(brianwilkerson) Figure out which error to report. error(node, null); return null; } } @Override public DartObjectImpl visitBooleanLiteral(BooleanLiteral node) { return new DartObjectImpl(typeProvider.getBoolType(), BoolState.from(node.getValue())); } @Override public DartObjectImpl visitConditionalExpression(ConditionalExpression node) { Expression condition = node.getCondition(); DartObjectImpl conditionResult = condition.accept(this); DartObjectImpl thenResult = node.getThenExpression().accept(this); DartObjectImpl elseResult = node.getElseExpression().accept(this); if (conditionResult == null) { return conditionResult; } else if (!conditionResult.isBool()) { errorReporter.reportErrorForNode(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL, condition); return null; } else if (thenResult == null) { return thenResult; } else if (elseResult == null) { return elseResult; } conditionResult = dartObjectComputer.applyBooleanConversion(condition, conditionResult); if (conditionResult == null) { return conditionResult; } if (conditionResult.isTrue()) { return thenResult; } else if (conditionResult.isFalse()) { return elseResult; } InterfaceType thenType = thenResult.getType(); InterfaceType elseType = elseResult.getType(); return validWithUnknownValue((InterfaceType) thenType.getLeastUpperBound(elseType)); } @Override public DartObjectImpl visitDoubleLiteral(DoubleLiteral node) { return new DartObjectImpl(typeProvider.getDoubleType(), new DoubleState(node.getValue())); } @Override public DartObjectImpl visitInstanceCreationExpression(InstanceCreationExpression node) { if (!node.isConst()) { // TODO(brianwilkerson) Figure out which error to report. error(node, null); return null; } beforeGetEvaluationResult(node); EvaluationResultImpl result = node.getEvaluationResult(); if (result != null) { return result.getValue(); } // TODO(brianwilkerson) Figure out which error to report. error(node, null); return null; } @Override public DartObjectImpl visitIntegerLiteral(IntegerLiteral node) { return new DartObjectImpl(typeProvider.getIntType(), new IntState(node.getValue())); } @Override public DartObjectImpl visitInterpolationExpression(InterpolationExpression node) { DartObjectImpl result = node.getExpression().accept(this); if (result != null && !result.isBoolNumStringOrNull()) { error(node, CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING); return null; } return dartObjectComputer.performToString(node, result); } @Override public DartObjectImpl visitInterpolationString(InterpolationString node) { return new DartObjectImpl(typeProvider.getStringType(), new StringState(node.getValue())); } @Override public DartObjectImpl visitListLiteral(ListLiteral node) { if (node.getConstKeyword() == null) { errorReporter.reportErrorForNode(CompileTimeErrorCode.MISSING_CONST_IN_LIST_LITERAL, node); return null; } boolean errorOccurred = false; ArrayList<DartObjectImpl> elements = new ArrayList<DartObjectImpl>(); for (Expression element : node.getElements()) { DartObjectImpl elementResult = element.accept(this); if (elementResult == null) { errorOccurred = true; } else { elements.add(elementResult); } } if (errorOccurred) { return null; } return new DartObjectImpl(typeProvider.getListType(), new ListState( elements.toArray(new DartObjectImpl[elements.size()]))); } @Override public DartObjectImpl visitMapLiteral(MapLiteral node) { if (node.getConstKeyword() == null) { errorReporter.reportErrorForNode(CompileTimeErrorCode.MISSING_CONST_IN_MAP_LITERAL, node); return null; } boolean errorOccurred = false; HashMap<DartObjectImpl, DartObjectImpl> map = new HashMap<DartObjectImpl, DartObjectImpl>(); for (MapLiteralEntry entry : node.getEntries()) { DartObjectImpl keyResult = entry.getKey().accept(this); DartObjectImpl valueResult = entry.getValue().accept(this); if (keyResult == null || valueResult == null) { errorOccurred = true; } else { map.put(keyResult, valueResult); } } if (errorOccurred) { return null; } return new DartObjectImpl(typeProvider.getMapType(), new MapState(map)); } @Override public DartObjectImpl visitMethodInvocation(MethodInvocation node) { Element element = node.getMethodName().getStaticElement(); if (element instanceof FunctionElement) { FunctionElement function = (FunctionElement) element; if (function.getName().equals("identical")) { NodeList<Expression> arguments = node.getArgumentList().getArguments(); if (arguments.size() == 2) { Element enclosingElement = function.getEnclosingElement(); if (enclosingElement instanceof CompilationUnitElement) { LibraryElement library = ((CompilationUnitElement) enclosingElement).getLibrary(); if (library.isDartCore()) { DartObjectImpl leftArgument = arguments.get(0).accept(this); DartObjectImpl rightArgument = arguments.get(1).accept(this); return dartObjectComputer.equalEqual(node, leftArgument, rightArgument); } } } } } // TODO(brianwilkerson) Figure out which error to report. error(node, null); return null; } @Override public DartObjectImpl visitNamedExpression(NamedExpression node) { return node.getExpression().accept(this); } @Override public DartObjectImpl visitNode(AstNode node) { // TODO(brianwilkerson) Figure out which error to report. error(node, null); return null; } @Override public DartObjectImpl visitNullLiteral(NullLiteral node) { return getNull(); } @Override public DartObjectImpl visitParenthesizedExpression(ParenthesizedExpression node) { return node.getExpression().accept(this); } @Override public DartObjectImpl visitPrefixedIdentifier(PrefixedIdentifier node) { // TODO(brianwilkerson) Uncomment the lines below when the new constant support can be added. // Element element = node.getStaticElement(); // if (isStringLength(element)) { // EvaluationResultImpl target = node.getPrefix().accept(this); // return target.stringLength(typeProvider, node); // } SimpleIdentifier prefixNode = node.getPrefix(); Element prefixElement = prefixNode.getStaticElement(); if (!(prefixElement instanceof PrefixElement)) { DartObjectImpl prefixResult = prefixNode.accept(this); if (prefixResult == null) { // The error has already been reported. return null; } } // validate prefixed identifier return getConstantValue(node, node.getStaticElement()); } @Override public DartObjectImpl visitPrefixExpression(PrefixExpression node) { DartObjectImpl operand = node.getOperand().accept(this); if (operand != null && operand.isNull()) { error(node, CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION); return null; } switch (node.getOperator().getType()) { case BANG: return dartObjectComputer.logicalNot(node, operand); case TILDE: return dartObjectComputer.bitNot(node, operand); case MINUS: return dartObjectComputer.negated(node, operand); default: // TODO(brianwilkerson) Figure out which error to report. error(node, null); return null; } } @Override public DartObjectImpl visitPropertyAccess(PropertyAccess node) { Element element = node.getPropertyName().getStaticElement(); // TODO(brianwilkerson) Uncomment the lines below when the new constant support can be added. // if (isStringLength(element)) { // EvaluationResultImpl target = node.getRealTarget().accept(this); // return target.stringLength(typeProvider, node); // } return getConstantValue(node, element); } @Override public DartObjectImpl visitSimpleIdentifier(SimpleIdentifier node) { if (lexicalEnvironment != null && lexicalEnvironment.containsKey(node.getName())) { return lexicalEnvironment.get(node.getName()); } return getConstantValue(node, node.getStaticElement()); } @Override public DartObjectImpl visitSimpleStringLiteral(SimpleStringLiteral node) { return new DartObjectImpl(typeProvider.getStringType(), new StringState(node.getValue())); } @Override public DartObjectImpl visitStringInterpolation(StringInterpolation node) { DartObjectImpl result = null; boolean first = true; for (InterpolationElement element : node.getElements()) { if (first) { result = element.accept(this); first = false; } else { result = dartObjectComputer.concatenate(node, result, element.accept(this)); } } return result; } @Override public DartObjectImpl visitSymbolLiteral(SymbolLiteral node) { StringBuilder builder = new StringBuilder(); Token[] components = node.getComponents(); for (int i = 0; i < components.length; i++) { if (i > 0) { builder.append('.'); } builder.append(components[i].getLexeme()); } return new DartObjectImpl(typeProvider.getSymbolType(), new SymbolState(builder.toString())); } /** * This method is called just before retrieving an evaluation result from an AST node. Unit tests * will override it to introduce additional error checking. */ protected void beforeGetEvaluationResult(AstNode node) { } /** * Return an object representing the value 'null'. * * @return an object representing the value 'null' */ DartObjectImpl getNull() { if (nullObject == null) { nullObject = new DartObjectImpl(typeProvider.getNullType(), NullState.NULL_STATE); } return nullObject; } DartObjectImpl validWithUnknownValue(InterfaceType type) { if (type.getElement().getLibrary().isDartCore()) { String typeName = type.getName(); if (typeName.equals("bool")) { return new DartObjectImpl(type, BoolState.UNKNOWN_VALUE); } else if (typeName.equals("double")) { return new DartObjectImpl(type, DoubleState.UNKNOWN_VALUE); } else if (typeName.equals("int")) { return new DartObjectImpl(type, IntState.UNKNOWN_VALUE); } else if (typeName.equals("String")) { return new DartObjectImpl(type, StringState.UNKNOWN_VALUE); } } return new DartObjectImpl(type, GenericState.UNKNOWN_VALUE); } /** * Return the value of the given expression, or a representation of 'null' if the expression * cannot be evaluated. * * @param expression the expression whose value is to be returned * @return the value of the given expression */ DartObjectImpl valueOf(Expression expression) { DartObjectImpl expressionValue = expression.accept(this); if (expressionValue != null) { return expressionValue; } return getNull(); } /** * Create an error associated with the given node. * * @param node the AST node associated with the error * @param code the error code indicating the nature of the error */ private void error(AstNode node, ErrorCode code) { errorReporter.reportErrorForNode( code == null ? CompileTimeErrorCode.INVALID_CONSTANT : code, node); } /** * Return the constant value of the static constant represented by the given element. * * @param node the node to be used if an error needs to be reported * @param element the element whose value is to be returned * @return the constant value of the static constant */ private DartObjectImpl getConstantValue(AstNode node, Element element) { if (element instanceof PropertyAccessorElement) { element = ((PropertyAccessorElement) element).getVariable(); } if (element instanceof VariableElementImpl) { VariableElementImpl variableElementImpl = (VariableElementImpl) element; beforeGetEvaluationResult(node); EvaluationResultImpl value = variableElementImpl.getEvaluationResult(); if (variableElementImpl.isConst() && value != null) { return value.getValue(); } } else if (element instanceof ExecutableElement) { ExecutableElement function = (ExecutableElement) element; if (function.isStatic()) { return new DartObjectImpl(typeProvider.getFunctionType(), new FunctionState(function)); } } else if (element instanceof ClassElement || element instanceof FunctionTypeAliasElement) { return new DartObjectImpl(typeProvider.getTypeType(), new TypeState(element)); } // TODO(brianwilkerson) Figure out which error to report. error(node, null); return null; } /** * Return {@code true} if the given element represents the 'length' getter in class 'String'. * * @param element the element being tested. * @return */ private boolean isStringLength(Element element) { if (!(element instanceof PropertyAccessorElement)) { return false; } PropertyAccessorElement accessor = (PropertyAccessorElement) element; if (!accessor.isGetter() || !accessor.getName().equals("length")) { return false; } Element parent = accessor.getEnclosingElement(); return parent.equals(typeProvider.getStringType().getElement()); } }