| // 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/standard_ast_factory.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(typeProvider); |
| |
| /** |
| * 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; |
| if (initializers != null) { |
| 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, |
| {ConstructorInvocation invocation}) { |
| 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<DartObjectImpl> positionalArguments = <DartObjectImpl>[]; |
| 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 { |
| var argumentValue = constantVisitor._valueOf(argument); |
| argumentValues[i] = argumentValue; |
| positionalArguments.add(argumentValue); |
| argumentNodes[i] = argument; |
| } |
| } |
| if (invocation == null) { |
| invocation = new ConstructorInvocation( |
| constructor, positionalArguments, namedArgumentValues); |
| } |
| 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, |
| invocation: invocation); |
| } |
| } |
| } |
| // 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 = astFactory.nodeList/*<Expression>*/(null); |
| } |
| |
| evaluateSuperConstructorCall(node, fieldMap, superConstructor, |
| superArguments, initializerVisitor, errorReporter); |
| } |
| } |
| return new DartObjectImpl( |
| definingClass, new GenericState(fieldMap, invocation: invocation)); |
| } |
| |
| 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(thenType, elseType) as ParameterizedType); |
| } |
| |
| @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<TypeAnnotation> typeArgs = node.typeArguments?.arguments; |
| if (typeArgs?.length == 1) { |
| DartType type = visitTypeAnnotation(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<TypeAnnotation> typeArgs = node.typeArguments?.arguments; |
| if (typeArgs?.length == 2) { |
| DartType keyTypeCandidate = |
| visitTypeAnnotation(typeArgs[0])?.toTypeValue(); |
| if (keyTypeCandidate != null) { |
| keyType = keyTypeCandidate; |
| } |
| DartType valueTypeCandidate = |
| visitTypeAnnotation(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())); |
| } |
| |
| DartObjectImpl visitTypeAnnotation(TypeAnnotation node) { |
| DartType type = evaluateType(node.type); |
| if (type == null) { |
| return super.visitTypeName(node); |
| } |
| return typeConstant(type); |
| } |
| |
| @override |
| DartObjectImpl visitTypeName(TypeName node) => visitTypeAnnotation(node); |
| |
| /** |
| * 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(); |
| } |
| } |