| Index: packages/analyzer/lib/src/dart/constant/evaluation.dart
|
| diff --git a/packages/analyzer/lib/src/dart/constant/evaluation.dart b/packages/analyzer/lib/src/dart/constant/evaluation.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c3353512d565d7dcf5a3281f876b20d8d1bbf1ea
|
| --- /dev/null
|
| +++ b/packages/analyzer/lib/src/dart/constant/evaluation.dart
|
| @@ -0,0 +1,2089 @@
|
| +// 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 analyzer.src.dart.constant.evaluation;
|
| +
|
| +import 'dart:collection';
|
| +
|
| +import 'package:analyzer/context/declared_variables.dart';
|
| +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/constant/value.dart';
|
| +import 'package:analyzer/dart/element/element.dart';
|
| +import 'package:analyzer/dart/element/type.dart';
|
| +import 'package:analyzer/error/error.dart';
|
| +import 'package:analyzer/error/listener.dart';
|
| +import 'package:analyzer/src/dart/constant/utilities.dart';
|
| +import 'package:analyzer/src/dart/constant/value.dart';
|
| +import 'package:analyzer/src/dart/element/element.dart';
|
| +import 'package:analyzer/src/dart/element/member.dart';
|
| +import 'package:analyzer/src/error/codes.dart';
|
| +import 'package:analyzer/src/generated/engine.dart';
|
| +import 'package:analyzer/src/generated/engine.dart'
|
| + show AnalysisEngine, RecordingErrorListener;
|
| +import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
|
| +import 'package:analyzer/src/generated/type_system.dart'
|
| + show TypeSystem, TypeSystemImpl;
|
| +import 'package:analyzer/src/generated/utilities_collection.dart';
|
| +import 'package:analyzer/src/generated/utilities_dart.dart' show ParameterKind;
|
| +import 'package:analyzer/src/task/dart.dart';
|
| +
|
| +/**
|
| + * Helper class encapsulating the methods for evaluating constants and
|
| + * constant instance creation expressions.
|
| + */
|
| +class ConstantEvaluationEngine {
|
| + /**
|
| + * Parameter to "fromEnvironment" methods that denotes the default value.
|
| + */
|
| + static String _DEFAULT_VALUE_PARAM = "defaultValue";
|
| +
|
| + /**
|
| + * Source of RegExp matching any public identifier.
|
| + * From sdk/lib/internal/symbol.dart.
|
| + */
|
| + static String _PUBLIC_IDENTIFIER_RE =
|
| + "(?!${ConstantValueComputer._RESERVED_WORD_RE}\\b(?!\\\$))[a-zA-Z\$][\\w\$]*";
|
| +
|
| + /**
|
| + * RegExp that validates a non-empty non-private symbol.
|
| + * From sdk/lib/internal/symbol.dart.
|
| + */
|
| + static RegExp _PUBLIC_SYMBOL_PATTERN = new RegExp(
|
| + "^(?:${ConstantValueComputer._OPERATOR_RE}\$|$_PUBLIC_IDENTIFIER_RE(?:=?\$|[.](?!\$)))+?\$");
|
| +
|
| + /**
|
| + * The type provider used to access the known types.
|
| + */
|
| + final TypeProvider typeProvider;
|
| +
|
| + /**
|
| + * The type system. This is used to guess the types of constants when their
|
| + * exact value is unknown.
|
| + */
|
| + final TypeSystem typeSystem;
|
| +
|
| + /**
|
| + * The set of variables declared on the command line using '-D'.
|
| + */
|
| + final DeclaredVariables _declaredVariables;
|
| +
|
| + /**
|
| + * Validator used to verify correct dependency analysis when running unit
|
| + * tests.
|
| + */
|
| + final ConstantEvaluationValidator validator;
|
| +
|
| + /** Whether we are running in strong mode. */
|
| + final bool strongMode;
|
| +
|
| + /**
|
| + * Initialize a newly created [ConstantEvaluationEngine]. The [typeProvider]
|
| + * is used to access known types. [_declaredVariables] is the set of
|
| + * variables declared on the command line using '-D'. The [validator], if
|
| + * given, is used to verify correct dependency analysis when running unit
|
| + * tests.
|
| + */
|
| + ConstantEvaluationEngine(TypeProvider typeProvider, this._declaredVariables,
|
| + {ConstantEvaluationValidator validator, TypeSystem typeSystem})
|
| + : typeProvider = typeProvider,
|
| + strongMode =
|
| + typeProvider.objectType.element.context.analysisOptions.strongMode,
|
| + validator =
|
| + validator ?? new ConstantEvaluationValidator_ForProduction(),
|
| + typeSystem = typeSystem ?? new TypeSystemImpl();
|
| +
|
| + /**
|
| + * Check that the arguments to a call to fromEnvironment() are correct. The
|
| + * [arguments] are the AST nodes of the arguments. The [argumentValues] are
|
| + * the values of the unnamed arguments. The [namedArgumentValues] are the
|
| + * values of the named arguments. The [expectedDefaultValueType] is the
|
| + * allowed type of the "defaultValue" parameter (if present). Note:
|
| + * "defaultValue" is always allowed to be null. Return `true` if the arguments
|
| + * are correct, `false` if there is an error.
|
| + */
|
| + bool checkFromEnvironmentArguments(
|
| + NodeList<Expression> arguments,
|
| + List<DartObjectImpl> argumentValues,
|
| + HashMap<String, DartObjectImpl> namedArgumentValues,
|
| + InterfaceType expectedDefaultValueType) {
|
| + int argumentCount = arguments.length;
|
| + if (argumentCount < 1 || argumentCount > 2) {
|
| + return false;
|
| + }
|
| + if (arguments[0] is NamedExpression) {
|
| + return false;
|
| + }
|
| + if (!identical(argumentValues[0].type, typeProvider.stringType)) {
|
| + return false;
|
| + }
|
| + if (argumentCount == 2) {
|
| + Expression secondArgument = arguments[1];
|
| + if (secondArgument is NamedExpression) {
|
| + if (!(secondArgument.name.label.name == _DEFAULT_VALUE_PARAM)) {
|
| + return false;
|
| + }
|
| + ParameterizedType defaultValueType =
|
| + namedArgumentValues[_DEFAULT_VALUE_PARAM].type;
|
| + if (!(identical(defaultValueType, expectedDefaultValueType) ||
|
| + identical(defaultValueType, typeProvider.nullType))) {
|
| + return false;
|
| + }
|
| + } else {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * Check that the arguments to a call to Symbol() are correct. The [arguments]
|
| + * are the AST nodes of the arguments. The [argumentValues] are the values of
|
| + * the unnamed arguments. The [namedArgumentValues] are the values of the
|
| + * named arguments. Return `true` if the arguments are correct, `false` if
|
| + * there is an error.
|
| + */
|
| + bool checkSymbolArguments(
|
| + NodeList<Expression> arguments,
|
| + List<DartObjectImpl> argumentValues,
|
| + HashMap<String, DartObjectImpl> namedArgumentValues) {
|
| + if (arguments.length != 1) {
|
| + return false;
|
| + }
|
| + if (arguments[0] is NamedExpression) {
|
| + return false;
|
| + }
|
| + if (!identical(argumentValues[0].type, typeProvider.stringType)) {
|
| + return false;
|
| + }
|
| + String name = argumentValues[0].toStringValue();
|
| + return isValidPublicSymbol(name);
|
| + }
|
| +
|
| + /**
|
| + * Compute the constant value associated with the given [constant].
|
| + */
|
| + void computeConstantValue(ConstantEvaluationTarget constant) {
|
| + validator.beforeComputeValue(constant);
|
| + if (constant is ParameterElementImpl) {
|
| + Expression defaultValue = constant.constantInitializer;
|
| + if (defaultValue != null) {
|
| + RecordingErrorListener errorListener = new RecordingErrorListener();
|
| + ErrorReporter errorReporter =
|
| + new ErrorReporter(errorListener, constant.source);
|
| + DartObjectImpl dartObject =
|
| + defaultValue.accept(new ConstantVisitor(this, errorReporter));
|
| + constant.evaluationResult =
|
| + new EvaluationResultImpl(dartObject, errorListener.errors);
|
| + }
|
| + } else if (constant is VariableElementImpl) {
|
| + Expression constantInitializer = constant.constantInitializer;
|
| + if (constantInitializer != null) {
|
| + RecordingErrorListener errorListener = new RecordingErrorListener();
|
| + ErrorReporter errorReporter =
|
| + new ErrorReporter(errorListener, constant.source);
|
| + DartObjectImpl dartObject = constantInitializer
|
| + .accept(new ConstantVisitor(this, errorReporter));
|
| + // Only check the type for truly const declarations (don't check final
|
| + // fields with initializers, since their types may be generic. The type
|
| + // of the final field will be checked later, when the constructor is
|
| + // invoked).
|
| + if (dartObject != null && constant.isConst) {
|
| + if (!runtimeTypeMatch(dartObject, constant.type)) {
|
| + errorReporter.reportErrorForElement(
|
| + CheckedModeCompileTimeErrorCode.VARIABLE_TYPE_MISMATCH,
|
| + constant,
|
| + [dartObject.type, constant.type]);
|
| + }
|
| + }
|
| + constant.evaluationResult =
|
| + new EvaluationResultImpl(dartObject, errorListener.errors);
|
| + }
|
| + } else if (constant is ConstructorElement) {
|
| + if (constant.isConst) {
|
| + // No evaluation needs to be done; constructor declarations are only in
|
| + // the dependency graph to ensure that any constants referred to in
|
| + // initializer lists and parameter defaults are evaluated before
|
| + // invocations of the constructor. However we do need to annotate the
|
| + // element as being free of constant evaluation cycles so that later
|
| + // code will know that it is safe to evaluate.
|
| + (constant as ConstructorElementImpl).isCycleFree = true;
|
| + }
|
| + } else if (constant is ElementAnnotationImpl) {
|
| + Annotation constNode = constant.annotationAst;
|
| + Element element = constant.element;
|
| + if (element is PropertyAccessorElement &&
|
| + element.variable is VariableElementImpl) {
|
| + // The annotation is a reference to a compile-time constant variable.
|
| + // Just copy the evaluation result.
|
| + VariableElementImpl variableElement =
|
| + element.variable as VariableElementImpl;
|
| + if (variableElement.evaluationResult != null) {
|
| + constant.evaluationResult = variableElement.evaluationResult;
|
| + } else {
|
| + // This could happen in the event that the annotation refers to a
|
| + // non-constant. The error is detected elsewhere, so just silently
|
| + // ignore it here.
|
| + constant.evaluationResult = new EvaluationResultImpl(null);
|
| + }
|
| + } else if (element is ConstructorElementImpl &&
|
| + element.isConst &&
|
| + constNode.arguments != null) {
|
| + RecordingErrorListener errorListener = new RecordingErrorListener();
|
| + ErrorReporter errorReporter =
|
| + new ErrorReporter(errorListener, constant.source);
|
| + ConstantVisitor constantVisitor =
|
| + new ConstantVisitor(this, errorReporter);
|
| + DartObjectImpl result = evaluateConstructorCall(
|
| + constNode,
|
| + constNode.arguments.arguments,
|
| + element,
|
| + constantVisitor,
|
| + errorReporter);
|
| + constant.evaluationResult =
|
| + new EvaluationResultImpl(result, errorListener.errors);
|
| + } else {
|
| + // This may happen for invalid code (e.g. failing to pass arguments
|
| + // to an annotation which references a const constructor). The error
|
| + // is detected elsewhere, so just silently ignore it here.
|
| + constant.evaluationResult = new EvaluationResultImpl(null);
|
| + }
|
| + } else if (constant is VariableElement) {
|
| + // constant is a VariableElement but not a VariableElementImpl. This can
|
| + // happen sometimes in the case of invalid user code (for example, a
|
| + // constant expression that refers to a non-static field inside a generic
|
| + // class will wind up referring to a FieldMember). The error is detected
|
| + // elsewhere, so just silently ignore it here.
|
| + } else {
|
| + // Should not happen.
|
| + assert(false);
|
| + AnalysisEngine.instance.logger.logError(
|
| + "Constant value computer trying to compute the value of a node of type ${constant.runtimeType}");
|
| + return;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Determine which constant elements need to have their values computed
|
| + * prior to computing the value of [constant], and report them using
|
| + * [callback].
|
| + */
|
| + void computeDependencies(
|
| + ConstantEvaluationTarget constant, ReferenceFinderCallback callback) {
|
| + ReferenceFinder referenceFinder = new ReferenceFinder(callback);
|
| + if (constant is ConstructorElement) {
|
| + constant = getConstructorImpl(constant);
|
| + }
|
| + if (constant is VariableElementImpl) {
|
| + Expression initializer = constant.constantInitializer;
|
| + if (initializer != null) {
|
| + initializer.accept(referenceFinder);
|
| + }
|
| + } else if (constant is ConstructorElementImpl) {
|
| + if (constant.isConst) {
|
| + constant.isCycleFree = false;
|
| + ConstructorElement redirectedConstructor =
|
| + getConstRedirectedConstructor(constant);
|
| + if (redirectedConstructor != null) {
|
| + ConstructorElement redirectedConstructorBase =
|
| + getConstructorImpl(redirectedConstructor);
|
| + callback(redirectedConstructorBase);
|
| + return;
|
| + } else if (constant.isFactory) {
|
| + // Factory constructor, but getConstRedirectedConstructor returned
|
| + // null. This can happen if we're visiting one of the special external
|
| + // const factory constructors in the SDK, or if the code contains
|
| + // errors (such as delegating to a non-const constructor, or delegating
|
| + // to a constructor that can't be resolved). In any of these cases,
|
| + // we'll evaluate calls to this constructor without having to refer to
|
| + // any other constants. So we don't need to report any dependencies.
|
| + return;
|
| + }
|
| + bool defaultSuperInvocationNeeded = true;
|
| + List<ConstructorInitializer> initializers =
|
| + constant.constantInitializers;
|
| + for (ConstructorInitializer initializer in initializers) {
|
| + if (initializer is SuperConstructorInvocation ||
|
| + initializer is RedirectingConstructorInvocation) {
|
| + defaultSuperInvocationNeeded = false;
|
| + }
|
| + initializer.accept(referenceFinder);
|
| + }
|
| + if (defaultSuperInvocationNeeded) {
|
| + // No explicit superconstructor invocation found, so we need to
|
| + // manually insert a reference to the implicit superconstructor.
|
| + InterfaceType superclass =
|
| + (constant.returnType as InterfaceType).superclass;
|
| + if (superclass != null && !superclass.isObject) {
|
| + ConstructorElement unnamedConstructor =
|
| + getConstructorImpl(superclass.element.unnamedConstructor);
|
| + if (unnamedConstructor != null) {
|
| + callback(unnamedConstructor);
|
| + }
|
| + }
|
| + }
|
| + for (FieldElement field in constant.enclosingElement.fields) {
|
| + // Note: non-static const isn't allowed but we handle it anyway so
|
| + // that we won't be confused by incorrect code.
|
| + if ((field.isFinal || field.isConst) &&
|
| + !field.isStatic &&
|
| + field.initializer != null) {
|
| + callback(field);
|
| + }
|
| + }
|
| + for (ParameterElement parameterElement in constant.parameters) {
|
| + callback(parameterElement);
|
| + }
|
| + }
|
| + } else if (constant is ElementAnnotationImpl) {
|
| + Annotation constNode = constant.annotationAst;
|
| + Element element = constant.element;
|
| + if (element is PropertyAccessorElement &&
|
| + element.variable is VariableElementImpl) {
|
| + // The annotation is a reference to a compile-time constant variable,
|
| + // so it depends on the variable.
|
| + callback(element.variable);
|
| + } else if (element is ConstructorElementImpl) {
|
| + // The annotation is a constructor invocation, so it depends on the
|
| + // constructor.
|
| + callback(element);
|
| + } else {
|
| + // This could happen in the event of invalid code. The error will be
|
| + // reported at constant evaluation time.
|
| + }
|
| + if (constNode == null) {
|
| + // We cannot determine what element the annotation is on, nor the offset
|
| + // of the annotation, so there's not a lot of information in this
|
| + // message, but it's better than getting an exception.
|
| + // https://github.com/dart-lang/sdk/issues/26811
|
| + AnalysisEngine.instance.logger.logInformation(
|
| + 'No annotationAst for $constant in ${constant.compilationUnit}');
|
| + } else if (constNode.arguments != null) {
|
| + constNode.arguments.accept(referenceFinder);
|
| + }
|
| + } else if (constant is VariableElement) {
|
| + // constant is a VariableElement but not a VariableElementImpl. This can
|
| + // happen sometimes in the case of invalid user code (for example, a
|
| + // constant expression that refers to a non-static field inside a generic
|
| + // class will wind up referring to a FieldMember). So just don't bother
|
| + // computing any dependencies.
|
| + } else {
|
| + // Should not happen.
|
| + assert(false);
|
| + AnalysisEngine.instance.logger.logError(
|
| + "Constant value computer trying to compute the value of a node of type ${constant.runtimeType}");
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Evaluate a call to fromEnvironment() on the bool, int, or String class. The
|
| + * [environmentValue] is the value fetched from the environment. The
|
| + * [builtInDefaultValue] is the value that should be used as the default if no
|
| + * "defaultValue" argument appears in [namedArgumentValues]. The
|
| + * [namedArgumentValues] are the values of the named parameters passed to
|
| + * fromEnvironment(). Return a [DartObjectImpl] object corresponding to the
|
| + * evaluated result.
|
| + */
|
| + DartObjectImpl computeValueFromEnvironment(
|
| + DartObject environmentValue,
|
| + DartObjectImpl builtInDefaultValue,
|
| + HashMap<String, DartObjectImpl> namedArgumentValues) {
|
| + DartObjectImpl value = environmentValue as DartObjectImpl;
|
| + if (value.isUnknown || value.isNull) {
|
| + // The name either doesn't exist in the environment or we couldn't parse
|
| + // the corresponding value.
|
| + // If the code supplied an explicit default, use it.
|
| + if (namedArgumentValues.containsKey(_DEFAULT_VALUE_PARAM)) {
|
| + value = namedArgumentValues[_DEFAULT_VALUE_PARAM];
|
| + } else if (value.isNull) {
|
| + // The code didn't supply an explicit default.
|
| + // The name exists in the environment but we couldn't parse the
|
| + // corresponding value.
|
| + // So use the built-in default value, because this is what the VM does.
|
| + value = builtInDefaultValue;
|
| + } else {
|
| + // The code didn't supply an explicit default.
|
| + // The name doesn't exist in the environment.
|
| + // The VM would use the built-in default value, but we don't want to do
|
| + // that for analysis because it's likely to lead to cascading errors.
|
| + // So just leave [value] in the unknown state.
|
| + }
|
| + }
|
| + return value;
|
| + }
|
| +
|
| + DartObjectImpl evaluateConstructorCall(
|
| + AstNode node,
|
| + List<Expression> arguments,
|
| + ConstructorElement constructor,
|
| + ConstantVisitor constantVisitor,
|
| + ErrorReporter errorReporter) {
|
| + if (!getConstructorImpl(constructor).isCycleFree) {
|
| + // It's not safe to evaluate this constructor, so bail out.
|
| + // TODO(paulberry): ensure that a reasonable error message is produced
|
| + // in this case, as well as other cases involving constant expression
|
| + // circularities (e.g. "compile-time constant expression depends on
|
| + // itself")
|
| + return new DartObjectImpl.validWithUnknownValue(constructor.returnType);
|
| + }
|
| + int argumentCount = arguments.length;
|
| + List<DartObjectImpl> argumentValues =
|
| + new List<DartObjectImpl>(argumentCount);
|
| + List<Expression> argumentNodes = new List<Expression>(argumentCount);
|
| + HashMap<String, DartObjectImpl> namedArgumentValues =
|
| + new HashMap<String, DartObjectImpl>();
|
| + HashMap<String, NamedExpression> namedArgumentNodes =
|
| + new HashMap<String, NamedExpression>();
|
| + for (int i = 0; i < argumentCount; i++) {
|
| + Expression argument = arguments[i];
|
| + if (argument is NamedExpression) {
|
| + String name = argument.name.label.name;
|
| + namedArgumentValues[name] =
|
| + constantVisitor._valueOf(argument.expression);
|
| + namedArgumentNodes[name] = argument;
|
| + argumentValues[i] = typeProvider.nullObject;
|
| + } else {
|
| + argumentValues[i] = constantVisitor._valueOf(argument);
|
| + argumentNodes[i] = argument;
|
| + }
|
| + }
|
| + constructor = followConstantRedirectionChain(constructor);
|
| + InterfaceType definingClass = constructor.returnType as InterfaceType;
|
| + if (constructor.isFactory) {
|
| + // We couldn't find a non-factory constructor.
|
| + // See if it's because we reached an external const factory constructor
|
| + // that we can emulate.
|
| + if (constructor.name == "fromEnvironment") {
|
| + if (!checkFromEnvironmentArguments(
|
| + arguments, argumentValues, namedArgumentValues, definingClass)) {
|
| + errorReporter.reportErrorForNode(
|
| + CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node);
|
| + return null;
|
| + }
|
| + String variableName =
|
| + argumentCount < 1 ? null : argumentValues[0].toStringValue();
|
| + if (identical(definingClass, typeProvider.boolType)) {
|
| + DartObject valueFromEnvironment;
|
| + valueFromEnvironment =
|
| + _declaredVariables.getBool(typeProvider, variableName);
|
| + return computeValueFromEnvironment(
|
| + valueFromEnvironment,
|
| + new DartObjectImpl(typeProvider.boolType, BoolState.FALSE_STATE),
|
| + namedArgumentValues);
|
| + } else if (identical(definingClass, typeProvider.intType)) {
|
| + DartObject valueFromEnvironment;
|
| + valueFromEnvironment =
|
| + _declaredVariables.getInt(typeProvider, variableName);
|
| + return computeValueFromEnvironment(
|
| + valueFromEnvironment,
|
| + new DartObjectImpl(typeProvider.nullType, NullState.NULL_STATE),
|
| + namedArgumentValues);
|
| + } else if (identical(definingClass, typeProvider.stringType)) {
|
| + DartObject valueFromEnvironment;
|
| + valueFromEnvironment =
|
| + _declaredVariables.getString(typeProvider, variableName);
|
| + return computeValueFromEnvironment(
|
| + valueFromEnvironment,
|
| + new DartObjectImpl(typeProvider.nullType, NullState.NULL_STATE),
|
| + namedArgumentValues);
|
| + }
|
| + } else if (constructor.name == "" &&
|
| + identical(definingClass, typeProvider.symbolType) &&
|
| + argumentCount == 1) {
|
| + if (!checkSymbolArguments(
|
| + arguments, argumentValues, namedArgumentValues)) {
|
| + errorReporter.reportErrorForNode(
|
| + CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node);
|
| + return null;
|
| + }
|
| + String argumentValue = argumentValues[0].toStringValue();
|
| + return new DartObjectImpl(
|
| + definingClass, new SymbolState(argumentValue));
|
| + }
|
| + // Either it's an external const factory constructor that we can't
|
| + // emulate, or an error occurred (a cycle, or a const constructor trying
|
| + // to delegate to a non-const constructor).
|
| + // In the former case, the best we can do is consider it an unknown value.
|
| + // In the latter case, the error has already been reported, so considering
|
| + // it an unknown value will suppress further errors.
|
| + return new DartObjectImpl.validWithUnknownValue(definingClass);
|
| + }
|
| + ConstructorElementImpl constructorBase = getConstructorImpl(constructor);
|
| + validator.beforeGetConstantInitializers(constructorBase);
|
| + List<ConstructorInitializer> initializers =
|
| + constructorBase.constantInitializers;
|
| + if (initializers == null) {
|
| + // This can happen in some cases where there are compile errors in the
|
| + // code being analyzed (for example if the code is trying to create a
|
| + // const instance using a non-const constructor, or the node we're
|
| + // visiting is involved in a cycle). The error has already been reported,
|
| + // so consider it an unknown value to suppress further errors.
|
| + return new DartObjectImpl.validWithUnknownValue(definingClass);
|
| + }
|
| +
|
| + // In strong mode, we allow constants to have type arguments.
|
| + //
|
| + // They will be added to the lexical environment when evaluating
|
| + // subexpressions.
|
| + HashMap<String, DartObjectImpl> typeArgumentMap;
|
| + if (strongMode) {
|
| + // Instantiate the constructor with the in-scope type arguments.
|
| + definingClass = constantVisitor.evaluateType(definingClass);
|
| + constructor = ConstructorMember.from(constructorBase, definingClass);
|
| +
|
| + typeArgumentMap = new HashMap<String, DartObjectImpl>.fromIterables(
|
| + definingClass.typeParameters.map((t) => t.name),
|
| + definingClass.typeArguments.map(constantVisitor.typeConstant));
|
| + }
|
| +
|
| + var fieldMap = new HashMap<String, DartObjectImpl>();
|
| + var fieldInitVisitor = new ConstantVisitor(this, errorReporter,
|
| + lexicalEnvironment: typeArgumentMap);
|
| + // Start with final fields that are initialized at their declaration site.
|
| + List<FieldElement> fields = constructor.enclosingElement.fields;
|
| + for (int i = 0; i < fields.length; i++) {
|
| + FieldElement field = fields[i];
|
| + if ((field.isFinal || field.isConst) &&
|
| + !field.isStatic &&
|
| + field is ConstFieldElementImpl) {
|
| + validator.beforeGetFieldEvaluationResult(field);
|
| +
|
| + DartObjectImpl fieldValue;
|
| + if (strongMode) {
|
| + fieldValue = field.constantInitializer.accept(fieldInitVisitor);
|
| + } else {
|
| + fieldValue = field.evaluationResult?.value;
|
| + }
|
| + // It is possible that the evaluation result is null.
|
| + // This happens for example when we have duplicate fields.
|
| + // class Test {final x = 1; final x = 2; const Test();}
|
| + if (fieldValue == null) {
|
| + continue;
|
| + }
|
| + // Match the value and the type.
|
| + DartType fieldType =
|
| + FieldMember.from(field, constructor.returnType).type;
|
| + if (fieldValue != null && !runtimeTypeMatch(fieldValue, fieldType)) {
|
| + errorReporter.reportErrorForNode(
|
| + CheckedModeCompileTimeErrorCode
|
| + .CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH,
|
| + node,
|
| + [fieldValue.type, field.name, fieldType]);
|
| + }
|
| + fieldMap[field.name] = fieldValue;
|
| + }
|
| + }
|
| + // Now evaluate the constructor declaration.
|
| + HashMap<String, DartObjectImpl> parameterMap =
|
| + new HashMap<String, DartObjectImpl>();
|
| + List<ParameterElement> parameters = constructor.parameters;
|
| + int parameterCount = parameters.length;
|
| +
|
| + for (int i = 0; i < parameterCount; i++) {
|
| + ParameterElement parameter = parameters[i];
|
| + ParameterElement baseParameter = parameter;
|
| + while (baseParameter is ParameterMember) {
|
| + baseParameter = (baseParameter as ParameterMember).baseElement;
|
| + }
|
| + DartObjectImpl argumentValue = null;
|
| + AstNode errorTarget = null;
|
| + if (baseParameter.parameterKind == ParameterKind.NAMED) {
|
| + argumentValue = namedArgumentValues[baseParameter.name];
|
| + errorTarget = namedArgumentNodes[baseParameter.name];
|
| + } else if (i < argumentCount) {
|
| + argumentValue = argumentValues[i];
|
| + errorTarget = argumentNodes[i];
|
| + }
|
| + if (errorTarget == null) {
|
| + // No argument node that we can direct error messages to, because we
|
| + // are handling an optional parameter that wasn't specified. So just
|
| + // direct error messages to the constructor call.
|
| + errorTarget = node;
|
| + }
|
| + if (argumentValue == null && baseParameter is ParameterElementImpl) {
|
| + // The parameter is an optional positional parameter for which no value
|
| + // was provided, so use the default value.
|
| + validator.beforeGetParameterDefault(baseParameter);
|
| + if (strongMode && baseParameter is ConstVariableElement) {
|
| + var defaultValue =
|
| + (baseParameter as ConstVariableElement).constantInitializer;
|
| + if (defaultValue == null) {
|
| + argumentValue = typeProvider.nullObject;
|
| + } else {
|
| + argumentValue = defaultValue.accept(fieldInitVisitor);
|
| + }
|
| + } else {
|
| + EvaluationResultImpl evaluationResult =
|
| + baseParameter.evaluationResult;
|
| + if (evaluationResult == null) {
|
| + // No default was provided, so the default value is null.
|
| + argumentValue = typeProvider.nullObject;
|
| + } else if (evaluationResult.value != null) {
|
| + argumentValue = evaluationResult.value;
|
| + }
|
| + }
|
| + }
|
| + if (argumentValue != null) {
|
| + if (!runtimeTypeMatch(argumentValue, parameter.type)) {
|
| + errorReporter.reportErrorForNode(
|
| + CheckedModeCompileTimeErrorCode
|
| + .CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH,
|
| + errorTarget,
|
| + [argumentValue.type, parameter.type]);
|
| + }
|
| + if (baseParameter.isInitializingFormal) {
|
| + FieldElement field = (parameter as FieldFormalParameterElement).field;
|
| + if (field != null) {
|
| + DartType fieldType = field.type;
|
| + if (fieldType != parameter.type) {
|
| + // We've already checked that the argument can be assigned to the
|
| + // parameter; we also need to check that it can be assigned to
|
| + // the field.
|
| + if (!runtimeTypeMatch(argumentValue, fieldType)) {
|
| + errorReporter.reportErrorForNode(
|
| + CheckedModeCompileTimeErrorCode
|
| + .CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH,
|
| + errorTarget,
|
| + [argumentValue.type, fieldType]);
|
| + }
|
| + }
|
| + String fieldName = field.name;
|
| + if (fieldMap.containsKey(fieldName)) {
|
| + errorReporter.reportErrorForNode(
|
| + CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node);
|
| + }
|
| + fieldMap[fieldName] = argumentValue;
|
| + }
|
| + }
|
| + String name = baseParameter.name;
|
| + parameterMap[name] = argumentValue;
|
| + }
|
| + }
|
| + ConstantVisitor initializerVisitor = new ConstantVisitor(
|
| + this, errorReporter,
|
| + lexicalEnvironment: parameterMap);
|
| + String superName = null;
|
| + NodeList<Expression> superArguments = null;
|
| + for (ConstructorInitializer initializer in initializers) {
|
| + if (initializer is ConstructorFieldInitializer) {
|
| + Expression initializerExpression = initializer.expression;
|
| + DartObjectImpl evaluationResult =
|
| + initializerExpression.accept(initializerVisitor);
|
| + if (evaluationResult != null) {
|
| + String fieldName = initializer.fieldName.name;
|
| + if (fieldMap.containsKey(fieldName)) {
|
| + errorReporter.reportErrorForNode(
|
| + CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node);
|
| + }
|
| + fieldMap[fieldName] = evaluationResult;
|
| + PropertyAccessorElement getter = definingClass.getGetter(fieldName);
|
| + if (getter != null) {
|
| + PropertyInducingElement field = getter.variable;
|
| + if (!runtimeTypeMatch(evaluationResult, field.type)) {
|
| + errorReporter.reportErrorForNode(
|
| + CheckedModeCompileTimeErrorCode
|
| + .CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH,
|
| + node,
|
| + [evaluationResult.type, fieldName, field.type]);
|
| + }
|
| + }
|
| + }
|
| + } else if (initializer is SuperConstructorInvocation) {
|
| + SimpleIdentifier name = initializer.constructorName;
|
| + if (name != null) {
|
| + superName = name.name;
|
| + }
|
| + superArguments = initializer.argumentList.arguments;
|
| + } else if (initializer is RedirectingConstructorInvocation) {
|
| + // This is a redirecting constructor, so just evaluate the constructor
|
| + // it redirects to.
|
| + ConstructorElement constructor = initializer.staticElement;
|
| + if (constructor != null && constructor.isConst) {
|
| + return evaluateConstructorCall(
|
| + node,
|
| + initializer.argumentList.arguments,
|
| + constructor,
|
| + initializerVisitor,
|
| + errorReporter);
|
| + }
|
| + }
|
| + }
|
| + // Evaluate explicit or implicit call to super().
|
| + InterfaceType superclass = definingClass.superclass;
|
| + if (superclass != null && !superclass.isObject) {
|
| + ConstructorElement superConstructor =
|
| + superclass.lookUpConstructor(superName, constructor.library);
|
| + if (superConstructor != null) {
|
| + if (superArguments == null) {
|
| + superArguments = new NodeList<Expression>(null);
|
| + }
|
| +
|
| + evaluateSuperConstructorCall(node, fieldMap, superConstructor,
|
| + superArguments, initializerVisitor, errorReporter);
|
| + }
|
| + }
|
| + return new DartObjectImpl(definingClass, new GenericState(fieldMap));
|
| + }
|
| +
|
| + void evaluateSuperConstructorCall(
|
| + AstNode node,
|
| + HashMap<String, DartObjectImpl> fieldMap,
|
| + ConstructorElement superConstructor,
|
| + List<Expression> superArguments,
|
| + ConstantVisitor initializerVisitor,
|
| + ErrorReporter errorReporter) {
|
| + if (superConstructor != null && superConstructor.isConst) {
|
| + DartObjectImpl evaluationResult = evaluateConstructorCall(node,
|
| + superArguments, superConstructor, initializerVisitor, errorReporter);
|
| + if (evaluationResult != null) {
|
| + fieldMap[GenericState.SUPERCLASS_FIELD] = evaluationResult;
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Attempt to follow the chain of factory redirections until a constructor is
|
| + * reached which is not a const factory constructor. Return the constant
|
| + * constructor which terminates the chain of factory redirections, if the
|
| + * chain terminates. If there is a problem (e.g. a redirection can't be found,
|
| + * or a cycle is encountered), the chain will be followed as far as possible
|
| + * and then a const factory constructor will be returned.
|
| + */
|
| + ConstructorElement followConstantRedirectionChain(
|
| + ConstructorElement constructor) {
|
| + HashSet<ConstructorElement> constructorsVisited =
|
| + new HashSet<ConstructorElement>();
|
| + while (true) {
|
| + ConstructorElement redirectedConstructor =
|
| + getConstRedirectedConstructor(constructor);
|
| + if (redirectedConstructor == null) {
|
| + break;
|
| + } else {
|
| + ConstructorElement constructorBase = getConstructorImpl(constructor);
|
| + constructorsVisited.add(constructorBase);
|
| + ConstructorElement redirectedConstructorBase =
|
| + getConstructorImpl(redirectedConstructor);
|
| + if (constructorsVisited.contains(redirectedConstructorBase)) {
|
| + // Cycle in redirecting factory constructors--this is not allowed
|
| + // and is checked elsewhere--see
|
| + // [ErrorVerifier.checkForRecursiveFactoryRedirect()]).
|
| + break;
|
| + }
|
| + }
|
| + constructor = redirectedConstructor;
|
| + }
|
| + return constructor;
|
| + }
|
| +
|
| + /**
|
| + * Generate an error indicating that the given [constant] is not a valid
|
| + * compile-time constant because it references at least one of the constants
|
| + * in the given [cycle], each of which directly or indirectly references the
|
| + * constant.
|
| + */
|
| + void generateCycleError(Iterable<ConstantEvaluationTarget> cycle,
|
| + ConstantEvaluationTarget constant) {
|
| + if (constant is VariableElement) {
|
| + RecordingErrorListener errorListener = new RecordingErrorListener();
|
| + ErrorReporter errorReporter =
|
| + new ErrorReporter(errorListener, constant.source);
|
| + // TODO(paulberry): It would be really nice if we could extract enough
|
| + // information from the 'cycle' argument to provide the user with a
|
| + // description of the cycle.
|
| + errorReporter.reportErrorForElement(
|
| + CompileTimeErrorCode.RECURSIVE_COMPILE_TIME_CONSTANT, constant, []);
|
| + (constant as VariableElementImpl).evaluationResult =
|
| + new EvaluationResultImpl(null, errorListener.errors);
|
| + } else if (constant is ConstructorElement) {
|
| + // We don't report cycle errors on constructor declarations since there
|
| + // is nowhere to put the error information.
|
| + } else {
|
| + // Should not happen. Formal parameter defaults and annotations should
|
| + // never appear as part of a cycle because they can't be referred to.
|
| + assert(false);
|
| + AnalysisEngine.instance.logger.logError(
|
| + "Constant value computer trying to report a cycle error for a node of type ${constant.runtimeType}");
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * If [constructor] redirects to another const constructor, return the
|
| + * const constructor it redirects to. Otherwise return `null`.
|
| + */
|
| + ConstructorElement getConstRedirectedConstructor(
|
| + ConstructorElement constructor) {
|
| + if (!constructor.isFactory) {
|
| + return null;
|
| + }
|
| + if (identical(constructor.enclosingElement.type, typeProvider.symbolType)) {
|
| + // The dart:core.Symbol has a const factory constructor that redirects
|
| + // to dart:_internal.Symbol. That in turn redirects to an external
|
| + // const constructor, which we won't be able to evaluate.
|
| + // So stop following the chain of redirections at dart:core.Symbol, and
|
| + // let [evaluateInstanceCreationExpression] handle it specially.
|
| + return null;
|
| + }
|
| + ConstructorElement redirectedConstructor =
|
| + constructor.redirectedConstructor;
|
| + if (redirectedConstructor == null) {
|
| + // This can happen if constructor is an external factory constructor.
|
| + return null;
|
| + }
|
| + if (!redirectedConstructor.isConst) {
|
| + // Delegating to a non-const constructor--this is not allowed (and
|
| + // is checked elsewhere--see
|
| + // [ErrorVerifier.checkForRedirectToNonConstConstructor()]).
|
| + return null;
|
| + }
|
| + return redirectedConstructor;
|
| + }
|
| +
|
| + /**
|
| + * Check if the object [obj] matches the type [type] according to runtime type
|
| + * checking rules.
|
| + */
|
| + bool runtimeTypeMatch(DartObjectImpl obj, DartType type) {
|
| + if (obj.isNull) {
|
| + return true;
|
| + }
|
| + if (type.isUndefined) {
|
| + return false;
|
| + }
|
| + return obj.type.isSubtypeOf(type);
|
| + }
|
| +
|
| + /**
|
| + * Determine whether the given string is a valid name for a public symbol
|
| + * (i.e. whether it is allowed for a call to the Symbol constructor).
|
| + */
|
| + static bool isValidPublicSymbol(String name) =>
|
| + name.isEmpty || name == "void" || _PUBLIC_SYMBOL_PATTERN.hasMatch(name);
|
| +}
|
| +
|
| +/**
|
| + * Interface used by unit tests to verify correct dependency analysis during
|
| + * constant evaluation.
|
| + */
|
| +abstract class ConstantEvaluationValidator {
|
| + /**
|
| + * This method is called just before computing the constant value associated
|
| + * with [constant]. Unit tests will override this method to introduce
|
| + * additional error checking.
|
| + */
|
| + void beforeComputeValue(ConstantEvaluationTarget constant);
|
| +
|
| + /**
|
| + * This method is called just before getting the constant initializers
|
| + * associated with the [constructor]. Unit tests will override this method to
|
| + * introduce additional error checking.
|
| + */
|
| + void beforeGetConstantInitializers(ConstructorElement constructor);
|
| +
|
| + /**
|
| + * This method is called just before retrieving an evaluation result from an
|
| + * element. Unit tests will override it to introduce additional error
|
| + * checking.
|
| + */
|
| + void beforeGetEvaluationResult(ConstantEvaluationTarget constant);
|
| +
|
| + /**
|
| + * This method is called just before getting the constant value of a field
|
| + * with an initializer. Unit tests will override this method to introduce
|
| + * additional error checking.
|
| + */
|
| + void beforeGetFieldEvaluationResult(FieldElementImpl field);
|
| +
|
| + /**
|
| + * This method is called just before getting a parameter's default value. Unit
|
| + * tests will override this method to introduce additional error checking.
|
| + */
|
| + void beforeGetParameterDefault(ParameterElement parameter);
|
| +}
|
| +
|
| +/**
|
| + * Implementation of [ConstantEvaluationValidator] used in production; does no
|
| + * validation.
|
| + */
|
| +class ConstantEvaluationValidator_ForProduction
|
| + implements ConstantEvaluationValidator {
|
| + @override
|
| + void beforeComputeValue(ConstantEvaluationTarget constant) {}
|
| +
|
| + @override
|
| + void beforeGetConstantInitializers(ConstructorElement constructor) {}
|
| +
|
| + @override
|
| + void beforeGetEvaluationResult(ConstantEvaluationTarget constant) {}
|
| +
|
| + @override
|
| + void beforeGetFieldEvaluationResult(FieldElementImpl field) {}
|
| +
|
| + @override
|
| + void beforeGetParameterDefault(ParameterElement parameter) {}
|
| +}
|
| +
|
| +/**
|
| + * An object used to compute the values of constant variables and constant
|
| + * constructor invocations in one or more compilation units. The expected usage
|
| + * pattern is for the compilation units to be added to this computer using the
|
| + * method [add] and then for the method [computeValues] to be invoked exactly
|
| + * once. Any use of an instance after invoking the method [computeValues] will
|
| + * result in unpredictable behavior.
|
| + */
|
| +class ConstantValueComputer {
|
| + /**
|
| + * Source of RegExp matching declarable operator names.
|
| + * From sdk/lib/internal/symbol.dart.
|
| + */
|
| + static String _OPERATOR_RE =
|
| + "(?:[\\-+*/%&|^]|\\[\\]=?|==|~/?|<[<=]?|>[>=]?|unary-)";
|
| +
|
| + /**
|
| + * Source of RegExp matching Dart reserved words.
|
| + * From sdk/lib/internal/symbol.dart.
|
| + */
|
| + static String _RESERVED_WORD_RE =
|
| + "(?:assert|break|c(?:a(?:se|tch)|lass|on(?:st|tinue))|d(?:efault|o)|e(?:lse|num|xtends)|f(?:alse|inal(?:ly)?|or)|i[fns]|n(?:ew|ull)|ret(?:hrow|urn)|s(?:uper|witch)|t(?:h(?:is|row)|r(?:ue|y))|v(?:ar|oid)|w(?:hile|ith))";
|
| +
|
| + /**
|
| + * A graph in which the nodes are the constants, and the edges are from each
|
| + * constant to the other constants that are referenced by it.
|
| + */
|
| + DirectedGraph<ConstantEvaluationTarget> referenceGraph =
|
| + new DirectedGraph<ConstantEvaluationTarget>();
|
| +
|
| + /**
|
| + * The elements whose constant values need to be computed. Any elements
|
| + * which appear in [referenceGraph] but not in this set either belong to a
|
| + * different library cycle (and hence don't need to be recomputed) or were
|
| + * computed during a previous stage of resolution stage (e.g. constants
|
| + * associated with enums).
|
| + */
|
| + HashSet<ConstantEvaluationTarget> _constantsToCompute =
|
| + new HashSet<ConstantEvaluationTarget>();
|
| +
|
| + /**
|
| + * The evaluation engine that does the work of evaluating instance creation
|
| + * expressions.
|
| + */
|
| + final ConstantEvaluationEngine evaluationEngine;
|
| +
|
| + /**
|
| + * Initialize a newly created constant value computer. The [typeProvider] is
|
| + * the type provider used to access known types. The [declaredVariables] is
|
| + * the set of variables declared on the command line using '-D'.
|
| + */
|
| + ConstantValueComputer(
|
| + TypeProvider typeProvider, DeclaredVariables declaredVariables,
|
| + [ConstantEvaluationValidator validator, TypeSystem typeSystem])
|
| + : evaluationEngine = new ConstantEvaluationEngine(
|
| + typeProvider, declaredVariables,
|
| + validator: validator, typeSystem: typeSystem);
|
| +
|
| + /**
|
| + * Add the constants in the given compilation [unit] to the list of constants
|
| + * whose value needs to be computed.
|
| + */
|
| + void add(CompilationUnit unit) {
|
| + ConstantFinder constantFinder = new ConstantFinder();
|
| + unit.accept(constantFinder);
|
| + _constantsToCompute.addAll(constantFinder.constantsToCompute);
|
| + }
|
| +
|
| + /**
|
| + * Compute values for all of the constants in the compilation units that were
|
| + * added.
|
| + */
|
| + void computeValues() {
|
| + for (ConstantEvaluationTarget constant in _constantsToCompute) {
|
| + referenceGraph.addNode(constant);
|
| + evaluationEngine.computeDependencies(constant,
|
| + (ConstantEvaluationTarget dependency) {
|
| + referenceGraph.addEdge(constant, dependency);
|
| + });
|
| + }
|
| + List<List<ConstantEvaluationTarget>> topologicalSort =
|
| + referenceGraph.computeTopologicalSort();
|
| + for (List<ConstantEvaluationTarget> constantsInCycle in topologicalSort) {
|
| + if (constantsInCycle.length == 1) {
|
| + ConstantEvaluationTarget constant = constantsInCycle[0];
|
| + if (!referenceGraph.getTails(constant).contains(constant)) {
|
| + _computeValueFor(constant);
|
| + continue;
|
| + }
|
| + }
|
| + for (ConstantEvaluationTarget constant in constantsInCycle) {
|
| + evaluationEngine.generateCycleError(constantsInCycle, constant);
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Compute a value for the given [constant].
|
| + */
|
| + void _computeValueFor(ConstantEvaluationTarget constant) {
|
| + if (!_constantsToCompute.contains(constant)) {
|
| + // Element is in the dependency graph but should have been computed by
|
| + // a previous stage of analysis.
|
| + // TODO(paulberry): once we have moved over to the new task model, this
|
| + // should only occur for constants associated with enum members. Once
|
| + // that happens we should add an assertion to verify that it doesn't
|
| + // occur in any other cases.
|
| + return;
|
| + }
|
| + evaluationEngine.computeConstantValue(constant);
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A visitor used to evaluate constant expressions to produce their compile-time
|
| + * value. According to the Dart Language Specification: <blockquote> 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
|
| + * <b>null</b>.
|
| + * * A literal symbol.
|
| + * * <b>null</b>.
|
| + * * 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 <i>(e)</i> where <i>e</i> is a constant
|
| + * expression.
|
| + * * 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.
|
| + * * 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.
|
| + * * 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.
|
| + * * 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>.
|
| + * * 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>.
|
| + * * 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.
|
| + * </blockquote>
|
| + */
|
| +class ConstantVisitor extends UnifyingAstVisitor<DartObjectImpl> {
|
| + /**
|
| + * The type provider used to access the known types.
|
| + */
|
| + final ConstantEvaluationEngine evaluationEngine;
|
| +
|
| + final HashMap<String, DartObjectImpl> _lexicalEnvironment;
|
| +
|
| + /**
|
| + * Error reporter that we use to report errors accumulated while computing the
|
| + * constant.
|
| + */
|
| + final ErrorReporter _errorReporter;
|
| +
|
| + /**
|
| + * Helper class used to compute constant values.
|
| + */
|
| + DartObjectComputer _dartObjectComputer;
|
| +
|
| + /**
|
| + * Initialize a newly created constant visitor. The [evaluationEngine] is
|
| + * used to evaluate instance creation expressions. The [lexicalEnvironment]
|
| + * is a map containing values which should override identifiers, or `null` if
|
| + * no overriding is necessary. The [_errorReporter] is used to report errors
|
| + * found during evaluation. The [validator] is used by unit tests to verify
|
| + * correct dependency analysis.
|
| + */
|
| + ConstantVisitor(this.evaluationEngine, this._errorReporter,
|
| + {HashMap<String, DartObjectImpl> lexicalEnvironment})
|
| + : _lexicalEnvironment = lexicalEnvironment {
|
| + this._dartObjectComputer =
|
| + new DartObjectComputer(_errorReporter, evaluationEngine.typeProvider);
|
| + }
|
| +
|
| + /**
|
| + * Convenience getter to gain access to the [evalationEngine]'s type
|
| + * provider.
|
| + */
|
| + TypeProvider get _typeProvider => evaluationEngine.typeProvider;
|
| +
|
| + /**
|
| + * Convenience getter to gain access to the [evaluationEngine]'s type system.
|
| + */
|
| + TypeSystem get _typeSystem => evaluationEngine.typeSystem;
|
| +
|
| + /**
|
| + * Given a [type] that may contain free type variables, evaluate them against
|
| + * the current lexical environment and return the substituted type.
|
| + */
|
| + DartType evaluateType(DartType type) {
|
| + if (type is TypeParameterType) {
|
| + // Constants may only refer to type parameters in strong mode.
|
| + if (!evaluationEngine.strongMode) {
|
| + return null;
|
| + }
|
| +
|
| + String name = type.name;
|
| + if (_lexicalEnvironment != null) {
|
| + return _lexicalEnvironment[name]?.toTypeValue() ?? type;
|
| + }
|
| + return type;
|
| + }
|
| + if (type is ParameterizedType) {
|
| + List<DartType> typeArguments;
|
| + for (int i = 0; i < type.typeArguments.length; i++) {
|
| + DartType ta = type.typeArguments[i];
|
| + DartType t = evaluateType(ta);
|
| + if (!identical(t, ta)) {
|
| + if (typeArguments == null) {
|
| + typeArguments = type.typeArguments.toList(growable: false);
|
| + }
|
| + typeArguments[i] = t;
|
| + }
|
| + }
|
| + if (typeArguments == null) return type;
|
| + return type.substitute2(typeArguments, type.typeArguments);
|
| + }
|
| + return type;
|
| + }
|
| +
|
| + /**
|
| + * Given a [type], returns the constant value that contains that type value.
|
| + */
|
| + DartObjectImpl typeConstant(DartType type) {
|
| + return new DartObjectImpl(_typeProvider.typeType, new TypeState(type));
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitAdjacentStrings(AdjacentStrings node) {
|
| + DartObjectImpl result = null;
|
| + for (StringLiteral string in node.strings) {
|
| + if (result == null) {
|
| + result = string.accept(this);
|
| + } else {
|
| + result =
|
| + _dartObjectComputer.concatenate(node, result, string.accept(this));
|
| + }
|
| + }
|
| + return result;
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitBinaryExpression(BinaryExpression node) {
|
| + DartObjectImpl leftResult = node.leftOperand.accept(this);
|
| + DartObjectImpl rightResult = node.rightOperand.accept(this);
|
| + TokenType operatorType = node.operator.type;
|
| + // 'null' is almost never good operand
|
| + if (operatorType != TokenType.BANG_EQ &&
|
| + operatorType != TokenType.EQ_EQ &&
|
| + operatorType != TokenType.QUESTION_QUESTION) {
|
| + if (leftResult != null && leftResult.isNull ||
|
| + rightResult != null && rightResult.isNull) {
|
| + _error(node, CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION);
|
| + return null;
|
| + }
|
| + }
|
| + // evaluate operator
|
| + if (operatorType == TokenType.AMPERSAND) {
|
| + return _dartObjectComputer.bitAnd(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.AMPERSAND_AMPERSAND) {
|
| + return _dartObjectComputer.logicalAnd(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.BANG_EQ) {
|
| + return _dartObjectComputer.notEqual(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.BAR) {
|
| + return _dartObjectComputer.bitOr(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.BAR_BAR) {
|
| + return _dartObjectComputer.logicalOr(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.CARET) {
|
| + return _dartObjectComputer.bitXor(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.EQ_EQ) {
|
| + return _dartObjectComputer.equalEqual(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.GT) {
|
| + return _dartObjectComputer.greaterThan(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.GT_EQ) {
|
| + return _dartObjectComputer.greaterThanOrEqual(
|
| + node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.GT_GT) {
|
| + return _dartObjectComputer.shiftRight(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.LT) {
|
| + return _dartObjectComputer.lessThan(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.LT_EQ) {
|
| + return _dartObjectComputer.lessThanOrEqual(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.LT_LT) {
|
| + return _dartObjectComputer.shiftLeft(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.MINUS) {
|
| + return _dartObjectComputer.minus(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.PERCENT) {
|
| + return _dartObjectComputer.remainder(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.PLUS) {
|
| + return _dartObjectComputer.add(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.STAR) {
|
| + return _dartObjectComputer.times(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.SLASH) {
|
| + return _dartObjectComputer.divide(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.TILDE_SLASH) {
|
| + return _dartObjectComputer.integerDivide(node, leftResult, rightResult);
|
| + } else if (operatorType == TokenType.QUESTION_QUESTION) {
|
| + return _dartObjectComputer.questionQuestion(
|
| + node, leftResult, rightResult);
|
| + } else {
|
| + // TODO(brianwilkerson) Figure out which error to report.
|
| + _error(node, null);
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitBooleanLiteral(BooleanLiteral node) =>
|
| + new DartObjectImpl(_typeProvider.boolType, BoolState.from(node.value));
|
| +
|
| + @override
|
| + DartObjectImpl visitConditionalExpression(ConditionalExpression node) {
|
| + Expression condition = node.condition;
|
| + DartObjectImpl conditionResult = condition.accept(this);
|
| + DartObjectImpl thenResult = node.thenExpression.accept(this);
|
| + DartObjectImpl elseResult = node.elseExpression.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.toBoolValue() == true) {
|
| + return thenResult;
|
| + } else if (conditionResult.toBoolValue() == false) {
|
| + return elseResult;
|
| + }
|
| + ParameterizedType thenType = thenResult.type;
|
| + ParameterizedType elseType = elseResult.type;
|
| + return new DartObjectImpl.validWithUnknownValue(
|
| + _typeSystem.getLeastUpperBound(_typeProvider, thenType, elseType)
|
| + as InterfaceType);
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitDoubleLiteral(DoubleLiteral node) =>
|
| + new DartObjectImpl(_typeProvider.doubleType, new DoubleState(node.value));
|
| +
|
| + @override
|
| + DartObjectImpl visitInstanceCreationExpression(
|
| + InstanceCreationExpression node) {
|
| + if (!node.isConst) {
|
| + // TODO(brianwilkerson) Figure out which error to report.
|
| + _error(node, null);
|
| + return null;
|
| + }
|
| + ConstructorElement constructor = node.staticElement;
|
| + if (constructor == null) {
|
| + // Couldn't resolve the constructor so we can't compute a value. No
|
| + // problem - the error has already been reported.
|
| + return null;
|
| + }
|
| +
|
| + return evaluationEngine.evaluateConstructorCall(
|
| + node, node.argumentList.arguments, constructor, this, _errorReporter);
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitIntegerLiteral(IntegerLiteral node) =>
|
| + new DartObjectImpl(_typeProvider.intType, new IntState(node.value));
|
| +
|
| + @override
|
| + DartObjectImpl visitInterpolationExpression(InterpolationExpression node) {
|
| + DartObjectImpl result = node.expression.accept(this);
|
| + if (result != null && !result.isBoolNumStringOrNull) {
|
| + _error(node, CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING);
|
| + return null;
|
| + }
|
| + return _dartObjectComputer.performToString(node, result);
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitInterpolationString(InterpolationString node) =>
|
| + new DartObjectImpl(_typeProvider.stringType, new StringState(node.value));
|
| +
|
| + @override
|
| + DartObjectImpl visitListLiteral(ListLiteral node) {
|
| + if (node.constKeyword == null) {
|
| + _errorReporter.reportErrorForNode(
|
| + CompileTimeErrorCode.MISSING_CONST_IN_LIST_LITERAL, node);
|
| + return null;
|
| + }
|
| + bool errorOccurred = false;
|
| + List<DartObjectImpl> elements = new List<DartObjectImpl>();
|
| + for (Expression element in node.elements) {
|
| + DartObjectImpl elementResult = element.accept(this);
|
| + if (elementResult == null) {
|
| + errorOccurred = true;
|
| + } else {
|
| + elements.add(elementResult);
|
| + }
|
| + }
|
| + if (errorOccurred) {
|
| + return null;
|
| + }
|
| + DartType elementType = _typeProvider.dynamicType;
|
| + NodeList<TypeName> typeArgs = node.typeArguments?.arguments;
|
| + if (typeArgs?.length == 1) {
|
| + DartType type = visitTypeName(typeArgs[0])?.toTypeValue();
|
| + if (type != null) {
|
| + elementType = type;
|
| + }
|
| + }
|
| + InterfaceType listType = _typeProvider.listType.instantiate([elementType]);
|
| + return new DartObjectImpl(listType, new ListState(elements));
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitMapLiteral(MapLiteral node) {
|
| + if (node.constKeyword == null) {
|
| + _errorReporter.reportErrorForNode(
|
| + CompileTimeErrorCode.MISSING_CONST_IN_MAP_LITERAL, node);
|
| + return null;
|
| + }
|
| + bool errorOccurred = false;
|
| + LinkedHashMap<DartObjectImpl, DartObjectImpl> map =
|
| + new LinkedHashMap<DartObjectImpl, DartObjectImpl>();
|
| + for (MapLiteralEntry entry in node.entries) {
|
| + DartObjectImpl keyResult = entry.key.accept(this);
|
| + DartObjectImpl valueResult = entry.value.accept(this);
|
| + if (keyResult == null || valueResult == null) {
|
| + errorOccurred = true;
|
| + } else {
|
| + map[keyResult] = valueResult;
|
| + }
|
| + }
|
| + if (errorOccurred) {
|
| + return null;
|
| + }
|
| + DartType keyType = _typeProvider.dynamicType;
|
| + DartType valueType = _typeProvider.dynamicType;
|
| + NodeList<TypeName> typeArgs = node.typeArguments?.arguments;
|
| + if (typeArgs?.length == 2) {
|
| + DartType keyTypeCandidate = visitTypeName(typeArgs[0])?.toTypeValue();
|
| + if (keyTypeCandidate != null) {
|
| + keyType = keyTypeCandidate;
|
| + }
|
| + DartType valueTypeCandidate = visitTypeName(typeArgs[1])?.toTypeValue();
|
| + if (valueTypeCandidate != null) {
|
| + valueType = valueTypeCandidate;
|
| + }
|
| + }
|
| + InterfaceType mapType =
|
| + _typeProvider.mapType.instantiate([keyType, valueType]);
|
| + return new DartObjectImpl(mapType, new MapState(map));
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitMethodInvocation(MethodInvocation node) {
|
| + Element element = node.methodName.staticElement;
|
| + if (element is FunctionElement) {
|
| + if (element.name == "identical") {
|
| + NodeList<Expression> arguments = node.argumentList.arguments;
|
| + if (arguments.length == 2) {
|
| + Element enclosingElement = element.enclosingElement;
|
| + if (enclosingElement is CompilationUnitElement) {
|
| + LibraryElement library = enclosingElement.library;
|
| + if (library.isDartCore) {
|
| + DartObjectImpl leftArgument = arguments[0].accept(this);
|
| + DartObjectImpl rightArgument = arguments[1].accept(this);
|
| + return _dartObjectComputer.isIdentical(
|
| + node, leftArgument, rightArgument);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| + // TODO(brianwilkerson) Figure out which error to report.
|
| + _error(node, null);
|
| + return null;
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitNamedExpression(NamedExpression node) =>
|
| + node.expression.accept(this);
|
| +
|
| + @override
|
| + DartObjectImpl visitNode(AstNode node) {
|
| + // TODO(brianwilkerson) Figure out which error to report.
|
| + _error(node, null);
|
| + return null;
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitNullLiteral(NullLiteral node) => _typeProvider.nullObject;
|
| +
|
| + @override
|
| + DartObjectImpl visitParenthesizedExpression(ParenthesizedExpression node) =>
|
| + node.expression.accept(this);
|
| +
|
| + @override
|
| + DartObjectImpl visitPrefixedIdentifier(PrefixedIdentifier node) {
|
| + SimpleIdentifier prefixNode = node.prefix;
|
| + Element prefixElement = prefixNode.staticElement;
|
| + // String.length
|
| + if (prefixElement is! PrefixElement && prefixElement is! ClassElement) {
|
| + DartObjectImpl prefixResult = node.prefix.accept(this);
|
| + if (_isStringLength(prefixResult, node.identifier)) {
|
| + return prefixResult.stringLength(_typeProvider);
|
| + }
|
| + }
|
| + // importPrefix.CONST
|
| + if (prefixElement is! PrefixElement) {
|
| + DartObjectImpl prefixResult = prefixNode.accept(this);
|
| + if (prefixResult == null) {
|
| + // The error has already been reported.
|
| + return null;
|
| + }
|
| + }
|
| + // validate prefixed identifier
|
| + return _getConstantValue(node, node.staticElement);
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitPrefixExpression(PrefixExpression node) {
|
| + DartObjectImpl operand = node.operand.accept(this);
|
| + if (operand != null && operand.isNull) {
|
| + _error(node, CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION);
|
| + return null;
|
| + }
|
| + if (node.operator.type == TokenType.BANG) {
|
| + return _dartObjectComputer.logicalNot(node, operand);
|
| + } else if (node.operator.type == TokenType.TILDE) {
|
| + return _dartObjectComputer.bitNot(node, operand);
|
| + } else if (node.operator.type == TokenType.MINUS) {
|
| + return _dartObjectComputer.negated(node, operand);
|
| + } else {
|
| + // TODO(brianwilkerson) Figure out which error to report.
|
| + _error(node, null);
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitPropertyAccess(PropertyAccess node) {
|
| + if (node.target != null) {
|
| + DartObjectImpl prefixResult = node.target.accept(this);
|
| + if (_isStringLength(prefixResult, node.propertyName)) {
|
| + return prefixResult.stringLength(_typeProvider);
|
| + }
|
| + }
|
| + return _getConstantValue(node, node.propertyName.staticElement);
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitSimpleIdentifier(SimpleIdentifier node) {
|
| + if (_lexicalEnvironment != null &&
|
| + _lexicalEnvironment.containsKey(node.name)) {
|
| + return _lexicalEnvironment[node.name];
|
| + }
|
| + return _getConstantValue(node, node.staticElement);
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitSimpleStringLiteral(SimpleStringLiteral node) =>
|
| + new DartObjectImpl(_typeProvider.stringType, new StringState(node.value));
|
| +
|
| + @override
|
| + DartObjectImpl visitStringInterpolation(StringInterpolation node) {
|
| + DartObjectImpl result = null;
|
| + bool first = true;
|
| + for (InterpolationElement element in node.elements) {
|
| + if (first) {
|
| + result = element.accept(this);
|
| + first = false;
|
| + } else {
|
| + result =
|
| + _dartObjectComputer.concatenate(node, result, element.accept(this));
|
| + }
|
| + }
|
| + return result;
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitSymbolLiteral(SymbolLiteral node) {
|
| + StringBuffer buffer = new StringBuffer();
|
| + List<Token> components = node.components;
|
| + for (int i = 0; i < components.length; i++) {
|
| + if (i > 0) {
|
| + buffer.writeCharCode(0x2E);
|
| + }
|
| + buffer.write(components[i].lexeme);
|
| + }
|
| + return new DartObjectImpl(
|
| + _typeProvider.symbolType, new SymbolState(buffer.toString()));
|
| + }
|
| +
|
| + @override
|
| + DartObjectImpl visitTypeName(TypeName node) {
|
| + DartType type = evaluateType(node.type);
|
| + if (type == null) {
|
| + return super.visitTypeName(node);
|
| + }
|
| + return typeConstant(type);
|
| + }
|
| +
|
| + /**
|
| + * Create an error associated with the given [node]. The error will have the
|
| + * given error [code].
|
| + */
|
| + void _error(AstNode node, ErrorCode code) {
|
| + _errorReporter.reportErrorForNode(
|
| + code ?? CompileTimeErrorCode.INVALID_CONSTANT, node);
|
| + }
|
| +
|
| + /**
|
| + * Return the constant value of the static constant represented by the given
|
| + * [element]. The [node] is the node to be used if an error needs to be
|
| + * reported.
|
| + */
|
| + DartObjectImpl _getConstantValue(AstNode node, Element element) {
|
| + Element variableElement =
|
| + element is PropertyAccessorElement ? element.variable : element;
|
| + if (variableElement is VariableElementImpl) {
|
| + evaluationEngine.validator.beforeGetEvaluationResult(variableElement);
|
| + EvaluationResultImpl value = variableElement.evaluationResult;
|
| + if (variableElement.isConst && value != null) {
|
| + return value.value;
|
| + }
|
| + } else if (variableElement is ExecutableElement) {
|
| + ExecutableElement function = element;
|
| + if (function.isStatic) {
|
| + ParameterizedType functionType = function.type;
|
| + if (functionType == null) {
|
| + functionType = _typeProvider.functionType;
|
| + }
|
| + return new DartObjectImpl(functionType, new FunctionState(function));
|
| + }
|
| + } else if (variableElement is TypeDefiningElement) {
|
| + // Constants may only refer to type parameters in strong mode.
|
| + if (evaluationEngine.strongMode ||
|
| + variableElement is! TypeParameterElement) {
|
| + return new DartObjectImpl(
|
| + _typeProvider.typeType, new TypeState(variableElement.type));
|
| + }
|
| + }
|
| + // TODO(brianwilkerson) Figure out which error to report.
|
| + _error(node, null);
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Return `true` if the given [targetResult] represents a string and the
|
| + * [identifier] is "length".
|
| + */
|
| + bool _isStringLength(
|
| + DartObjectImpl targetResult, SimpleIdentifier identifier) {
|
| + if (targetResult == null || targetResult.type != _typeProvider.stringType) {
|
| + return false;
|
| + }
|
| + return identifier.name == 'length';
|
| + }
|
| +
|
| + /**
|
| + * Return the value of the given [expression], or a representation of 'null'
|
| + * if the expression cannot be evaluated.
|
| + */
|
| + DartObjectImpl _valueOf(Expression expression) {
|
| + DartObjectImpl expressionValue = expression.accept(this);
|
| + if (expressionValue != null) {
|
| + return expressionValue;
|
| + }
|
| + return _typeProvider.nullObject;
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A utility class that contains methods for manipulating instances of a Dart
|
| + * class and for collecting errors during evaluation.
|
| + */
|
| +class DartObjectComputer {
|
| + /**
|
| + * The error reporter that we are using to collect errors.
|
| + */
|
| + final ErrorReporter _errorReporter;
|
| +
|
| + /**
|
| + * The type provider used to create objects of the appropriate types, and to
|
| + * identify when an object is of a built-in type.
|
| + */
|
| + final TypeProvider _typeProvider;
|
| +
|
| + DartObjectComputer(this._errorReporter, this._typeProvider);
|
| +
|
| + DartObjectImpl add(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.add(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + return null;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Return the result of applying boolean conversion to the [evaluationResult].
|
| + * The [node] is the node against which errors should be reported.
|
| + */
|
| + DartObjectImpl applyBooleanConversion(
|
| + AstNode node, DartObjectImpl evaluationResult) {
|
| + if (evaluationResult != null) {
|
| + try {
|
| + return evaluationResult.convertToBool(_typeProvider);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl bitAnd(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.bitAnd(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl bitNot(Expression node, DartObjectImpl evaluationResult) {
|
| + if (evaluationResult != null) {
|
| + try {
|
| + return evaluationResult.bitNot(_typeProvider);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl bitOr(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.bitOr(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl bitXor(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.bitXor(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl concatenate(Expression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.concatenate(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl divide(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.divide(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl equalEqual(Expression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.equalEqual(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl greaterThan(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.greaterThan(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl greaterThanOrEqual(BinaryExpression node,
|
| + DartObjectImpl leftOperand, DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.greaterThanOrEqual(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl integerDivide(BinaryExpression node,
|
| + DartObjectImpl leftOperand, DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.integerDivide(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl isIdentical(Expression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.isIdentical(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl lessThan(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.lessThan(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl lessThanOrEqual(BinaryExpression node,
|
| + DartObjectImpl leftOperand, DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.lessThanOrEqual(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl logicalAnd(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.logicalAnd(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl logicalNot(Expression node, DartObjectImpl evaluationResult) {
|
| + if (evaluationResult != null) {
|
| + try {
|
| + return evaluationResult.logicalNot(_typeProvider);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl logicalOr(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.logicalOr(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl minus(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.minus(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl negated(Expression node, DartObjectImpl evaluationResult) {
|
| + if (evaluationResult != null) {
|
| + try {
|
| + return evaluationResult.negated(_typeProvider);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl notEqual(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.notEqual(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl performToString(
|
| + AstNode node, DartObjectImpl evaluationResult) {
|
| + if (evaluationResult != null) {
|
| + try {
|
| + return evaluationResult.performToString(_typeProvider);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl questionQuestion(Expression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + if (leftOperand.isNull) {
|
| + return rightOperand;
|
| + }
|
| + return leftOperand;
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl remainder(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.remainder(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl shiftLeft(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.shiftLeft(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + DartObjectImpl shiftRight(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.shiftRight(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Return the result of invoking the 'length' getter on the
|
| + * [evaluationResult]. The [node] is the node against which errors should be
|
| + * reported.
|
| + */
|
| + EvaluationResultImpl stringLength(
|
| + Expression node, EvaluationResultImpl evaluationResult) {
|
| + if (evaluationResult.value != null) {
|
| + try {
|
| + return new EvaluationResultImpl(
|
| + evaluationResult.value.stringLength(_typeProvider));
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return new EvaluationResultImpl(null);
|
| + }
|
| +
|
| + DartObjectImpl times(BinaryExpression node, DartObjectImpl leftOperand,
|
| + DartObjectImpl rightOperand) {
|
| + if (leftOperand != null && rightOperand != null) {
|
| + try {
|
| + return leftOperand.times(_typeProvider, rightOperand);
|
| + } on EvaluationException catch (exception) {
|
| + _errorReporter.reportErrorForNode(exception.errorCode, node);
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * The result of attempting to evaluate an expression.
|
| + */
|
| +class EvaluationResult {
|
| + // TODO(brianwilkerson) Merge with EvaluationResultImpl
|
| + /**
|
| + * The value of the expression.
|
| + */
|
| + final DartObject value;
|
| +
|
| + /**
|
| + * The errors that should be reported for the expression(s) that were
|
| + * evaluated.
|
| + */
|
| + final List<AnalysisError> _errors;
|
| +
|
| + /**
|
| + * Initialize a newly created result object with the given [value] and set of
|
| + * [_errors]. Clients should use one of the factory methods: [forErrors] and
|
| + * [forValue].
|
| + */
|
| + EvaluationResult(this.value, this._errors);
|
| +
|
| + /**
|
| + * Return a list containing the errors that should be reported for the
|
| + * expression(s) that were evaluated. If there are no such errors, the list
|
| + * will be empty. The list can be empty even if the expression is not a valid
|
| + * compile time constant if the errors would have been reported by other parts
|
| + * of the analysis engine.
|
| + */
|
| + List<AnalysisError> get errors => _errors ?? AnalysisError.NO_ERRORS;
|
| +
|
| + /**
|
| + * Return `true` if the expression is a compile-time constant expression that
|
| + * would not throw an exception when evaluated.
|
| + */
|
| + bool get isValid => _errors == null;
|
| +
|
| + /**
|
| + * Return an evaluation result representing the result of evaluating an
|
| + * expression that is not a compile-time constant because of the given
|
| + * [errors].
|
| + */
|
| + static EvaluationResult forErrors(List<AnalysisError> errors) =>
|
| + new EvaluationResult(null, errors);
|
| +
|
| + /**
|
| + * Return an evaluation result representing the result of evaluating an
|
| + * expression that is a compile-time constant that evaluates to the given
|
| + * [value].
|
| + */
|
| + static EvaluationResult forValue(DartObject value) =>
|
| + new EvaluationResult(value, null);
|
| +}
|
| +
|
| +/**
|
| + * The result of attempting to evaluate a expression.
|
| + */
|
| +class EvaluationResultImpl {
|
| + /**
|
| + * The errors encountered while trying to evaluate the compile time constant.
|
| + * These errors may or may not have prevented the expression from being a
|
| + * valid compile time constant.
|
| + */
|
| + List<AnalysisError> _errors;
|
| +
|
| + /**
|
| + * The value of the expression, or `null` if the value couldn't be computed
|
| + * due to errors.
|
| + */
|
| + final DartObjectImpl value;
|
| +
|
| + EvaluationResultImpl(this.value, [List<AnalysisError> errors]) {
|
| + this._errors = errors ?? <AnalysisError>[];
|
| + }
|
| +
|
| + List<AnalysisError> get errors => _errors;
|
| +
|
| + bool equalValues(TypeProvider typeProvider, EvaluationResultImpl result) {
|
| + if (this.value != null) {
|
| + if (result.value == null) {
|
| + return false;
|
| + }
|
| + return value == result.value;
|
| + } else {
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + @override
|
| + String toString() {
|
| + if (value == null) {
|
| + return "error";
|
| + }
|
| + return value.toString();
|
| + }
|
| +}
|
|
|