| // Copyright (c) 2018, 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 'package:analyzer/dart/analysis/declared_variables.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/constant/value.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/src/dart/ast/utilities.dart'; |
| import 'package:analyzer/src/dart/constant/evaluation.dart'; |
| import 'package:analyzer/src/dart/constant/potentially_constant.dart'; |
| import 'package:analyzer/src/dart/constant/value.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| |
| /// Instances of the class `ConstantVerifier` traverse an AST structure looking |
| /// for additional errors and warnings not covered by the parser and resolver. |
| /// In particular, it looks for errors and warnings related to constant |
| /// expressions. |
| class ConstantVerifier extends RecursiveAstVisitor<void> { |
| /// The error reporter by which errors will be reported. |
| final ErrorReporter _errorReporter; |
| |
| /// The type provider used to access the known types. |
| final TypeProvider _typeProvider; |
| |
| /// The type system in use. |
| final TypeSystem _typeSystem; |
| |
| /// The set of variables declared using '-D' on the command line. |
| final DeclaredVariables declaredVariables; |
| |
| /// The type representing the type 'bool'. |
| InterfaceType _boolType; |
| |
| /// The type representing the type 'int'. |
| InterfaceType _intType; |
| |
| /// The type representing the type 'num'. |
| InterfaceType _numType; |
| |
| /// The type representing the type 'string'. |
| InterfaceType _stringType; |
| |
| /// The current library that is being analyzed. |
| final LibraryElement _currentLibrary; |
| |
| ConstantEvaluationEngine _evaluationEngine; |
| |
| /// Initialize a newly created constant verifier. |
| /// |
| /// @param errorReporter the error reporter by which errors will be reported |
| ConstantVerifier(this._errorReporter, LibraryElement currentLibrary, |
| this._typeProvider, this.declaredVariables, |
| {bool forAnalysisDriver: false}) |
| : _currentLibrary = currentLibrary, |
| _typeSystem = currentLibrary.context.typeSystem { |
| this._boolType = _typeProvider.boolType; |
| this._intType = _typeProvider.intType; |
| this._numType = _typeProvider.numType; |
| this._stringType = _typeProvider.stringType; |
| this._evaluationEngine = new ConstantEvaluationEngine( |
| _typeProvider, declaredVariables, |
| forAnalysisDriver: forAnalysisDriver, |
| typeSystem: _typeSystem, |
| experimentStatus: |
| (currentLibrary.context.analysisOptions as AnalysisOptionsImpl) |
| .experimentStatus); |
| } |
| |
| @override |
| void visitAnnotation(Annotation node) { |
| super.visitAnnotation(node); |
| // check annotation creation |
| Element element = node.element; |
| if (element is ConstructorElement) { |
| // should be 'const' constructor |
| if (!element.isConst) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.NON_CONSTANT_ANNOTATION_CONSTRUCTOR, node); |
| return; |
| } |
| // should have arguments |
| ArgumentList argumentList = node.arguments; |
| if (argumentList == null) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.NO_ANNOTATION_CONSTRUCTOR_ARGUMENTS, node); |
| return; |
| } |
| // arguments should be constants |
| _validateConstantArguments(argumentList); |
| } |
| } |
| |
| @override |
| void visitConstructorDeclaration(ConstructorDeclaration node) { |
| if (node.constKeyword != null) { |
| _validateConstructorInitializers(node); |
| _validateFieldInitializers(node.parent, node); |
| } |
| _validateDefaultValues(node.parameters); |
| super.visitConstructorDeclaration(node); |
| } |
| |
| @override |
| void visitFunctionExpression(FunctionExpression node) { |
| super.visitFunctionExpression(node); |
| _validateDefaultValues(node.parameters); |
| } |
| |
| @override |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| if (node.isConst) { |
| TypeName typeName = node.constructorName.type; |
| _checkForConstWithTypeParameters(typeName); |
| |
| // We need to evaluate the constant to see if any errors occur during its |
| // evaluation. |
| ConstructorElement constructor = node.staticElement; |
| if (constructor != null) { |
| ConstantVisitor constantVisitor = |
| new ConstantVisitor(_evaluationEngine, _errorReporter); |
| _evaluationEngine.evaluateConstructorCall( |
| node, |
| node.argumentList.arguments, |
| constructor, |
| constantVisitor, |
| _errorReporter); |
| } |
| } else { |
| super.visitInstanceCreationExpression(node); |
| } |
| } |
| |
| @override |
| void visitListLiteral(ListLiteral node) { |
| super.visitListLiteral(node); |
| if (node.isConst) { |
| InterfaceType nodeType = node.staticType; |
| DartType elementType = nodeType.typeArguments[0]; |
| var verifier = _ConstLiteralVerifier( |
| this, |
| isConst: true, |
| errorCode: CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT, |
| forList: true, |
| listElementType: elementType, |
| ); |
| for (CollectionElement element in node.elements2) { |
| verifier.verify(element); |
| } |
| } |
| } |
| |
| @override |
| void visitMethodDeclaration(MethodDeclaration node) { |
| super.visitMethodDeclaration(node); |
| _validateDefaultValues(node.parameters); |
| } |
| |
| @override |
| void visitSetOrMapLiteral(SetOrMapLiteral node) { |
| super.visitSetOrMapLiteral(node); |
| bool isConst = node.isConst; |
| if (node.isSet) { |
| if (isConst) { |
| InterfaceType nodeType = node.staticType; |
| var elementType = nodeType.typeArguments[0]; |
| var duplicateElements = <Expression>[]; |
| var verifier = _ConstLiteralVerifier( |
| this, |
| isConst: isConst, |
| errorCode: CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT, |
| forSet: true, |
| setElementType: elementType, |
| setUniqueValues: Set<DartObject>(), |
| setDuplicateElements: duplicateElements, |
| ); |
| for (CollectionElement element in node.elements2) { |
| verifier.verify(element); |
| } |
| for (var duplicateElement in duplicateElements) { |
| _errorReporter.reportErrorForNode( |
| StaticWarningCode.EQUAL_VALUES_IN_CONST_SET, |
| duplicateElement, |
| ); |
| } |
| } |
| } else if (node.isMap) { |
| InterfaceType nodeType = node.staticType; |
| var keyType = nodeType.typeArguments[0]; |
| var valueType = nodeType.typeArguments[1]; |
| bool reportEqualKeys = true; |
| var duplicateKeyElements = <Expression>[]; |
| var verifier = _ConstLiteralVerifier( |
| this, |
| isConst: isConst, |
| errorCode: CompileTimeErrorCode.NON_CONSTANT_MAP_ELEMENT, |
| forMap: true, |
| mapKeyType: keyType, |
| mapValueType: valueType, |
| mapUniqueKeys: Set<DartObject>(), |
| mapDuplicateKeyElements: duplicateKeyElements, |
| ); |
| for (CollectionElement entry in node.elements2) { |
| verifier.verify(entry); |
| } |
| if (reportEqualKeys) { |
| for (var duplicateKeyElement in duplicateKeyElements) { |
| _errorReporter.reportErrorForNode( |
| StaticWarningCode.EQUAL_KEYS_IN_MAP, |
| duplicateKeyElement, |
| ); |
| } |
| } |
| } |
| } |
| |
| @override |
| void visitSwitchStatement(SwitchStatement node) { |
| // TODO(paulberry): to minimize error messages, it would be nice to |
| // compare all types with the most popular type rather than the first |
| // type. |
| NodeList<SwitchMember> switchMembers = node.members; |
| bool foundError = false; |
| DartType firstType = null; |
| for (SwitchMember switchMember in switchMembers) { |
| if (switchMember is SwitchCase) { |
| Expression expression = switchMember.expression; |
| DartObjectImpl caseResult = _validate( |
| expression, CompileTimeErrorCode.NON_CONSTANT_CASE_EXPRESSION); |
| if (caseResult != null) { |
| _reportErrorIfFromDeferredLibrary( |
| expression, |
| CompileTimeErrorCode |
| .NON_CONSTANT_CASE_EXPRESSION_FROM_DEFERRED_LIBRARY); |
| DartObject value = caseResult; |
| if (firstType == null) { |
| firstType = value.type; |
| } else { |
| DartType nType = value.type; |
| if (firstType != nType) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.INCONSISTENT_CASE_EXPRESSION_TYPES, |
| expression, |
| [expression.toSource(), firstType.displayName]); |
| foundError = true; |
| } |
| } |
| } |
| } |
| } |
| if (!foundError) { |
| _checkForCaseExpressionTypeImplementsEquals(node, firstType); |
| } |
| super.visitSwitchStatement(node); |
| } |
| |
| @override |
| void visitVariableDeclaration(VariableDeclaration node) { |
| super.visitVariableDeclaration(node); |
| Expression initializer = node.initializer; |
| if (initializer != null && (node.isConst || node.isFinal)) { |
| VariableElementImpl element = node.declaredElement as VariableElementImpl; |
| EvaluationResultImpl result = element.evaluationResult; |
| if (result == null) { |
| // Variables marked "const" should have had their values computed by |
| // ConstantValueComputer. Other variables will only have had their |
| // values computed if the value was needed (e.g. final variables in a |
| // class containing const constructors). |
| assert(!node.isConst); |
| return; |
| } |
| _reportErrors(result.errors, |
| CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE); |
| _reportErrorIfFromDeferredLibrary( |
| initializer, |
| CompileTimeErrorCode |
| .CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE_FROM_DEFERRED_LIBRARY); |
| } |
| } |
| |
| /// This verifies that the passed switch statement does not have a case |
| /// expression with the operator '==' overridden. |
| /// |
| /// @param node the switch statement to evaluate |
| /// @param type the common type of all 'case' expressions |
| /// @return `true` if and only if an error code is generated on the passed |
| /// node. |
| /// See [CompileTimeErrorCode.CASE_EXPRESSION_TYPE_IMPLEMENTS_EQUALS]. |
| bool _checkForCaseExpressionTypeImplementsEquals( |
| SwitchStatement node, DartType type) { |
| if (!_implementsEqualsWhenNotAllowed(type)) { |
| return false; |
| } |
| // report error |
| _errorReporter.reportErrorForToken( |
| CompileTimeErrorCode.CASE_EXPRESSION_TYPE_IMPLEMENTS_EQUALS, |
| node.switchKeyword, |
| [type.displayName]); |
| return true; |
| } |
| |
| /// Verify that the given [type] does not reference any type parameters. |
| /// |
| /// See [CompileTimeErrorCode.CONST_WITH_TYPE_PARAMETERS]. |
| void _checkForConstWithTypeParameters(TypeAnnotation type) { |
| // something wrong with AST |
| if (type is! TypeName) { |
| return; |
| } |
| TypeName typeName = type; |
| Identifier name = typeName.name; |
| if (name == null) { |
| return; |
| } |
| // should not be a type parameter |
| if (name.staticElement is TypeParameterElement) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_WITH_TYPE_PARAMETERS, name); |
| } |
| // check type arguments |
| TypeArgumentList typeArguments = typeName.typeArguments; |
| if (typeArguments != null) { |
| for (TypeAnnotation argument in typeArguments.arguments) { |
| _checkForConstWithTypeParameters(argument); |
| } |
| } |
| } |
| |
| /// @return `true` if given [Type] implements operator <i>==</i>, and it is |
| /// not <i>int</i> or <i>String</i>. |
| bool _implementsEqualsWhenNotAllowed(DartType type) { |
| // ignore int or String |
| if (type == null || type == _intType || type == _typeProvider.stringType) { |
| return false; |
| } else if (type == _typeProvider.doubleType) { |
| return true; |
| } |
| // prepare ClassElement |
| Element element = type.element; |
| if (element is ClassElement) { |
| // lookup for == |
| MethodElement method = |
| element.lookUpConcreteMethod("==", _currentLibrary); |
| if (method == null || method.enclosingElement.type.isObject) { |
| return false; |
| } |
| // there is == that we don't like |
| return true; |
| } |
| return false; |
| } |
| |
| /// Given some computed [Expression], this method generates the passed |
| /// [ErrorCode] on the node if its' value consists of information from a |
| /// deferred library. |
| /// |
| /// @param expression the expression to be tested for a deferred library |
| /// reference |
| /// @param errorCode the error code to be used if the expression is or |
| /// consists of a reference to a deferred library |
| void _reportErrorIfFromDeferredLibrary( |
| Expression expression, ErrorCode errorCode) { |
| DeferredLibraryReferenceDetector referenceDetector = |
| new DeferredLibraryReferenceDetector(); |
| expression.accept(referenceDetector); |
| if (referenceDetector.result) { |
| _errorReporter.reportErrorForNode(errorCode, expression); |
| } |
| } |
| |
| /// Report any errors in the given list. Except for special cases, use the |
| /// given error code rather than the one reported in the error. |
| /// |
| /// @param errors the errors that need to be reported |
| /// @param errorCode the error code to be used |
| void _reportErrors(List<AnalysisError> errors, ErrorCode errorCode) { |
| int length = errors.length; |
| for (int i = 0; i < length; i++) { |
| AnalysisError data = errors[i]; |
| ErrorCode dataErrorCode = data.errorCode; |
| if (identical(dataErrorCode, |
| CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION) || |
| identical( |
| dataErrorCode, CompileTimeErrorCode.CONST_EVAL_THROWS_IDBZE) || |
| identical(dataErrorCode, |
| CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING) || |
| identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL) || |
| identical( |
| dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_INT) || |
| identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_INT) || |
| identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_NUM) || |
| identical(dataErrorCode, |
| CompileTimeErrorCode.RECURSIVE_COMPILE_TIME_CONSTANT) || |
| identical( |
| dataErrorCode, |
| CheckedModeCompileTimeErrorCode |
| .CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH) || |
| identical( |
| dataErrorCode, |
| CheckedModeCompileTimeErrorCode |
| .CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH) || |
| identical(dataErrorCode, |
| CheckedModeCompileTimeErrorCode.VARIABLE_TYPE_MISMATCH)) { |
| _errorReporter.reportError(data); |
| } else if (errorCode != null) { |
| _errorReporter.reportError(new AnalysisError( |
| data.source, data.offset, data.length, errorCode)); |
| } |
| } |
| } |
| |
| /// Validate that the given expression is a compile time constant. Return the |
| /// value of the compile time constant, or `null` if the expression is not a |
| /// compile time constant. |
| /// |
| /// @param expression the expression to be validated |
| /// @param errorCode the error code to be used if the expression is not a |
| /// compile time constant |
| /// @return the value of the compile time constant |
| DartObjectImpl _validate(Expression expression, ErrorCode errorCode) { |
| RecordingErrorListener errorListener = new RecordingErrorListener(); |
| ErrorReporter subErrorReporter = |
| new ErrorReporter(errorListener, _errorReporter.source); |
| DartObjectImpl result = expression |
| .accept(new ConstantVisitor(_evaluationEngine, subErrorReporter)); |
| _reportErrors(errorListener.errors, errorCode); |
| return result; |
| } |
| |
| /// Validate that if the passed arguments are constant expressions. |
| /// |
| /// @param argumentList the argument list to evaluate |
| void _validateConstantArguments(ArgumentList argumentList) { |
| for (Expression argument in argumentList.arguments) { |
| Expression realArgument = |
| argument is NamedExpression ? argument.expression : argument; |
| _validate( |
| realArgument, CompileTimeErrorCode.CONST_WITH_NON_CONSTANT_ARGUMENT); |
| } |
| } |
| |
| /// Validates that the expressions of the initializers of the given constant |
| /// [constructor] are all compile time constants. |
| void _validateConstructorInitializers(ConstructorDeclaration constructor) { |
| List<ParameterElement> parameterElements = |
| constructor.parameters.parameterElements; |
| NodeList<ConstructorInitializer> initializers = constructor.initializers; |
| for (ConstructorInitializer initializer in initializers) { |
| if (initializer is AssertInitializer) { |
| _validateInitializerExpression( |
| parameterElements, initializer.condition); |
| Expression message = initializer.message; |
| if (message != null) { |
| _validateInitializerExpression(parameterElements, message); |
| } |
| } else if (initializer is ConstructorFieldInitializer) { |
| _validateInitializerExpression( |
| parameterElements, initializer.expression); |
| } else if (initializer is RedirectingConstructorInvocation) { |
| _validateInitializerInvocationArguments( |
| parameterElements, initializer.argumentList); |
| } else if (initializer is SuperConstructorInvocation) { |
| _validateInitializerInvocationArguments( |
| parameterElements, initializer.argumentList); |
| } |
| } |
| } |
| |
| /// Validate that the default value associated with each of the parameters in |
| /// the given list is a compile time constant. |
| /// |
| /// @param parameters the list of parameters to be validated |
| void _validateDefaultValues(FormalParameterList parameters) { |
| if (parameters == null) { |
| return; |
| } |
| for (FormalParameter parameter in parameters.parameters) { |
| if (parameter is DefaultFormalParameter) { |
| Expression defaultValue = parameter.defaultValue; |
| DartObjectImpl result; |
| if (defaultValue == null) { |
| result = |
| new DartObjectImpl(_typeProvider.nullType, NullState.NULL_STATE); |
| } else { |
| result = _validate( |
| defaultValue, CompileTimeErrorCode.NON_CONSTANT_DEFAULT_VALUE); |
| if (result != null) { |
| _reportErrorIfFromDeferredLibrary( |
| defaultValue, |
| CompileTimeErrorCode |
| .NON_CONSTANT_DEFAULT_VALUE_FROM_DEFERRED_LIBRARY); |
| } |
| } |
| VariableElementImpl element = |
| parameter.declaredElement as VariableElementImpl; |
| element.evaluationResult = new EvaluationResultImpl(result); |
| } |
| } |
| } |
| |
| /// Validates that the expressions of any field initializers in the class |
| /// declaration are all compile time constants. Since this is only required if |
| /// the class has a constant constructor, the error is reported at the |
| /// constructor site. |
| /// |
| /// @param classDeclaration the class which should be validated |
| /// @param errorSite the site at which errors should be reported. |
| void _validateFieldInitializers(ClassOrMixinDeclaration classDeclaration, |
| ConstructorDeclaration errorSite) { |
| NodeList<ClassMember> members = classDeclaration.members; |
| for (ClassMember member in members) { |
| if (member is FieldDeclaration && !member.isStatic) { |
| for (VariableDeclaration variableDeclaration |
| in member.fields.variables) { |
| Expression initializer = variableDeclaration.initializer; |
| if (initializer != null) { |
| // Ignore any errors produced during validation--if the constant |
| // can't be evaluated we'll just report a single error. |
| AnalysisErrorListener errorListener = |
| AnalysisErrorListener.NULL_LISTENER; |
| ErrorReporter subErrorReporter = |
| new ErrorReporter(errorListener, _errorReporter.source); |
| DartObjectImpl result = initializer.accept( |
| new ConstantVisitor(_evaluationEngine, subErrorReporter)); |
| if (result == null) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode |
| .CONST_CONSTRUCTOR_WITH_FIELD_INITIALIZED_BY_NON_CONST, |
| errorSite, |
| [variableDeclaration.name.name]); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /// Validates that the given expression is a compile time constant. |
| /// |
| /// @param parameterElements the elements of parameters of constant |
| /// constructor, they are considered as a valid potentially constant |
| /// expressions |
| /// @param expression the expression to validate |
| void _validateInitializerExpression( |
| List<ParameterElement> parameterElements, Expression expression) { |
| RecordingErrorListener errorListener = new RecordingErrorListener(); |
| ErrorReporter subErrorReporter = |
| new ErrorReporter(errorListener, _errorReporter.source); |
| DartObjectImpl result = expression.accept( |
| new _ConstantVerifier_validateInitializerExpression(_typeSystem, |
| _evaluationEngine, subErrorReporter, this, parameterElements)); |
| _reportErrors(errorListener.errors, |
| CompileTimeErrorCode.NON_CONSTANT_VALUE_IN_INITIALIZER); |
| if (result != null) { |
| _reportErrorIfFromDeferredLibrary( |
| expression, |
| CompileTimeErrorCode |
| .NON_CONSTANT_VALUE_IN_INITIALIZER_FROM_DEFERRED_LIBRARY); |
| } |
| } |
| |
| /// Validates that all of the arguments of a constructor initializer are |
| /// compile time constants. |
| /// |
| /// @param parameterElements the elements of parameters of constant |
| /// constructor, they are considered as a valid potentially constant |
| /// expressions |
| /// @param argumentList the argument list to validate |
| void _validateInitializerInvocationArguments( |
| List<ParameterElement> parameterElements, ArgumentList argumentList) { |
| if (argumentList == null) { |
| return; |
| } |
| for (Expression argument in argumentList.arguments) { |
| _validateInitializerExpression(parameterElements, argument); |
| } |
| } |
| } |
| |
| class _ConstantVerifier_validateInitializerExpression extends ConstantVisitor { |
| final TypeSystem typeSystem; |
| final ConstantVerifier verifier; |
| |
| List<ParameterElement> parameterElements; |
| |
| _ConstantVerifier_validateInitializerExpression( |
| this.typeSystem, |
| ConstantEvaluationEngine evaluationEngine, |
| ErrorReporter errorReporter, |
| this.verifier, |
| this.parameterElements) |
| : super(evaluationEngine, errorReporter); |
| |
| @override |
| DartObjectImpl visitSimpleIdentifier(SimpleIdentifier node) { |
| Element element = node.staticElement; |
| int length = parameterElements.length; |
| for (int i = 0; i < length; i++) { |
| ParameterElement parameterElement = parameterElements[i]; |
| if (identical(parameterElement, element) && parameterElement != null) { |
| DartType type = parameterElement.type; |
| if (type != null) { |
| if (type.isDynamic) { |
| return new DartObjectImpl( |
| verifier._typeProvider.objectType, DynamicState.DYNAMIC_STATE); |
| } else if (typeSystem.isSubtypeOf(type, verifier._boolType)) { |
| return new DartObjectImpl( |
| verifier._typeProvider.boolType, BoolState.UNKNOWN_VALUE); |
| } else if (typeSystem.isSubtypeOf( |
| type, verifier._typeProvider.doubleType)) { |
| return new DartObjectImpl( |
| verifier._typeProvider.doubleType, DoubleState.UNKNOWN_VALUE); |
| } else if (typeSystem.isSubtypeOf(type, verifier._intType)) { |
| return new DartObjectImpl( |
| verifier._typeProvider.intType, IntState.UNKNOWN_VALUE); |
| } else if (typeSystem.isSubtypeOf(type, verifier._numType)) { |
| return new DartObjectImpl( |
| verifier._typeProvider.numType, NumState.UNKNOWN_VALUE); |
| } else if (typeSystem.isSubtypeOf(type, verifier._stringType)) { |
| return new DartObjectImpl( |
| verifier._typeProvider.stringType, StringState.UNKNOWN_VALUE); |
| } |
| // |
| // We don't test for other types of objects (such as List, Map, |
| // Function or Type) because there are no operations allowed on such |
| // types other than '==' and '!=', which means that we don't need to |
| // know the type when there is no specific data about the state of |
| // such objects. |
| // |
| } |
| return new DartObjectImpl( |
| type is InterfaceType ? type : verifier._typeProvider.objectType, |
| GenericState.UNKNOWN_VALUE); |
| } |
| } |
| return super.visitSimpleIdentifier(node); |
| } |
| } |
| |
| class _ConstLiteralVerifier { |
| final ConstantVerifier verifier; |
| final bool isConst; |
| final Set<DartObject> mapUniqueKeys; |
| final List<Expression> mapDuplicateKeyElements; |
| final ErrorCode errorCode; |
| final DartType listElementType; |
| final DartType mapKeyType; |
| final DartType mapValueType; |
| final DartType setElementType; |
| final Set<DartObject> setUniqueValues; |
| final List<CollectionElement> setDuplicateElements; |
| final bool forList; |
| final bool forMap; |
| final bool forSet; |
| |
| _ConstLiteralVerifier( |
| this.verifier, { |
| this.isConst, |
| this.mapUniqueKeys, |
| this.mapDuplicateKeyElements, |
| this.errorCode, |
| this.listElementType, |
| this.mapKeyType, |
| this.mapValueType, |
| this.setElementType, |
| this.setUniqueValues, |
| this.setDuplicateElements, |
| this.forList = false, |
| this.forMap = false, |
| this.forSet = false, |
| }); |
| |
| bool verify(CollectionElement element) { |
| if (element is Expression) { |
| if (!isConst) return true; |
| |
| var value = verifier._validate(element, errorCode); |
| if (value == null) return false; |
| |
| if (forList) { |
| return _validateListExpression(element, value); |
| } |
| |
| if (forSet) { |
| return _validateSetExpression(element, value); |
| } |
| |
| return true; |
| } else if (element is ForElement) { |
| if (!isConst) return true; |
| |
| verifier._errorReporter.reportErrorForNode(errorCode, element); |
| return false; |
| } else if (element is IfElement) { |
| if (!isConst) return true; |
| |
| var conditionValue = verifier._validate(element.condition, errorCode); |
| var conditionBool = conditionValue?.toBoolValue(); |
| |
| // The errors have already been reported. |
| if (conditionBool == null) return false; |
| |
| var thenValid = true; |
| var elseValid = true; |
| if (conditionBool) { |
| thenValid = verify(element.thenElement); |
| if (element.elseElement != null) { |
| elseValid = _reportNotPotentialConstants(element.elseElement); |
| } |
| } else { |
| thenValid = _reportNotPotentialConstants(element.thenElement); |
| if (element.elseElement != null) { |
| elseValid = verify(element.elseElement); |
| } |
| } |
| |
| return thenValid && elseValid; |
| } else if (element is MapLiteralEntry) { |
| return _validateMapLiteralEntry(element); |
| } else if (element is SpreadElement) { |
| if (!isConst) return true; |
| |
| var value = verifier._validate(element.expression, errorCode); |
| if (value == null) return false; |
| |
| if (forList || forSet) { |
| return _validateListOrSetSpread(element, value); |
| } |
| |
| if (forMap) { |
| return _validateMapSpread(element, value); |
| } |
| |
| return true; |
| } |
| throw new UnsupportedError( |
| 'Unhandled type of collection element: ${element.runtimeType}', |
| ); |
| } |
| |
| /// Return `true` if the [node] is a potential constant. |
| bool _reportNotPotentialConstants(AstNode node) { |
| var notPotentiallyConstants = getNotPotentiallyConstants(node); |
| if (notPotentiallyConstants.isEmpty) return true; |
| |
| for (var notConst in notPotentiallyConstants) { |
| CompileTimeErrorCode errorCode; |
| if (forList) { |
| errorCode = CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT; |
| } else if (forMap) { |
| errorCode = CompileTimeErrorCode.NON_CONSTANT_MAP_ELEMENT; |
| for (var parent = notConst; parent != null; parent = parent.parent) { |
| if (parent is MapLiteralEntry) { |
| if (parent.key == notConst) { |
| errorCode = CompileTimeErrorCode.NON_CONSTANT_MAP_KEY; |
| } else { |
| errorCode = CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE; |
| } |
| break; |
| } |
| } |
| } else if (forSet) { |
| errorCode = CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT; |
| } |
| verifier._errorReporter.reportErrorForNode(errorCode, notConst); |
| } |
| |
| return false; |
| } |
| |
| bool _validateListExpression(Expression expression, DartObjectImpl value) { |
| if (!verifier._evaluationEngine.runtimeTypeMatch(value, listElementType)) { |
| verifier._errorReporter.reportErrorForNode( |
| StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE, |
| expression, |
| [value.type, listElementType], |
| ); |
| return false; |
| } |
| |
| verifier._reportErrorIfFromDeferredLibrary( |
| expression, |
| CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT_FROM_DEFERRED_LIBRARY, |
| ); |
| |
| return true; |
| } |
| |
| bool _validateListOrSetSpread(SpreadElement element, DartObjectImpl value) { |
| var listValue = value.toListValue(); |
| var setValue = value.toSetValue(); |
| |
| if (listValue == null && setValue == null) { |
| if (value.isNull && _isNullableSpread(element)) { |
| return true; |
| } |
| verifier._errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET, |
| element.expression, |
| ); |
| return false; |
| } |
| |
| if (listValue != null) { |
| var elementType = value.type.typeArguments[0]; |
| if (verifier._implementsEqualsWhenNotAllowed(elementType)) { |
| verifier._errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_SET_ELEMENT_TYPE_IMPLEMENTS_EQUALS, |
| element, |
| [elementType], |
| ); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool _validateMapLiteralEntry(MapLiteralEntry entry) { |
| if (!forMap) return false; |
| |
| var keyExpression = entry.key; |
| var valueExpression = entry.value; |
| |
| if (isConst) { |
| var keyValue = verifier._validate( |
| keyExpression, |
| CompileTimeErrorCode.NON_CONSTANT_MAP_KEY, |
| ); |
| var valueValue = verifier._validate( |
| valueExpression, |
| CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE, |
| ); |
| |
| if (keyValue != null) { |
| var keyType = keyValue.type; |
| |
| if (!verifier._evaluationEngine |
| .runtimeTypeMatch(keyValue, mapKeyType)) { |
| verifier._errorReporter.reportErrorForNode( |
| StaticWarningCode.MAP_KEY_TYPE_NOT_ASSIGNABLE, |
| keyExpression, |
| [keyType, mapKeyType], |
| ); |
| } |
| |
| if (verifier._implementsEqualsWhenNotAllowed(keyType)) { |
| verifier._errorReporter.reportErrorForNode( |
| CompileTimeErrorCode |
| .CONST_MAP_KEY_EXPRESSION_TYPE_IMPLEMENTS_EQUALS, |
| keyExpression, |
| [keyType], |
| ); |
| } |
| |
| verifier._reportErrorIfFromDeferredLibrary( |
| keyExpression, |
| CompileTimeErrorCode.NON_CONSTANT_MAP_KEY_FROM_DEFERRED_LIBRARY, |
| ); |
| |
| if (!mapUniqueKeys.add(keyValue)) { |
| mapDuplicateKeyElements.add(keyExpression); |
| } |
| } |
| |
| if (valueValue != null) { |
| if (!verifier._evaluationEngine |
| .runtimeTypeMatch(valueValue, mapValueType)) { |
| verifier._errorReporter.reportErrorForNode( |
| StaticWarningCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE, |
| valueExpression, |
| [valueValue.type, mapValueType], |
| ); |
| } |
| |
| verifier._reportErrorIfFromDeferredLibrary( |
| valueExpression, |
| CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE_FROM_DEFERRED_LIBRARY, |
| ); |
| } |
| } else { |
| // Note: we throw the errors away because this isn't actually a const. |
| var nullErrorReporter = new ErrorReporter( |
| AnalysisErrorListener.NULL_LISTENER, |
| verifier._errorReporter.source, |
| ); |
| var keyValue = keyExpression.accept( |
| new ConstantVisitor(verifier._evaluationEngine, nullErrorReporter), |
| ); |
| |
| if (keyValue != null) { |
| if (!mapUniqueKeys.add(keyValue)) { |
| mapDuplicateKeyElements.add(keyExpression); |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool _validateMapSpread(SpreadElement element, DartObjectImpl value) { |
| if (value.isNull && _isNullableSpread(element)) { |
| return true; |
| } |
| Map<DartObject, DartObject> map = value.toMapValue(); |
| if (map != null) { |
| // TODO(brianwilkerson) Figure out how to improve the error messages. They |
| // currently point to the whole spread expression, but the key and/or |
| // value being referenced might not be located there (if it's referenced |
| // through a const variable). |
| for (var entry in map.entries) { |
| DartObjectImpl keyValue = entry.key; |
| if (keyValue != null) { |
| if (!mapUniqueKeys.add(keyValue)) { |
| mapDuplicateKeyElements.add(element.expression); |
| } |
| } |
| } |
| return true; |
| } |
| verifier._errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP, |
| element.expression, |
| ); |
| return false; |
| } |
| |
| bool _validateSetExpression(Expression expression, DartObjectImpl value) { |
| if (!verifier._evaluationEngine.runtimeTypeMatch(value, setElementType)) { |
| verifier._errorReporter.reportErrorForNode( |
| StaticWarningCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE, |
| expression, |
| [value.type, setElementType], |
| ); |
| return false; |
| } |
| |
| if (verifier._implementsEqualsWhenNotAllowed(value.type)) { |
| verifier._errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.CONST_SET_ELEMENT_TYPE_IMPLEMENTS_EQUALS, |
| expression, |
| [value.type], |
| ); |
| return false; |
| } |
| |
| verifier._reportErrorIfFromDeferredLibrary( |
| expression, |
| CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT_FROM_DEFERRED_LIBRARY, |
| ); |
| |
| if (!setUniqueValues.add(value)) { |
| setDuplicateElements.add(expression); |
| } |
| |
| return true; |
| } |
| |
| static bool _isNullableSpread(SpreadElement element) { |
| return element.spreadOperator.type == |
| TokenType.PERIOD_PERIOD_PERIOD_QUESTION; |
| } |
| } |