| // 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. |
| |
| import 'dart:collection'; |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/nullability_suffix.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/dart/element/type_provider.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/src/dart/ast/ast_factory.dart'; |
| import 'package:analyzer/src/dart/constant/from_environment_evaluator.dart'; |
| import 'package:analyzer/src/dart/constant/has_type_parameter_reference.dart'; |
| import 'package:analyzer/src/dart/constant/potentially_constant.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/member.dart'; |
| import 'package:analyzer/src/dart/element/type_algebra.dart'; |
| import 'package:analyzer/src/dart/element/type_system.dart' show TypeSystemImpl; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/generated/constant.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/task/api/model.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 const String _DEFAULT_VALUE_PARAM = "defaultValue"; |
| |
| /// Source of RegExp matching declarable operator names. |
| /// From sdk/lib/internal/symbol.dart. |
| static const String _OPERATOR_RE = |
| "(?:[\\-+*/%&|^]|\\[\\]=?|==|~/?|<[<=]?|>[>=]?|unary-)"; |
| |
| /// Source of RegExp matching Dart reserved words. |
| /// From sdk/lib/internal/symbol.dart. |
| static const 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))"; |
| |
| /// Source of RegExp matching any public identifier. |
| /// From sdk/lib/internal/symbol.dart. |
| static const String _PUBLIC_IDENTIFIER_RE = |
| "(?!$_RESERVED_WORD_RE\\b(?!\\\$))[a-zA-Z\$][\\w\$]*"; |
| |
| /// RegExp that validates a non-empty non-private symbol. |
| /// From sdk/lib/internal/symbol.dart. |
| static final RegExp _PUBLIC_SYMBOL_PATTERN = RegExp( |
| "^(?:$_OPERATOR_RE\$|$_PUBLIC_IDENTIFIER_RE(?:=?\$|[.](?!\$)))+?\$"); |
| |
| /// The set of variables declared on the command line using '-D'. |
| final DeclaredVariables _declaredVariables; |
| |
| /// Whether the `triple_shift` experiment is enabled. |
| final bool _isTripleShiftExperimentEnabled; |
| |
| /// Initialize a newly created [ConstantEvaluationEngine]. |
| /// |
| /// [declaredVariables] is the set of variables declared on the command |
| /// line using '-D'. |
| ConstantEvaluationEngine( |
| DeclaredVariables declaredVariables, this._isTripleShiftExperimentEnabled) |
| : _declaredVariables = declaredVariables; |
| |
| /// 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( |
| LibraryElementImpl library, |
| List<Expression> arguments, |
| List<DartObjectImpl?> argumentValues, |
| Map<String, DartObjectImpl> namedArgumentValues, |
| InterfaceType expectedDefaultValueType) { |
| int argumentCount = arguments.length; |
| if (argumentCount < 1 || argumentCount > 2) { |
| return false; |
| } |
| if (arguments[0] is NamedExpression) { |
| return false; |
| } |
| if (argumentValues[0]!.type != library.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 (!(defaultValueType == expectedDefaultValueType || |
| defaultValueType == library.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( |
| LibraryElementImpl library, |
| List<Expression> arguments, |
| List<DartObjectImpl?> argumentValues, |
| Map<String, DartObjectImpl> namedArgumentValues) { |
| if (arguments.length != 1) { |
| return false; |
| } |
| if (arguments[0] is NamedExpression) { |
| return false; |
| } |
| if (argumentValues[0]!.type != library.typeProvider.stringType) { |
| return false; |
| } |
| var name = argumentValues[0]?.toStringValue(); |
| if (name == null) { |
| return false; |
| } |
| // TODO(srawlins): If the argument is '>>>' but triple-shift is not enabled, |
| // report a different error indicating that the triple-shift experiment |
| // should be enabled, or a minimum SDK version set, when one is declared. |
| return isValidPublicSymbol(name) || |
| (_isTripleShiftExperimentEnabled && name == '>>>'); |
| } |
| |
| /// Compute the constant value associated with the given [constant]. |
| void computeConstantValue(ConstantEvaluationTarget constant) { |
| if (constant is Element) { |
| var element = constant as Element; |
| constant = element.declaration as ConstantEvaluationTarget; |
| } |
| |
| var library = constant.library as LibraryElementImpl; |
| if (constant is ParameterElementImpl) { |
| if (constant.isOptional) { |
| var defaultValue = constant.constantInitializer; |
| if (defaultValue != null) { |
| RecordingErrorListener errorListener = RecordingErrorListener(); |
| ErrorReporter errorReporter = ErrorReporter( |
| errorListener, |
| constant.source!, |
| isNonNullableByDefault: library.isNonNullableByDefault, |
| ); |
| var dartObject = defaultValue |
| .accept(ConstantVisitor(this, library, errorReporter)); |
| constant.evaluationResult = |
| EvaluationResultImpl(dartObject, errorListener.errors); |
| } else { |
| constant.evaluationResult = EvaluationResultImpl( |
| _nullObject(library), |
| ); |
| } |
| } |
| } else if (constant is VariableElementImpl) { |
| var constantInitializer = constant.constantInitializer; |
| if (constantInitializer != null) { |
| RecordingErrorListener errorListener = RecordingErrorListener(); |
| ErrorReporter errorReporter = ErrorReporter( |
| errorListener, |
| constant.source!, |
| isNonNullableByDefault: library.isNonNullableByDefault, |
| ); |
| var dartObject = constantInitializer |
| .accept(ConstantVisitor(this, library, 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(library, dartObject, constant.type)) { |
| // TODO(brianwilkerson) This should not be reported if |
| // CompileTimeErrorCode.INVALID_ASSIGNMENT has already been |
| // reported (that is, if the static types are also wrong). |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.VARIABLE_TYPE_MISMATCH, |
| constantInitializer, |
| [dartObject.type, constant.type]); |
| } |
| } |
| constant.evaluationResult = |
| EvaluationResultImpl(dartObject, errorListener.errors); |
| } |
| } else if (constant is ConstructorElementImpl) { |
| 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. |
| constant.isConstantEvaluated = true; |
| } |
| } else if (constant is ElementAnnotationImpl) { |
| var constNode = constant.annotationAst; |
| var element = constant.element; |
| if (element is PropertyAccessorElement) { |
| // The annotation is a reference to a compile-time constant variable. |
| // Just copy the evaluation result. |
| VariableElementImpl variableElement = |
| element.variable.declaration 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 = EvaluationResultImpl(null); |
| } |
| } else if (element is ConstructorElement && |
| element.isConst && |
| constNode.arguments != null) { |
| RecordingErrorListener errorListener = RecordingErrorListener(); |
| ErrorReporter errorReporter = ErrorReporter( |
| errorListener, |
| constant.source, |
| isNonNullableByDefault: library.isNonNullableByDefault, |
| ); |
| ConstantVisitor constantVisitor = |
| ConstantVisitor(this, library, errorReporter); |
| var result = evaluateConstructorCall( |
| library, |
| constNode, |
| constNode.arguments!.arguments, |
| element, |
| constantVisitor, |
| errorReporter); |
| constant.evaluationResult = |
| 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 = 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.instrumentationService |
| .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 = ReferenceFinder(callback); |
| if (constant is ConstructorElement) { |
| constant = constant.declaration; |
| } |
| if (constant is VariableElement) { |
| var declaration = constant.declaration as VariableElementImpl; |
| var initializer = declaration.constantInitializer; |
| if (initializer != null) { |
| initializer.accept(referenceFinder); |
| } |
| } else if (constant is ConstructorElementImpl) { |
| if (constant.isConst) { |
| var redirectedConstructor = getConstRedirectedConstructor(constant); |
| if (redirectedConstructor != null) { |
| var redirectedConstructorBase = redirectedConstructor.declaration; |
| 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; |
| var 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. |
| var superclass = constant.returnType.superclass; |
| if (superclass != null && !superclass.isDartCoreObject) { |
| var unnamedConstructor = |
| superclass.element.unnamedConstructor?.declaration; |
| 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.hasInitializer) { |
| callback(field); |
| } |
| } |
| for (ParameterElement parameterElement in constant.parameters) { |
| callback(parameterElement); |
| } |
| } |
| } else if (constant is ElementAnnotationImpl) { |
| Annotation constNode = constant.annotationAst; |
| var element = constant.element; |
| if (element is PropertyAccessorElement) { |
| // The annotation is a reference to a compile-time constant variable, |
| // so it depends on the variable. |
| callback(element.variable.declaration); |
| } else if (element is ConstructorElement) { |
| // The annotation is a constructor invocation, so it depends on the |
| // constructor. |
| callback(element.declaration); |
| } else { |
| // This could happen in the event of invalid code. The error will be |
| // reported at constant evaluation time. |
| } |
| 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.instrumentationService |
| .logError("Constant value computer trying to compute " |
| "the value of a node of type ${constant.runtimeType}"); |
| } |
| } |
| |
| DartObjectImpl? evaluateConstructorCall( |
| LibraryElementImpl library, |
| AstNode node, |
| List<Expression> arguments, |
| ConstructorElement constructor, |
| ConstantVisitor constantVisitor, |
| ErrorReporter errorReporter, |
| {ConstructorInvocation? invocation}) { |
| if (!constructor.isConst) { |
| if (node is InstanceCreationExpression && node.keyword != null) { |
| errorReporter.reportErrorForToken( |
| CompileTimeErrorCode.CONST_WITH_NON_CONST, node.keyword!); |
| } else { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_WITH_NON_CONST, node); |
| } |
| return null; |
| } |
| |
| if (!(constructor.declaration as ConstructorElementImpl).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 DartObjectImpl.validWithUnknownValue( |
| library.typeSystem, |
| constructor.returnType, |
| ); |
| } |
| |
| int argumentCount = arguments.length; |
| var argumentValues = List<DartObjectImpl?>.filled(argumentCount, null); |
| Map<String, NamedExpression>? namedNodes; |
| Map<String, DartObjectImpl>? namedValues; |
| for (int i = 0; i < argumentCount; i++) { |
| Expression argument = arguments[i]; |
| if (argument is NamedExpression) { |
| namedNodes ??= HashMap<String, NamedExpression>(); |
| namedValues ??= HashMap<String, DartObjectImpl>(); |
| String name = argument.name.label.name; |
| namedNodes[name] = argument; |
| namedValues[name] = constantVisitor._valueOf(argument.expression); |
| } else { |
| var argumentValue = constantVisitor._valueOf(argument); |
| argumentValues[i] = argumentValue; |
| } |
| } |
| namedNodes ??= const {}; |
| namedValues ??= const {}; |
| |
| invocation ??= ConstructorInvocation( |
| constructor, |
| argumentValues, |
| namedValues, |
| ); |
| |
| constructor = followConstantRedirectionChain(constructor); |
| InterfaceType definingType = constructor.returnType; |
| 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. |
| ClassElement definingClass = constructor.enclosingElement; |
| if (constructor.name == "fromEnvironment") { |
| if (!checkFromEnvironmentArguments( |
| library, arguments, argumentValues, namedValues, definingType)) { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node); |
| return null; |
| } |
| String? variableName = |
| argumentCount < 1 ? null : argumentValues[0]?.toStringValue(); |
| if (definingClass == library.typeProvider.boolElement) { |
| return FromEnvironmentEvaluator( |
| library.typeSystem, |
| _declaredVariables, |
| ).getBool2(variableName, namedValues, constructor); |
| } else if (definingClass == library.typeProvider.intElement) { |
| return FromEnvironmentEvaluator( |
| library.typeSystem, |
| _declaredVariables, |
| ).getInt2(variableName, namedValues, constructor); |
| } else if (definingClass == library.typeProvider.stringElement) { |
| return FromEnvironmentEvaluator( |
| library.typeSystem, |
| _declaredVariables, |
| ).getString2(variableName, namedValues, constructor); |
| } |
| } else if (constructor.name == 'hasEnvironment' && |
| definingClass == library.typeProvider.boolElement) { |
| var name = |
| argumentCount < 1 ? null : argumentValues[0]?.toStringValue(); |
| return FromEnvironmentEvaluator( |
| library.typeSystem, |
| _declaredVariables, |
| ).hasEnvironment(name); |
| } else if (constructor.name == "" && |
| definingClass == library.typeProvider.symbolElement && |
| argumentCount == 1) { |
| if (!checkSymbolArguments( |
| library, arguments, argumentValues, namedValues)) { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node); |
| return null; |
| } |
| var argumentValue = argumentValues[0]?.toStringValue(); |
| return DartObjectImpl( |
| library.typeSystem, |
| definingType, |
| 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 DartObjectImpl.validWithUnknownValue( |
| library.typeSystem, |
| definingType, |
| ); |
| } |
| |
| var fieldMap = HashMap<String, DartObjectImpl>(); |
| |
| // The errors reported while computing values for field initializers, or |
| // default values for the constructor parameters, cannot be reported |
| // into the current ErrorReporter, because they usually happen in a |
| // different source. But they still should cause a constant evaluation |
| // error for the current node. |
| var externalErrorListener = BooleanErrorListener(); |
| var externalErrorReporter = ErrorReporter( |
| externalErrorListener, |
| constructor.source, |
| isNonNullableByDefault: library.isNonNullableByDefault, |
| ); |
| |
| // 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) { |
| var 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 (!runtimeTypeMatch(library, fieldValue, fieldType)) { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH, |
| node, |
| [fieldValue.type, field.name, fieldType]); |
| } |
| fieldMap[field.name] = fieldValue; |
| } |
| } |
| // Now evaluate the constructor declaration. |
| Map<String, DartObjectImpl> parameterMap = |
| 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.declaration; |
| DartObjectImpl? argumentValue; |
| AstNode? errorTarget; |
| if (baseParameter.isNamed) { |
| argumentValue = namedValues[baseParameter.name]; |
| errorTarget = namedNodes[baseParameter.name]; |
| } else if (i < argumentCount) { |
| argumentValue = argumentValues[i]; |
| errorTarget = arguments[i]; |
| } |
| // 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. |
| var evaluationResult = baseParameter.evaluationResult; |
| if (evaluationResult == null) { |
| // No default was provided, so the default value is null. |
| argumentValue = _nullObject(library); |
| } else if (evaluationResult.value != null) { |
| argumentValue = evaluationResult.value; |
| } |
| } |
| if (argumentValue != null) { |
| if (!runtimeTypeMatch(library, argumentValue, parameter.type)) { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH, |
| errorTarget, |
| [argumentValue.type, parameter.type]); |
| } |
| if (baseParameter.isInitializingFormal) { |
| var 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(library, argumentValue, fieldType)) { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.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 = ConstantVisitor( |
| this, |
| constructor.library as LibraryElementImpl, |
| externalErrorReporter, |
| lexicalEnvironment: parameterMap, |
| substitution: Substitution.fromInterfaceType(definingType), |
| ); |
| var constructorBase = constructor.declaration as ConstructorElementImpl; |
| var initializers = constructorBase.constantInitializers; |
| String? superName; |
| NodeList<Expression>? superArguments; |
| for (var i = 0; i < initializers.length; i++) { |
| var initializer = initializers[i]; |
| if (initializer is ConstructorFieldInitializer) { |
| Expression initializerExpression = initializer.expression; |
| var 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; |
| var getter = definingType.getGetter(fieldName); |
| if (getter != null) { |
| PropertyInducingElement field = getter.variable; |
| if (!runtimeTypeMatch(library, evaluationResult, field.type)) { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH, |
| node, |
| [evaluationResult.type, fieldName, field.type]); |
| } |
| } |
| } else { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node); |
| } |
| } else if (initializer is SuperConstructorInvocation) { |
| var 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. |
| var constructor = initializer.staticElement; |
| if (constructor != null && constructor.isConst) { |
| // Instantiate the constructor with the in-scope type arguments. |
| constructor = ConstructorMember.from(constructor, definingType); |
| |
| var result = evaluateConstructorCall( |
| library, |
| node, |
| initializer.argumentList.arguments, |
| constructor, |
| initializerVisitor, |
| externalErrorReporter, |
| invocation: invocation); |
| if (externalErrorListener.errorReported) { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node); |
| } |
| return result; |
| } |
| } else if (initializer is AssertInitializer) { |
| var condition = initializer.condition; |
| var evaluationResult = condition.accept(initializerVisitor); |
| if (evaluationResult == null || |
| !evaluationResult.isBool || |
| evaluationResult.toBoolValue() == false) { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node); |
| return null; |
| } |
| } |
| } |
| // Evaluate explicit or implicit call to super(). |
| var superclass = definingType.superclass; |
| if (superclass != null && !superclass.isDartCoreObject) { |
| var superConstructor = |
| superclass.lookUpConstructor(superName, constructor.library); |
| if (superConstructor != null) { |
| superArguments ??= astFactory.nodeList<Expression>(node); |
| |
| if (constructor is ConstructorMember && constructor.isLegacy) { |
| superConstructor = |
| Member.legacy(superConstructor) as ConstructorElement; |
| } |
| |
| evaluateSuperConstructorCall(library, node, fieldMap, superConstructor, |
| superArguments, initializerVisitor, externalErrorReporter); |
| } |
| } |
| if (externalErrorListener.errorReported) { |
| errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, node); |
| } |
| return DartObjectImpl( |
| library.typeSystem, |
| definingType, |
| GenericState(fieldMap, invocation: invocation), |
| ); |
| } |
| |
| void evaluateSuperConstructorCall( |
| LibraryElementImpl library, |
| AstNode node, |
| Map<String, DartObjectImpl> fieldMap, |
| ConstructorElement? superConstructor, |
| List<Expression> superArguments, |
| ConstantVisitor initializerVisitor, |
| ErrorReporter errorReporter) { |
| if (superConstructor != null && superConstructor.isConst) { |
| var evaluationResult = evaluateConstructorCall(library, 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) { |
| var constructorsVisited = <ConstructorElement>{}; |
| while (true) { |
| var redirectedConstructor = getConstRedirectedConstructor(constructor); |
| if (redirectedConstructor == null) { |
| break; |
| } else { |
| var constructorBase = constructor.declaration; |
| constructorsVisited.add(constructorBase); |
| var redirectedConstructorBase = redirectedConstructor.declaration; |
| 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 = RecordingErrorListener(); |
| ErrorReporter errorReporter = ErrorReporter( |
| errorListener, |
| constant.source!, |
| isNonNullableByDefault: constant.library!.isNonNullableByDefault, |
| ); |
| // 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 = |
| 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.instrumentationService |
| .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; |
| } |
| var typeProvider = constructor.library.typeProvider; |
| if (constructor.enclosingElement == typeProvider.symbolElement) { |
| // 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; |
| } |
| var 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( |
| LibraryElementImpl library, |
| DartObjectImpl obj, |
| DartType type, |
| ) { |
| var typeSystem = library.typeSystem; |
| if (!typeSystem.isNonNullableByDefault) { |
| type = typeSystem.toLegacyType(type); |
| } |
| var objType = obj.type; |
| return typeSystem.isSubtypeOf(objType, type); |
| } |
| |
| DartObjectImpl _nullObject(LibraryElementImpl library) { |
| return DartObjectImpl( |
| library.typeSystem, |
| library.typeProvider.nullType, |
| NullState.NULL_STATE, |
| ); |
| } |
| |
| /// 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 for [AnalysisTarget]s for which constant evaluation can be |
| /// performed. |
| abstract class ConstantEvaluationTarget extends AnalysisTarget { |
| /// Return the [AnalysisContext] which should be used to evaluate this |
| /// constant. |
| AnalysisContext get context; |
| |
| /// Return whether this constant is evaluated. |
| bool get isConstantEvaluated; |
| |
| /// The library with this constant. |
| LibraryElement? get library; |
| } |
| |
| /// 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) {} |
| } |
| |
| /// A visitor used to evaluate constant expressions to produce their |
| /// compile-time value. |
| class ConstantVisitor extends UnifyingAstVisitor<DartObjectImpl> { |
| /// The evaluation engine used to access the feature set, type system, and |
| /// type provider. |
| final ConstantEvaluationEngine evaluationEngine; |
| |
| /// The library that contains the constant expression being evaluated. |
| final LibraryElementImpl _library; |
| |
| final Map<String, DartObjectImpl>? _lexicalEnvironment; |
| final Substitution? _substitution; |
| |
| /// Error reporter that we use to report errors accumulated while computing |
| /// the constant. |
| final ErrorReporter _errorReporter; |
| |
| /// Helper class used to compute constant values. |
| late final 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. |
| /// |
| /// The [substitution] is specified for instance creations. |
| ConstantVisitor( |
| this.evaluationEngine, |
| this._library, |
| this._errorReporter, { |
| Map<String, DartObjectImpl>? lexicalEnvironment, |
| Substitution? substitution, |
| }) : _lexicalEnvironment = lexicalEnvironment, |
| _substitution = substitution { |
| _dartObjectComputer = DartObjectComputer( |
| _library.typeSystem, |
| _errorReporter, |
| ); |
| } |
| |
| /// Convenience getter to gain access to the [evaluationEngine]'s type system. |
| TypeSystemImpl get typeSystem => _library.typeSystem; |
| |
| bool get _isNonNullableByDefault => typeSystem.isNonNullableByDefault; |
| |
| /// Convenience getter to gain access to the [evaluationEngine]'s type |
| /// provider. |
| TypeProvider get _typeProvider => _library.typeProvider; |
| |
| @override |
| DartObjectImpl? visitAdjacentStrings(AdjacentStrings node) { |
| DartObjectImpl? result; |
| 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? visitAsExpression(AsExpression node) { |
| var expressionResult = node.expression.accept(this); |
| var typeResult = node.type.accept(this); |
| return _dartObjectComputer.castToType(node, expressionResult, typeResult); |
| } |
| |
| @override |
| DartObjectImpl? visitBinaryExpression(BinaryExpression node) { |
| TokenType operatorType = node.operator.type; |
| var leftResult = node.leftOperand.accept(this); |
| // evaluate lazy operators |
| if (operatorType == TokenType.AMPERSAND_AMPERSAND) { |
| if (leftResult?.toBoolValue() == false) { |
| _reportNotPotentialConstants(node.rightOperand); |
| } |
| return _dartObjectComputer.lazyAnd( |
| node, leftResult, () => node.rightOperand.accept(this)); |
| } else if (operatorType == TokenType.BAR_BAR) { |
| if (leftResult?.toBoolValue() == true) { |
| _reportNotPotentialConstants(node.rightOperand); |
| } |
| return _dartObjectComputer.lazyOr( |
| node, leftResult, () => node.rightOperand.accept(this)); |
| } else if (operatorType == TokenType.QUESTION_QUESTION) { |
| if (leftResult?.isNull != true) { |
| _reportNotPotentialConstants(node.rightOperand); |
| } |
| return _dartObjectComputer.lazyQuestionQuestion( |
| node, leftResult, () => node.rightOperand.accept(this)); |
| } |
| // evaluate eager operators |
| var rightResult = node.rightOperand.accept(this); |
| if (operatorType == TokenType.AMPERSAND) { |
| return _dartObjectComputer.eagerAnd(node, leftResult, rightResult); |
| } else if (operatorType == TokenType.BANG_EQ) { |
| return _dartObjectComputer.notEqual(node, leftResult, rightResult); |
| } else if (operatorType == TokenType.BAR) { |
| return _dartObjectComputer.eagerOr(node, leftResult, rightResult); |
| } else if (operatorType == TokenType.CARET) { |
| return _dartObjectComputer.eagerXor(node, leftResult, rightResult); |
| } else if (operatorType == TokenType.EQ_EQ) { |
| return _dartObjectComputer.lazyEqualEqual(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.GT_GT_GT) { |
| return _dartObjectComputer.logicalShiftRight( |
| 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 { |
| // TODO(brianwilkerson) Figure out which error to report. |
| _error(node, null); |
| return null; |
| } |
| } |
| |
| @override |
| DartObjectImpl visitBooleanLiteral(BooleanLiteral node) { |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.boolType, |
| BoolState.from(node.value), |
| ); |
| } |
| |
| @override |
| DartObjectImpl? visitConditionalExpression(ConditionalExpression node) { |
| var condition = node.condition; |
| var conditionResult = condition.accept(this); |
| |
| if (conditionResult == null) { |
| return conditionResult; |
| } else if (!conditionResult.isBool) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL, condition); |
| return null; |
| } |
| conditionResult = |
| _dartObjectComputer.applyBooleanConversion(condition, conditionResult); |
| if (conditionResult == null) { |
| return conditionResult; |
| } |
| if (conditionResult.toBoolValue() == true) { |
| _reportNotPotentialConstants(node.elseExpression); |
| return node.thenExpression.accept(this); |
| } else if (conditionResult.toBoolValue() == false) { |
| _reportNotPotentialConstants(node.thenExpression); |
| return node.elseExpression.accept(this); |
| } |
| |
| // We used to return an object with a known type and an unknown value, but |
| // we can't do that without evaluating both the 'then' and 'else' |
| // expressions, and we're not suppose to do that under lazy semantics. I'm |
| // not sure which failure mode is worse. |
| return null; |
| } |
| |
| @override |
| DartObjectImpl visitDoubleLiteral(DoubleLiteral node) { |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.doubleType, |
| DoubleState(node.value), |
| ); |
| } |
| |
| @override |
| DartObjectImpl? visitInstanceCreationExpression( |
| InstanceCreationExpression node) { |
| if (!node.isConst) { |
| // TODO(brianwilkerson) Figure out which error to report. |
| _error(node, null); |
| return null; |
| } |
| var constructor = node.constructorName.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(_library, node, |
| node.argumentList.arguments, constructor, this, _errorReporter); |
| } |
| |
| @override |
| DartObjectImpl visitIntegerLiteral(IntegerLiteral node) { |
| if (node.staticType == _typeProvider.doubleType) { |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.doubleType, |
| DoubleState(node.value?.toDouble()), |
| ); |
| } |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.intType, |
| IntState(node.value), |
| ); |
| } |
| |
| @override |
| DartObjectImpl? visitInterpolationExpression(InterpolationExpression node) { |
| var 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) { |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.stringType, |
| StringState(node.value), |
| ); |
| } |
| |
| @override |
| DartObjectImpl? visitIsExpression(IsExpression node) { |
| var expressionResult = node.expression.accept(this); |
| var typeResult = node.type.accept(this); |
| return _dartObjectComputer.typeTest(node, expressionResult, typeResult); |
| } |
| |
| @override |
| DartObjectImpl? visitListLiteral(ListLiteral node) { |
| if (!node.isConst) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.MISSING_CONST_IN_LIST_LITERAL, node); |
| return null; |
| } |
| bool errorOccurred = false; |
| List<DartObjectImpl> list = []; |
| for (CollectionElement element in node.elements) { |
| errorOccurred = errorOccurred | _addElementsToList(list, element); |
| } |
| if (errorOccurred) { |
| return null; |
| } |
| var nodeType = node.staticType; |
| DartType elementType = |
| nodeType is InterfaceType && nodeType.typeArguments.isNotEmpty |
| ? nodeType.typeArguments[0] |
| : _typeProvider.dynamicType; |
| InterfaceType listType = _typeProvider.listType(elementType); |
| return DartObjectImpl(typeSystem, listType, ListState(list)); |
| } |
| |
| @override |
| DartObjectImpl? visitMethodInvocation(MethodInvocation node) { |
| var element = node.methodName.staticElement; |
| if (element is FunctionElement) { |
| if (element.name == "identical") { |
| NodeList<Expression> arguments = node.argumentList.arguments; |
| if (arguments.length == 2) { |
| var enclosingElement = element.enclosingElement; |
| if (enclosingElement is CompilationUnitElement) { |
| LibraryElement library = enclosingElement.library; |
| if (library.isDartCore) { |
| var leftArgument = arguments[0].accept(this); |
| var 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) { |
| return evaluationEngine._nullObject(_library); |
| } |
| |
| @override |
| DartObjectImpl? visitParenthesizedExpression(ParenthesizedExpression node) => |
| node.expression.accept(this); |
| |
| @override |
| DartObjectImpl? visitPrefixedIdentifier(PrefixedIdentifier node) { |
| SimpleIdentifier prefixNode = node.prefix; |
| var prefixElement = prefixNode.staticElement; |
| // String.length |
| if (prefixElement is! PrefixElement && |
| prefixElement is! ClassElement && |
| prefixElement is! ExtensionElement) { |
| var prefixResult = prefixNode.accept(this); |
| if (prefixResult != null && |
| _isStringLength(prefixResult, node.identifier)) { |
| return prefixResult.stringLength(typeSystem); |
| } |
| } |
| // importPrefix.CONST |
| if (prefixElement is! PrefixElement && prefixElement is! ExtensionElement) { |
| var 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) { |
| var 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) { |
| var target = node.target; |
| if (target != null) { |
| var prefixResult = target.accept(this); |
| if (prefixResult != null && |
| _isStringLength(prefixResult, node.propertyName)) { |
| return prefixResult.stringLength(typeSystem); |
| } |
| } |
| return _getConstantValue(node, node.propertyName.staticElement); |
| } |
| |
| @override |
| DartObjectImpl? visitSetOrMapLiteral(SetOrMapLiteral node) { |
| // Note: due to dartbug.com/33441, it's possible that a set/map literal |
| // resynthesized from a summary will have neither its `isSet` or `isMap` |
| // boolean set to `true`. We work around the problem by assuming such |
| // literals are maps. |
| // TODO(paulberry): when dartbug.com/33441 is fixed, add an assertion here |
| // to verify that `node.isSet == !node.isMap`. |
| bool isMap = !node.isSet; |
| if (isMap) { |
| if (!node.isConst) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.MISSING_CONST_IN_MAP_LITERAL, node); |
| return null; |
| } |
| bool errorOccurred = false; |
| Map<DartObjectImpl, DartObjectImpl> map = {}; |
| for (CollectionElement element in node.elements) { |
| errorOccurred = errorOccurred | _addElementsToMap(map, element); |
| } |
| if (errorOccurred) { |
| return null; |
| } |
| DartType keyType = _typeProvider.dynamicType; |
| DartType valueType = _typeProvider.dynamicType; |
| var nodeType = node.staticType; |
| if (nodeType is InterfaceType) { |
| var typeArguments = nodeType.typeArguments; |
| if (typeArguments.length >= 2) { |
| keyType = typeArguments[0]; |
| valueType = typeArguments[1]; |
| } |
| } |
| InterfaceType mapType = _typeProvider.mapType(keyType, valueType); |
| return DartObjectImpl(typeSystem, mapType, MapState(map)); |
| } else { |
| if (!node.isConst) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.MISSING_CONST_IN_SET_LITERAL, node); |
| return null; |
| } |
| bool errorOccurred = false; |
| Set<DartObjectImpl> set = <DartObjectImpl>{}; |
| for (CollectionElement element in node.elements) { |
| errorOccurred = errorOccurred | _addElementsToSet(set, element); |
| } |
| if (errorOccurred) { |
| return null; |
| } |
| var nodeType = node.staticType; |
| DartType elementType = |
| nodeType is InterfaceType && nodeType.typeArguments.isNotEmpty |
| ? nodeType.typeArguments[0] |
| : _typeProvider.dynamicType; |
| InterfaceType setType = _typeProvider.setType(elementType); |
| return DartObjectImpl(typeSystem, setType, SetState(set)); |
| } |
| } |
| |
| @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) { |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.stringType, |
| StringState(node.value), |
| ); |
| } |
| |
| @override |
| DartObjectImpl? visitStringInterpolation(StringInterpolation node) { |
| DartObjectImpl? result; |
| 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 = 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 DartObjectImpl( |
| typeSystem, |
| _typeProvider.symbolType, |
| SymbolState(buffer.toString()), |
| ); |
| } |
| |
| @override |
| DartObjectImpl? visitTypeName(TypeName node) { |
| var type = node.type; |
| |
| if (type == null) { |
| return null; |
| } |
| |
| if (!_isNonNullableByDefault && hasTypeParameterReference(type)) { |
| return super.visitTypeName(node); |
| } |
| |
| if (_substitution != null) { |
| type = _substitution!.substituteType(type); |
| } |
| |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.typeType, |
| TypeState(type), |
| ); |
| } |
| |
| /// Add the entries produced by evaluating the given collection [element] to |
| /// the given [list]. Return `true` if the evaluation of one or more of the |
| /// elements failed. |
| bool _addElementsToList(List<DartObject> list, CollectionElement element) { |
| if (element is ForElement) { |
| _error(element, null); |
| } else if (element is IfElement) { |
| var conditionValue = _evaluateCondition(element.condition); |
| if (conditionValue == null) { |
| return true; |
| } else if (conditionValue) { |
| return _addElementsToList(list, element.thenElement); |
| } else if (element.elseElement != null) { |
| return _addElementsToList(list, element.elseElement!); |
| } |
| return false; |
| } else if (element is Expression) { |
| var value = element.accept(this); |
| if (value == null) { |
| return true; |
| } |
| list.add(value); |
| return false; |
| } else if (element is SpreadElement) { |
| var elementResult = element.expression.accept(this); |
| var value = elementResult?.toListValue(); |
| if (value == null) { |
| return true; |
| } |
| list.addAll(value); |
| return false; |
| } |
| // This error should have been reported elsewhere. |
| return true; |
| } |
| |
| /// Add the entries produced by evaluating the given map [element] to the |
| /// given [map]. Return `true` if the evaluation of one or more of the entries |
| /// failed. |
| bool _addElementsToMap( |
| Map<DartObjectImpl, DartObjectImpl> map, CollectionElement element) { |
| if (element is ForElement) { |
| _error(element, null); |
| } else if (element is IfElement) { |
| var conditionValue = _evaluateCondition(element.condition); |
| if (conditionValue == null) { |
| return true; |
| } else if (conditionValue) { |
| return _addElementsToMap(map, element.thenElement); |
| } else if (element.elseElement != null) { |
| return _addElementsToMap(map, element.elseElement!); |
| } |
| return false; |
| } else if (element is MapLiteralEntry) { |
| var keyResult = element.key.accept(this); |
| var valueResult = element.value.accept(this); |
| if (keyResult == null || valueResult == null) { |
| return true; |
| } |
| map[keyResult] = valueResult; |
| return false; |
| } else if (element is SpreadElement) { |
| var elementResult = element.expression.accept(this); |
| var value = elementResult?.toMapValue(); |
| if (value == null) { |
| return true; |
| } |
| map.addAll(value); |
| return false; |
| } |
| // This error should have been reported elsewhere. |
| return true; |
| } |
| |
| /// Add the entries produced by evaluating the given collection [element] to |
| /// the given [set]. Return `true` if the evaluation of one or more of the |
| /// elements failed. |
| bool _addElementsToSet(Set<DartObject> set, CollectionElement element) { |
| if (element is ForElement) { |
| _error(element, null); |
| } else if (element is IfElement) { |
| var conditionValue = _evaluateCondition(element.condition); |
| if (conditionValue == null) { |
| return true; |
| } else if (conditionValue) { |
| return _addElementsToSet(set, element.thenElement); |
| } else if (element.elseElement != null) { |
| return _addElementsToSet(set, element.elseElement!); |
| } |
| return false; |
| } else if (element is Expression) { |
| var value = element.accept(this); |
| if (value == null) { |
| return true; |
| } |
| set.add(value); |
| return false; |
| } else if (element is SpreadElement) { |
| var elementResult = element.expression.accept(this); |
| var value = elementResult?.toSetValue(); |
| if (value == null) { |
| return true; |
| } |
| set.addAll(value); |
| return false; |
| } |
| // This error should have been reported elsewhere. |
| return true; |
| } |
| |
| /// Create an error associated with the given [node]. The error will have the |
| /// given error [code]. |
| void _error(AstNode node, ErrorCode? code) { |
| if (code == null) { |
| var parent = node.parent; |
| var parent2 = parent?.parent; |
| if (parent is ArgumentList && |
| parent2 is InstanceCreationExpression && |
| parent2.isConst) { |
| code = CompileTimeErrorCode.CONST_WITH_NON_CONSTANT_ARGUMENT; |
| } else { |
| code = CompileTimeErrorCode.INVALID_CONSTANT; |
| } |
| } |
| _errorReporter.reportErrorForNode(code, node); |
| } |
| |
| /// Evaluate the given [condition] with the assumption that it must be a |
| /// `bool`. |
| bool? _evaluateCondition(Expression condition) { |
| var conditionResult = condition.accept(this); |
| var conditionValue = conditionResult?.toBoolValue(); |
| if (conditionValue == null) { |
| if (conditionResult?.type != _typeProvider.boolType) { |
| // TODO(brianwilkerson) Figure out why the static type is sometimes null. |
| var staticType = condition.staticType; |
| if (staticType == null || |
| typeSystem.isAssignableTo(staticType, _typeProvider.boolType)) { |
| // If the static type is not assignable, then we will have already |
| // reported this error. |
| // TODO(mfairhurst) get the FeatureSet to suppress this for nnbd too. |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, condition); |
| } |
| } |
| } |
| return conditionValue; |
| } |
| |
| /// 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(Expression node, Element? element) { |
| element = element?.declaration; |
| var variableElement = |
| element is PropertyAccessorElement ? element.variable : element; |
| |
| if (node is SimpleIdentifier && |
| (node.tearOffTypeArgumentTypes?.any(hasTypeParameterReference) ?? |
| false)) { |
| _error(node, null); |
| } |
| |
| if (variableElement is VariableElementImpl) { |
| // We access values of constant variables here in two cases: when we |
| // compute values of other constant variables, or when we compute values |
| // and errors for other constant expressions. In either case we have |
| // already computed values of all dependencies first (or detect a cycle), |
| // so the value has already been computed and we can just return it. |
| var value = variableElement.evaluationResult; |
| if (variableElement.isConst && value != null) { |
| return value.value; |
| } |
| } else if (variableElement is ExecutableElement) { |
| var function = element as ExecutableElement; |
| if (function.isStatic) { |
| var functionType = node.staticType as ParameterizedType; |
| return DartObjectImpl( |
| typeSystem, |
| functionType, |
| FunctionState(function), |
| ); |
| } |
| } else if (variableElement is ClassElement) { |
| var type = variableElement.instantiate( |
| typeArguments: variableElement.typeParameters |
| .map((t) => _typeProvider.dynamicType) |
| .toList(), |
| nullabilitySuffix: NullabilitySuffix.star, |
| ); |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.typeType, |
| TypeState(type), |
| ); |
| } else if (variableElement is DynamicElementImpl) { |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.typeType, |
| TypeState(_typeProvider.dynamicType), |
| ); |
| } else if (variableElement is TypeAliasElement) { |
| var type = variableElement.instantiate( |
| typeArguments: variableElement.typeParameters |
| .map((t) => _typeProvider.dynamicType) |
| .toList(), |
| nullabilitySuffix: NullabilitySuffix.star, |
| ); |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.typeType, |
| TypeState(type), |
| ); |
| } else if (variableElement is NeverElementImpl) { |
| return DartObjectImpl( |
| typeSystem, |
| _typeProvider.typeType, |
| TypeState(_typeProvider.neverType), |
| ); |
| } else if (variableElement is TypeParameterElement) { |
| // Constants may not refer to type parameters. |
| } |
| |
| // 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.type.element != _typeProvider.stringElement) { |
| return false; |
| } |
| return identifier.name == 'length' && |
| identifier.staticElement?.enclosingElement is! ExtensionElement; |
| } |
| |
| void _reportNotPotentialConstants(AstNode node) { |
| var notPotentiallyConstants = getNotPotentiallyConstants( |
| node, |
| isNonNullableByDefault: _isNonNullableByDefault, |
| ); |
| if (notPotentiallyConstants.isEmpty) return; |
| |
| for (var notConst in notPotentiallyConstants) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INVALID_CONSTANT, |
| notConst, |
| ); |
| } |
| } |
| |
| /// Return the value of the given [expression], or a representation of 'null' |
| /// if the expression cannot be evaluated. |
| DartObjectImpl _valueOf(Expression expression) { |
| var expressionValue = expression.accept(this); |
| if (expressionValue != null) { |
| return expressionValue; |
| } |
| return evaluationEngine._nullObject(_library); |
| } |
| } |
| |
| /// A utility class that contains methods for manipulating instances of a Dart |
| /// class and for collecting errors during evaluation. |
| class DartObjectComputer { |
| final TypeSystemImpl _typeSystem; |
| |
| /// The error reporter that we are using to collect errors. |
| final ErrorReporter _errorReporter; |
| |
| DartObjectComputer(this._typeSystem, this._errorReporter); |
| |
| DartObjectImpl? add(BinaryExpression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| try { |
| return leftOperand.add(_typeSystem, 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(_typeSystem); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? bitNot(Expression node, DartObjectImpl? evaluationResult) { |
| if (evaluationResult != null) { |
| try { |
| return evaluationResult.bitNot(_typeSystem); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? castToType( |
| AsExpression node, DartObjectImpl? expression, DartObjectImpl? type) { |
| if (expression != null && type != null) { |
| try { |
| return expression.castToType(_typeSystem, type); |
| } 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(_typeSystem, 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(_typeSystem, rightOperand); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? eagerAnd(BinaryExpression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| try { |
| return leftOperand.eagerAnd(_typeSystem, rightOperand); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? eagerOr(BinaryExpression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| try { |
| return leftOperand.eagerOr(_typeSystem, rightOperand); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? eagerQuestionQuestion(Expression node, |
| DartObjectImpl? leftOperand, DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| if (leftOperand.isNull) { |
| return rightOperand; |
| } |
| return leftOperand; |
| } |
| return null; |
| } |
| |
| DartObjectImpl? eagerXor(BinaryExpression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| try { |
| return leftOperand.eagerXor(_typeSystem, 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(_typeSystem, 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(_typeSystem, 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(_typeSystem, 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(_typeSystem, 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.isIdentical2(_typeSystem, rightOperand); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? lazyAnd(BinaryExpression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? Function() rightOperandComputer) { |
| if (leftOperand != null) { |
| try { |
| return leftOperand.lazyAnd(_typeSystem, rightOperandComputer); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? lazyEqualEqual(Expression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| try { |
| return leftOperand.lazyEqualEqual(_typeSystem, rightOperand); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? lazyOr(BinaryExpression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? Function() rightOperandComputer) { |
| if (leftOperand != null) { |
| try { |
| return leftOperand.lazyOr(_typeSystem, rightOperandComputer); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? lazyQuestionQuestion( |
| Expression node, |
| DartObjectImpl? leftOperand, |
| DartObjectImpl? Function() rightOperandComputer) { |
| if (leftOperand != null) { |
| if (leftOperand.isNull) { |
| return rightOperandComputer(); |
| } |
| return leftOperand; |
| } |
| return null; |
| } |
| |
| DartObjectImpl? lessThan(BinaryExpression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| try { |
| return leftOperand.lessThan(_typeSystem, 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(_typeSystem, 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(_typeSystem); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? logicalShiftRight(BinaryExpression node, |
| DartObjectImpl? leftOperand, DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| try { |
| return leftOperand.logicalShiftRight(_typeSystem, 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(_typeSystem, 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(_typeSystem); |
| } 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(_typeSystem, 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(_typeSystem); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? remainder(BinaryExpression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| try { |
| return leftOperand.remainder(_typeSystem, 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(_typeSystem, 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(_typeSystem, 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) { |
| var value = evaluationResult.value; |
| if (value != null) { |
| try { |
| return EvaluationResultImpl(value.stringLength(_typeSystem)); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return EvaluationResultImpl(null); |
| } |
| |
| DartObjectImpl? times(BinaryExpression node, DartObjectImpl? leftOperand, |
| DartObjectImpl? rightOperand) { |
| if (leftOperand != null && rightOperand != null) { |
| try { |
| return leftOperand.times(_typeSystem, rightOperand); |
| } on EvaluationException catch (exception) { |
| _errorReporter.reportErrorForNode(exception.errorCode, node); |
| } |
| } |
| return null; |
| } |
| |
| DartObjectImpl? typeTest( |
| IsExpression node, DartObjectImpl? expression, DartObjectImpl? type) { |
| if (expression != null && type != null) { |
| try { |
| DartObjectImpl result = expression.hasType(_typeSystem, type); |
| if (node.notOperator != null) { |
| return result.logicalNot(_typeSystem); |
| } |
| return result; |
| } 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) => |
| 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) => |
| 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. |
| late final 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]) { |
| _errors = errors ?? <AnalysisError>[]; |
| } |
| |
| List<AnalysisError> get errors => _errors; |
| |
| bool equalValues(TypeProvider typeProvider, EvaluationResultImpl result) { |
| if (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(); |
| } |
| } |