| // Copyright (c) 2019, 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 'dart:collection'; |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| |
| /** |
| * Instances of the class [ConstantEvaluator] evaluate constant expressions to |
| * produce their compile-time value. |
| * |
| * According to the Dart Language Specification: |
| * |
| * > A constant expression is one of the following: |
| * > |
| * > * A literal number. |
| * > * A literal boolean. |
| * > * A literal string where any interpolated expression is a compile-time |
| * > constant that evaluates to a numeric, string or boolean value or to |
| * > **null**. |
| * > * A literal symbol. |
| * > * **null**. |
| * > * A qualified reference to a static constant variable. |
| * > * An identifier expression that denotes a constant variable, class or type |
| * > alias. |
| * > * A constant constructor invocation. |
| * > * A constant list literal. |
| * > * A constant map literal. |
| * > * A simple or qualified identifier denoting a top-level function or a |
| * > static method. |
| * > * A parenthesized expression _(e)_ where _e_ is a constant expression. |
| * > * <span> |
| * > 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. |
| * > </span> |
| * > * <span> |
| * > 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. |
| * > </span> |
| * > * <span> |
| * > 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>e<sub>1</sub></i> and <i>e<sub>2</sub></i> are constant |
| * > expressions that evaluate to a boolean value. |
| * > </span> |
| * > * <span> |
| * > 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>. |
| * > </span> |
| * > * <span> |
| * > 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> 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>. |
| * > </span> |
| * > * <span> |
| * > An expression of one the form <i>e<sub>1</sub> + e<sub>2</sub></i>, |
| * > <i>e<sub>1</sub> -e<sub>2</sub></i> where <i>e<sub>1</sub> and |
| * > e<sub>2</sub></i> are constant expressions that evaluate to a numeric or |
| * > string value or to <b>null</b>. |
| * > </span> |
| * > * <span> |
| * > 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. |
| * > </span> |
| * |
| * However, this comment is now at least a little bit out of sync with the spec. |
| * |
| * The values returned by instances of this class are therefore `null` and |
| * instances of the classes `Boolean`, `BigInteger`, `Double`, `String`, and |
| * `DartObject`. |
| * |
| * In addition, this class defines several values that can be returned to |
| * indicate various conditions encountered during evaluation. These are |
| * documented with the static fields that define those values. |
| */ |
| class ConstantEvaluator extends GeneralizingAstVisitor<Object> { |
| /** |
| * The value returned for expressions (or non-expression nodes) that are not |
| * compile-time constant expressions. |
| */ |
| static Object NOT_A_CONSTANT = new Object(); |
| |
| @override |
| Object visitAdjacentStrings(AdjacentStrings node) { |
| StringBuffer buffer = new StringBuffer(); |
| for (StringLiteral string in node.strings) { |
| Object value = string.accept(this); |
| if (identical(value, NOT_A_CONSTANT)) { |
| return value; |
| } |
| buffer.write(value); |
| } |
| return buffer.toString(); |
| } |
| |
| @override |
| Object visitBinaryExpression(BinaryExpression node) { |
| Object leftOperand = node.leftOperand.accept(this); |
| if (identical(leftOperand, NOT_A_CONSTANT)) { |
| return leftOperand; |
| } |
| Object rightOperand = node.rightOperand.accept(this); |
| if (identical(rightOperand, NOT_A_CONSTANT)) { |
| return rightOperand; |
| } |
| while (true) { |
| if (node.operator.type == TokenType.AMPERSAND) { |
| // integer or {@code null} |
| if (leftOperand is int && rightOperand is int) { |
| return leftOperand & rightOperand; |
| } |
| } else if (node.operator.type == TokenType.AMPERSAND_AMPERSAND) { |
| // boolean or {@code null} |
| if (leftOperand is bool && rightOperand is bool) { |
| return leftOperand && rightOperand; |
| } |
| } else if (node.operator.type == TokenType.BANG_EQ) { |
| // numeric, string, boolean, or {@code null} |
| if (leftOperand is bool && rightOperand is bool) { |
| return leftOperand != rightOperand; |
| } else if (leftOperand is num && rightOperand is num) { |
| return leftOperand != rightOperand; |
| } else if (leftOperand is String && rightOperand is String) { |
| return leftOperand != rightOperand; |
| } |
| } else if (node.operator.type == TokenType.BAR) { |
| // integer or {@code null} |
| if (leftOperand is int && rightOperand is int) { |
| return leftOperand | rightOperand; |
| } |
| } else if (node.operator.type == TokenType.BAR_BAR) { |
| // boolean or {@code null} |
| if (leftOperand is bool && rightOperand is bool) { |
| return leftOperand || rightOperand; |
| } |
| } else if (node.operator.type == TokenType.CARET) { |
| // integer or {@code null} |
| if (leftOperand is int && rightOperand is int) { |
| return leftOperand ^ rightOperand; |
| } |
| } else if (node.operator.type == TokenType.EQ_EQ) { |
| // numeric, string, boolean, or {@code null} |
| if (leftOperand is bool && rightOperand is bool) { |
| return leftOperand == rightOperand; |
| } else if (leftOperand is num && rightOperand is num) { |
| return leftOperand == rightOperand; |
| } else if (leftOperand is String && rightOperand is String) { |
| return leftOperand == rightOperand; |
| } |
| } else if (node.operator.type == TokenType.GT) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand.compareTo(rightOperand) > 0; |
| } |
| } else if (node.operator.type == TokenType.GT_EQ) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand.compareTo(rightOperand) >= 0; |
| } |
| } else if (node.operator.type == TokenType.GT_GT) { |
| // integer or {@code null} |
| if (leftOperand is int && rightOperand is int) { |
| return leftOperand >> rightOperand; |
| } |
| } else if (node.operator.type == TokenType.LT) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand.compareTo(rightOperand) < 0; |
| } |
| } else if (node.operator.type == TokenType.LT_EQ) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand.compareTo(rightOperand) <= 0; |
| } |
| } else if (node.operator.type == TokenType.LT_LT) { |
| // integer or {@code null} |
| if (leftOperand is int && rightOperand is int) { |
| return leftOperand << rightOperand; |
| } |
| } else if (node.operator.type == TokenType.MINUS) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand - rightOperand; |
| } |
| } else if (node.operator.type == TokenType.PERCENT) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand.remainder(rightOperand); |
| } |
| } else if (node.operator.type == TokenType.PLUS) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand + rightOperand; |
| } |
| if (leftOperand is String && rightOperand is String) { |
| return leftOperand + rightOperand; |
| } |
| } else if (node.operator.type == TokenType.STAR) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand * rightOperand; |
| } |
| } else if (node.operator.type == TokenType.SLASH) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand / rightOperand; |
| } |
| } else if (node.operator.type == TokenType.TILDE_SLASH) { |
| // numeric or {@code null} |
| if (leftOperand is num && rightOperand is num) { |
| return leftOperand ~/ rightOperand; |
| } |
| } |
| break; |
| } |
| // TODO(brianwilkerson) This doesn't handle numeric conversions. |
| return visitExpression(node); |
| } |
| |
| @override |
| Object visitBooleanLiteral(BooleanLiteral node) => node.value ? true : false; |
| |
| @override |
| Object visitDoubleLiteral(DoubleLiteral node) => node.value; |
| |
| @override |
| Object visitIntegerLiteral(IntegerLiteral node) => node.value; |
| |
| @override |
| Object visitInterpolationExpression(InterpolationExpression node) { |
| Object value = node.expression.accept(this); |
| if (value == null || value is bool || value is String || value is num) { |
| return value; |
| } |
| return NOT_A_CONSTANT; |
| } |
| |
| @override |
| Object visitInterpolationString(InterpolationString node) => node.value; |
| |
| @override |
| Object visitListLiteral(ListLiteral node) { |
| List<Object> list = new List<Object>(); |
| for (CollectionElement element in node.elements) { |
| if (element is Expression) { |
| Object value = element.accept(this); |
| if (identical(value, NOT_A_CONSTANT)) { |
| return value; |
| } |
| list.add(value); |
| } else { |
| // There are a lot of constants that this class does not support, so we |
| // didn't add support for the extended collection support. |
| return NOT_A_CONSTANT; |
| } |
| } |
| return list; |
| } |
| |
| @override |
| Object visitMethodInvocation(MethodInvocation node) => visitNode(node); |
| |
| @override |
| Object visitNode(AstNode node) => NOT_A_CONSTANT; |
| |
| @override |
| Object visitNullLiteral(NullLiteral node) => null; |
| |
| @override |
| Object visitParenthesizedExpression(ParenthesizedExpression node) => |
| node.expression.accept(this); |
| |
| @override |
| Object visitPrefixedIdentifier(PrefixedIdentifier node) => |
| _getConstantValue(null); |
| |
| @override |
| Object visitPrefixExpression(PrefixExpression node) { |
| Object operand = node.operand.accept(this); |
| if (identical(operand, NOT_A_CONSTANT)) { |
| return operand; |
| } |
| while (true) { |
| if (node.operator.type == TokenType.BANG) { |
| if (identical(operand, true)) { |
| return false; |
| } else if (identical(operand, false)) { |
| return true; |
| } |
| } else if (node.operator.type == TokenType.TILDE) { |
| if (operand is int) { |
| return ~operand; |
| } |
| } else if (node.operator.type == TokenType.MINUS) { |
| if (operand == null) { |
| return null; |
| } else if (operand is num) { |
| return -operand; |
| } |
| } else {} |
| break; |
| } |
| return NOT_A_CONSTANT; |
| } |
| |
| @override |
| Object visitPropertyAccess(PropertyAccess node) => _getConstantValue(null); |
| |
| @override |
| Object visitSetOrMapLiteral(SetOrMapLiteral node) { |
| // There are a lot of constants that this class does not support, so we |
| // didn't add support for set literals. As a result, this assumes that we're |
| // looking at a map literal until we prove otherwise. |
| Map<String, Object> map = new HashMap<String, Object>(); |
| for (CollectionElement element in node.elements) { |
| if (element is MapLiteralEntry) { |
| Object key = element.key.accept(this); |
| Object value = element.value.accept(this); |
| if (key is String && !identical(value, NOT_A_CONSTANT)) { |
| map[key] = value; |
| } else { |
| return NOT_A_CONSTANT; |
| } |
| } else { |
| // There are a lot of constants that this class does not support, so |
| // we didn't add support for the extended collection support. |
| return NOT_A_CONSTANT; |
| } |
| } |
| return map; |
| } |
| |
| @override |
| Object visitSimpleIdentifier(SimpleIdentifier node) => |
| _getConstantValue(null); |
| |
| @override |
| Object visitSimpleStringLiteral(SimpleStringLiteral node) => node.value; |
| |
| @override |
| Object visitStringInterpolation(StringInterpolation node) { |
| StringBuffer buffer = new StringBuffer(); |
| for (InterpolationElement element in node.elements) { |
| Object value = element.accept(this); |
| if (identical(value, NOT_A_CONSTANT)) { |
| return value; |
| } |
| buffer.write(value); |
| } |
| return buffer.toString(); |
| } |
| |
| @override |
| Object visitSymbolLiteral(SymbolLiteral node) { |
| // TODO(brianwilkerson) This isn't optimal because a Symbol is not a String. |
| StringBuffer buffer = new StringBuffer(); |
| for (Token component in node.components) { |
| if (buffer.length > 0) { |
| buffer.writeCharCode(0x2E); |
| } |
| buffer.write(component.lexeme); |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * Return the constant value of the static constant represented by the given |
| * [element]. |
| */ |
| Object _getConstantValue(Element element) { |
| // TODO(brianwilkerson) Implement this |
| // if (element is FieldElement) { |
| // FieldElement field = element; |
| // if (field.isStatic && field.isConst) { |
| // //field.getConstantValue(); |
| // } |
| // // } else if (element instanceof VariableElement) { |
| // // VariableElement variable = (VariableElement) element; |
| // // if (variable.isStatic() && variable.isConst()) { |
| // // //variable.getConstantValue(); |
| // // } |
| // } |
| return NOT_A_CONSTANT; |
| } |
| } |