| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library analyzer.src.generated.resolver; |
| |
| import 'dart:collection'; |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/standard_resolution_map.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/type.dart'; |
| import 'package:analyzer/dart/element/visitor.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/exception/exception.dart'; |
| import 'package:analyzer/src/dart/ast/ast.dart'; |
| import 'package:analyzer/src/dart/ast/utilities.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/member.dart' show ConstructorMember; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/resolver/inheritance_manager.dart'; |
| import 'package:analyzer/src/dart/resolver/scope.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/generated/constant.dart'; |
| import 'package:analyzer/src/generated/element_resolver.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/error_verifier.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/generated/static_type_analyzer.dart'; |
| import 'package:analyzer/src/generated/testing/element_factory.dart'; |
| import 'package:analyzer/src/generated/type_system.dart'; |
| import 'package:analyzer/src/generated/utilities_dart.dart'; |
| |
| export 'package:analyzer/src/dart/resolver/inheritance_manager.dart'; |
| export 'package:analyzer/src/dart/resolver/scope.dart'; |
| export 'package:analyzer/src/generated/type_system.dart'; |
| |
| /** |
| * Instances of the class `BestPracticesVerifier` traverse an AST structure looking for |
| * violations of Dart best practices. |
| */ |
| class BestPracticesVerifier extends RecursiveAstVisitor<Object> { |
| // static String _HASHCODE_GETTER_NAME = "hashCode"; |
| |
| static String _NULL_TYPE_NAME = "Null"; |
| |
| static String _TO_INT_METHOD_NAME = "toInt"; |
| |
| /** |
| * The class containing the AST nodes being visited, or `null` if we are not in the scope of |
| * a class. |
| */ |
| ClassElementImpl _enclosingClass; |
| |
| /** |
| * A flag indicating whether a surrounding member (compilation unit or class) |
| * is deprecated. |
| */ |
| bool inDeprecatedMember; |
| |
| /** |
| * The error reporter by which errors will be reported. |
| */ |
| final ErrorReporter _errorReporter; |
| |
| /** |
| * The type [Null]. |
| */ |
| final InterfaceType _nullType; |
| |
| /** |
| * The type Future<Null>, which is needed for determining whether it is safe |
| * to have a bare "return;" in an async method. |
| */ |
| final InterfaceType _futureNullType; |
| |
| /** |
| * The type system primitives |
| */ |
| TypeSystem _typeSystem; |
| |
| /** |
| * The current library |
| */ |
| LibraryElement _currentLibrary; |
| |
| /** |
| * The inheritance manager used to find overridden methods. |
| */ |
| InheritanceManager _manager; |
| |
| /** |
| * Create a new instance of the [BestPracticesVerifier]. |
| * |
| * @param errorReporter the error reporter |
| */ |
| BestPracticesVerifier(this._errorReporter, TypeProvider typeProvider, |
| this._currentLibrary, this._manager, |
| {TypeSystem typeSystem}) |
| : _nullType = typeProvider.nullType, |
| _futureNullType = typeProvider.futureNullType, |
| _typeSystem = typeSystem ?? new TypeSystemImpl(typeProvider) { |
| inDeprecatedMember = _currentLibrary.isDeprecated; |
| } |
| |
| @override |
| Object visitAnnotation(Annotation node) { |
| ElementAnnotation element = |
| resolutionMap.elementAnnotationForAnnotation(node); |
| if (element?.isFactory == true) { |
| AstNode parent = node.parent; |
| if (parent is MethodDeclaration) { |
| _checkForInvalidFactory(parent); |
| } else { |
| _errorReporter |
| .reportErrorForNode(HintCode.INVALID_FACTORY_ANNOTATION, node, []); |
| } |
| } else if (element?.isImmutable == true) { |
| AstNode parent = node.parent; |
| if (parent is! ClassDeclaration) { |
| _errorReporter.reportErrorForNode( |
| HintCode.INVALID_IMMUTABLE_ANNOTATION, node, []); |
| } |
| } |
| return super.visitAnnotation(node); |
| } |
| |
| @override |
| Object visitArgumentList(ArgumentList node) { |
| for (Expression argument in node.arguments) { |
| ParameterElement parameter = argument.bestParameterElement; |
| if (parameter?.parameterKind == ParameterKind.POSITIONAL) { |
| _checkForDeprecatedMemberUse(parameter, argument); |
| } |
| } |
| _checkForArgumentTypesNotAssignableInList(node); |
| return super.visitArgumentList(node); |
| } |
| |
| @override |
| Object visitAsExpression(AsExpression node) { |
| _checkForUnnecessaryCast(node); |
| return super.visitAsExpression(node); |
| } |
| |
| @override |
| Object visitAssertInitializer(AssertInitializer node) { |
| _checkForPossibleNullCondition(node.condition); |
| return super.visitAssertInitializer(node); |
| } |
| |
| @override |
| Object visitAssertStatement(AssertStatement node) { |
| _checkForPossibleNullCondition(node.condition); |
| return super.visitAssertStatement(node); |
| } |
| |
| @override |
| Object visitAssignmentExpression(AssignmentExpression node) { |
| TokenType operatorType = node.operator.type; |
| if (operatorType == TokenType.EQ) { |
| _checkForUseOfVoidResult(node.rightHandSide); |
| _checkForInvalidAssignment(node.leftHandSide, node.rightHandSide); |
| } else { |
| _checkForDeprecatedMemberUse(node.bestElement, node); |
| } |
| return super.visitAssignmentExpression(node); |
| } |
| |
| @override |
| Object visitBinaryExpression(BinaryExpression node) { |
| _checkForDivisionOptimizationHint(node); |
| _checkForDeprecatedMemberUse(node.bestElement, node); |
| return super.visitBinaryExpression(node); |
| } |
| |
| @override |
| Object visitClassDeclaration(ClassDeclaration node) { |
| ClassElementImpl outerClass = _enclosingClass; |
| bool wasInDeprecatedMember = inDeprecatedMember; |
| ClassElement element = AbstractClassElementImpl.getImpl(node.element); |
| if (element != null && element.isDeprecated) { |
| inDeprecatedMember = true; |
| } |
| try { |
| _enclosingClass = element; |
| // Commented out until we decide that we want this hint in the analyzer |
| // checkForOverrideEqualsButNotHashCode(node); |
| _checkForImmutable(node); |
| return super.visitClassDeclaration(node); |
| } finally { |
| _enclosingClass = outerClass; |
| inDeprecatedMember = wasInDeprecatedMember; |
| } |
| } |
| |
| @override |
| Object visitConditionalExpression(ConditionalExpression node) { |
| _checkForPossibleNullCondition(node.condition); |
| return super.visitConditionalExpression(node); |
| } |
| |
| @override |
| Object visitConstructorDeclaration(ConstructorDeclaration node) { |
| if (resolutionMap.elementDeclaredByConstructorDeclaration(node).isFactory) { |
| if (node.body is BlockFunctionBody) { |
| // Check the block for a return statement, if not, create the hint. |
| if (!ExitDetector.exits(node.body)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.MISSING_RETURN, node, [node.returnType.name]); |
| } |
| } |
| } |
| return super.visitConstructorDeclaration(node); |
| } |
| |
| @override |
| Object visitDoStatement(DoStatement node) { |
| _checkForPossibleNullCondition(node.condition); |
| return super.visitDoStatement(node); |
| } |
| |
| @override |
| Object visitExportDirective(ExportDirective node) { |
| _checkForDeprecatedMemberUse(node.uriElement, node); |
| return super.visitExportDirective(node); |
| } |
| |
| @override |
| Object visitForStatement(ForStatement node) { |
| _checkForPossibleNullCondition(node.condition); |
| return super.visitForStatement(node); |
| } |
| |
| @override |
| Object visitFunctionDeclaration(FunctionDeclaration node) { |
| bool wasInDeprecatedMember = inDeprecatedMember; |
| ExecutableElement element = node.element; |
| if (element != null && element.isDeprecated) { |
| inDeprecatedMember = true; |
| } |
| try { |
| _checkForMissingReturn(node.returnType, node.functionExpression.body); |
| return super.visitFunctionDeclaration(node); |
| } finally { |
| inDeprecatedMember = wasInDeprecatedMember; |
| } |
| } |
| |
| @override |
| Object visitIfStatement(IfStatement node) { |
| _checkForPossibleNullCondition(node.condition); |
| return super.visitIfStatement(node); |
| } |
| |
| @override |
| Object visitImportDirective(ImportDirective node) { |
| _checkForDeprecatedMemberUse(node.uriElement, node); |
| ImportElement importElement = node.element; |
| if (importElement != null && importElement.isDeferred) { |
| _checkForLoadLibraryFunction(node, importElement); |
| } |
| return super.visitImportDirective(node); |
| } |
| |
| @override |
| Object visitIndexExpression(IndexExpression node) { |
| _checkForDeprecatedMemberUse(node.bestElement, node); |
| return super.visitIndexExpression(node); |
| } |
| |
| @override |
| Object visitInstanceCreationExpression(InstanceCreationExpression node) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| return super.visitInstanceCreationExpression(node); |
| } |
| |
| @override |
| Object visitIsExpression(IsExpression node) { |
| _checkAllTypeChecks(node); |
| return super.visitIsExpression(node); |
| } |
| |
| @override |
| Object visitMethodDeclaration(MethodDeclaration node) { |
| bool wasInDeprecatedMember = inDeprecatedMember; |
| ExecutableElement element = node.element; |
| if (element != null && element.isDeprecated) { |
| inDeprecatedMember = true; |
| } |
| try { |
| // This was determined to not be a good hint, see: dartbug.com/16029 |
| //checkForOverridingPrivateMember(node); |
| _checkForMissingReturn(node.returnType, node.body); |
| _checkForUnnecessaryNoSuchMethod(node); |
| return super.visitMethodDeclaration(node); |
| } finally { |
| inDeprecatedMember = wasInDeprecatedMember; |
| } |
| } |
| |
| @override |
| Object visitMethodInvocation(MethodInvocation node) { |
| Expression realTarget = node.realTarget; |
| _checkForAbstractSuperMemberReference(realTarget, node.methodName); |
| _checkForCanBeNullAfterNullAware( |
| realTarget, node.operator, null, node.methodName); |
| DartType staticInvokeType = node.staticInvokeType; |
| if (staticInvokeType is InterfaceType) { |
| MethodElement methodElement = staticInvokeType.lookUpMethod( |
| FunctionElement.CALL_METHOD_NAME, _currentLibrary); |
| _checkForDeprecatedMemberUse(methodElement, node); |
| } |
| return super.visitMethodInvocation(node); |
| } |
| |
| @override |
| Object visitPostfixExpression(PostfixExpression node) { |
| _checkForDeprecatedMemberUse(node.bestElement, node); |
| return super.visitPostfixExpression(node); |
| } |
| |
| @override |
| Object visitPrefixExpression(PrefixExpression node) { |
| _checkForDeprecatedMemberUse(node.bestElement, node); |
| return super.visitPrefixExpression(node); |
| } |
| |
| @override |
| Object visitPropertyAccess(PropertyAccess node) { |
| Expression realTarget = node.realTarget; |
| _checkForAbstractSuperMemberReference(realTarget, node.propertyName); |
| _checkForCanBeNullAfterNullAware( |
| realTarget, node.operator, node.propertyName, null); |
| return super.visitPropertyAccess(node); |
| } |
| |
| @override |
| Object visitRedirectingConstructorInvocation( |
| RedirectingConstructorInvocation node) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| return super.visitRedirectingConstructorInvocation(node); |
| } |
| |
| @override |
| Object visitSimpleIdentifier(SimpleIdentifier node) { |
| _checkForDeprecatedMemberUseAtIdentifier(node); |
| _checkForInvalidProtectedMemberAccess(node); |
| return super.visitSimpleIdentifier(node); |
| } |
| |
| @override |
| Object visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| _checkForDeprecatedMemberUse(node.staticElement, node); |
| return super.visitSuperConstructorInvocation(node); |
| } |
| |
| @override |
| Object visitVariableDeclaration(VariableDeclaration node) { |
| _checkForUseOfVoidResult(node.initializer); |
| _checkForInvalidAssignment(node.name, node.initializer); |
| return super.visitVariableDeclaration(node); |
| } |
| |
| @override |
| Object visitWhileStatement(WhileStatement node) { |
| _checkForPossibleNullCondition(node.condition); |
| return super.visitWhileStatement(node); |
| } |
| |
| /** |
| * Check for the passed is expression for the unnecessary type check hint codes as well as null |
| * checks expressed using an is expression. |
| * |
| * @param node the is expression to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * See [HintCode.TYPE_CHECK_IS_NOT_NULL], [HintCode.TYPE_CHECK_IS_NULL], |
| * [HintCode.UNNECESSARY_TYPE_CHECK_TRUE], and |
| * [HintCode.UNNECESSARY_TYPE_CHECK_FALSE]. |
| */ |
| bool _checkAllTypeChecks(IsExpression node) { |
| Expression expression = node.expression; |
| TypeAnnotation typeName = node.type; |
| DartType lhsType = expression.staticType; |
| DartType rhsType = typeName.type; |
| if (lhsType == null || rhsType == null) { |
| return false; |
| } |
| String rhsNameStr = typeName is TypeName ? typeName.name.name : null; |
| // if x is dynamic |
| if (rhsType.isDynamic && rhsNameStr == Keyword.DYNAMIC.syntax) { |
| if (node.notOperator == null) { |
| // the is case |
| _errorReporter.reportErrorForNode( |
| HintCode.UNNECESSARY_TYPE_CHECK_TRUE, node); |
| } else { |
| // the is not case |
| _errorReporter.reportErrorForNode( |
| HintCode.UNNECESSARY_TYPE_CHECK_FALSE, node); |
| } |
| return true; |
| } |
| Element rhsElement = rhsType.element; |
| LibraryElement libraryElement = rhsElement?.library; |
| if (libraryElement != null && libraryElement.isDartCore) { |
| // if x is Object or null is Null |
| if (rhsType.isObject || |
| (expression is NullLiteral && rhsNameStr == _NULL_TYPE_NAME)) { |
| if (node.notOperator == null) { |
| // the is case |
| _errorReporter.reportErrorForNode( |
| HintCode.UNNECESSARY_TYPE_CHECK_TRUE, node); |
| } else { |
| // the is not case |
| _errorReporter.reportErrorForNode( |
| HintCode.UNNECESSARY_TYPE_CHECK_FALSE, node); |
| } |
| return true; |
| } else if (rhsNameStr == _NULL_TYPE_NAME) { |
| if (node.notOperator == null) { |
| // the is case |
| _errorReporter.reportErrorForNode(HintCode.TYPE_CHECK_IS_NULL, node); |
| } else { |
| // the is not case |
| _errorReporter.reportErrorForNode( |
| HintCode.TYPE_CHECK_IS_NOT_NULL, node); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void _checkForAbstractSuperMemberReference( |
| Expression target, SimpleIdentifier name) { |
| if (target is SuperExpression && |
| !_currentLibrary.context.analysisOptions.enableSuperMixins) { |
| Element element = name.staticElement; |
| if (element is ExecutableElement && element.isAbstract) { |
| if (!_enclosingClass.hasNoSuchMethod) { |
| ExecutableElement concrete = null; |
| if (element.kind == ElementKind.METHOD) { |
| concrete = _enclosingClass.lookUpInheritedConcreteMethod( |
| element.displayName, _currentLibrary); |
| } else if (element.kind == ElementKind.GETTER) { |
| concrete = _enclosingClass.lookUpInheritedConcreteGetter( |
| element.displayName, _currentLibrary); |
| } else if (element.kind == ElementKind.SETTER) { |
| concrete = _enclosingClass.lookUpInheritedConcreteSetter( |
| element.displayName, _currentLibrary); |
| } |
| if (concrete == null) { |
| _errorReporter.reportTypeErrorForNode( |
| HintCode.ABSTRACT_SUPER_MEMBER_REFERENCE, |
| name, |
| [element.kind.displayName, name.name]); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * This verifies that the passed expression can be assigned to its corresponding parameters. |
| * |
| * This method corresponds to ErrorVerifier.checkForArgumentTypeNotAssignable. |
| * |
| * TODO (jwren) In the ErrorVerifier there are other warnings that we could have a corresponding |
| * hint for: see other callers of ErrorVerifier.checkForArgumentTypeNotAssignable(..). |
| * |
| * @param expression the expression to evaluate |
| * @param expectedStaticType the expected static type of the parameter |
| * @param actualStaticType the actual static type of the argument |
| * @param expectedPropagatedType the expected propagated type of the parameter, may be |
| * `null` |
| * @param actualPropagatedType the expected propagated type of the parameter, may be `null` |
| * @return `true` if and only if an hint code is generated on the passed node |
| * See [HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE]. |
| */ |
| bool _checkForArgumentTypeNotAssignable( |
| Expression expression, |
| DartType expectedStaticType, |
| DartType actualStaticType, |
| DartType expectedPropagatedType, |
| DartType actualPropagatedType, |
| ErrorCode hintCode) { |
| // |
| // Warning case: test static type information |
| // |
| if (actualStaticType != null && expectedStaticType != null) { |
| if (!_typeSystem.isAssignableTo(actualStaticType, expectedStaticType)) { |
| // A warning was created in the ErrorVerifier, return false, don't |
| // create a hint when a warning has already been created. |
| return false; |
| } |
| } |
| // |
| // Hint case: test propagated type information |
| // |
| // Compute the best types to use. |
| DartType expectedBestType = expectedPropagatedType ?? expectedStaticType; |
| DartType actualBestType = actualPropagatedType ?? actualStaticType; |
| if (actualBestType != null && expectedBestType != null) { |
| if (!_typeSystem.isAssignableTo(actualBestType, expectedBestType)) { |
| _errorReporter.reportTypeErrorForNode( |
| hintCode, expression, [actualBestType, expectedBestType]); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * This verifies that the passed argument can be assigned to its corresponding parameter. |
| * |
| * This method corresponds to ErrorCode.checkForArgumentTypeNotAssignableForArgument. |
| * |
| * @param argument the argument to evaluate |
| * @return `true` if and only if an hint code is generated on the passed node |
| * See [HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE]. |
| */ |
| bool _checkForArgumentTypeNotAssignableForArgument(Expression argument) { |
| if (argument == null) { |
| return false; |
| } |
| ParameterElement staticParameterElement = argument.staticParameterElement; |
| DartType staticParameterType = staticParameterElement?.type; |
| ParameterElement propagatedParameterElement = |
| argument.propagatedParameterElement; |
| DartType propagatedParameterType = propagatedParameterElement?.type; |
| return _checkForArgumentTypeNotAssignableWithExpectedTypes( |
| argument, |
| staticParameterType, |
| propagatedParameterType, |
| HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE); |
| } |
| |
| /** |
| * This verifies that the passed expression can be assigned to its corresponding parameters. |
| * |
| * This method corresponds to ErrorCode.checkForArgumentTypeNotAssignableWithExpectedTypes. |
| * |
| * @param expression the expression to evaluate |
| * @param expectedStaticType the expected static type |
| * @param expectedPropagatedType the expected propagated type, may be `null` |
| * @return `true` if and only if an hint code is generated on the passed node |
| * See [HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE]. |
| */ |
| bool _checkForArgumentTypeNotAssignableWithExpectedTypes( |
| Expression expression, |
| DartType expectedStaticType, |
| DartType expectedPropagatedType, |
| ErrorCode errorCode) => |
| _checkForArgumentTypeNotAssignable( |
| expression, |
| expectedStaticType, |
| expression.staticType, |
| expectedPropagatedType, |
| expression.propagatedType, |
| errorCode); |
| |
| /** |
| * This verifies that the passed arguments can be assigned to their corresponding parameters. |
| * |
| * This method corresponds to ErrorCode.checkForArgumentTypesNotAssignableInList. |
| * |
| * @param node the arguments to evaluate |
| * @return `true` if and only if an hint code is generated on the passed node |
| * See [HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE]. |
| */ |
| bool _checkForArgumentTypesNotAssignableInList(ArgumentList argumentList) { |
| if (argumentList == null) { |
| return false; |
| } |
| bool problemReported = false; |
| for (Expression argument in argumentList.arguments) { |
| if (_checkForArgumentTypeNotAssignableForArgument(argument)) { |
| problemReported = true; |
| } |
| } |
| return problemReported; |
| } |
| |
| /** |
| * Produce a hint if the given [target] could have a value of `null`, and |
| * [identifier] is not a name of a getter or a method that exists in the |
| * class [Null]. |
| */ |
| void _checkForCanBeNullAfterNullAware(Expression target, Token operator, |
| SimpleIdentifier propertyName, SimpleIdentifier methodName) { |
| if (operator?.type == TokenType.QUESTION_PERIOD) { |
| return; |
| } |
| bool isNullTypeMember() { |
| if (propertyName != null) { |
| String name = propertyName.name; |
| return _nullType.lookUpGetter(name, _currentLibrary) != null; |
| } |
| if (methodName != null) { |
| String name = methodName.name; |
| return _nullType.lookUpMethod(name, _currentLibrary) != null; |
| } |
| return false; |
| } |
| |
| target = target?.unParenthesized; |
| if (target is MethodInvocation) { |
| if (target.operator?.type == TokenType.QUESTION_PERIOD && |
| !isNullTypeMember()) { |
| _errorReporter.reportErrorForNode( |
| HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, target); |
| } |
| } else if (target is PropertyAccess) { |
| if (target.operator.type == TokenType.QUESTION_PERIOD && |
| !isNullTypeMember()) { |
| _errorReporter.reportErrorForNode( |
| HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, target); |
| } |
| } |
| } |
| |
| /** |
| * Given some [Element], look at the associated metadata and report the use of the member if |
| * it is declared as deprecated. |
| * |
| * @param element some element to check for deprecated use of |
| * @param node the node use for the location of the error |
| * @return `true` if and only if a hint code is generated on the passed node |
| * See [HintCode.DEPRECATED_MEMBER_USE]. |
| */ |
| void _checkForDeprecatedMemberUse(Element element, AstNode node) { |
| bool isDeprecated(Element element) { |
| if (element == null) { |
| return false; |
| } else if (element is PropertyAccessorElement && element.isSynthetic) { |
| // TODO(brianwilkerson) Why isn't this the implementation for PropertyAccessorElement? |
| Element variable = element.variable; |
| if (variable == null) { |
| return false; |
| } |
| return variable.isDeprecated; |
| } |
| return element.isDeprecated; |
| } |
| |
| if (!inDeprecatedMember && isDeprecated(element)) { |
| String displayName = element.displayName; |
| if (element is ConstructorElement) { |
| // TODO(jwren) We should modify ConstructorElement.getDisplayName(), |
| // or have the logic centralized elsewhere, instead of doing this logic |
| // here. |
| displayName = element.enclosingElement.displayName; |
| if (!element.displayName.isEmpty) { |
| displayName = "$displayName.${element.displayName}"; |
| } |
| } else if (displayName == FunctionElement.CALL_METHOD_NAME && |
| node is MethodInvocation && |
| node.staticInvokeType is InterfaceType) { |
| displayName = "${resolutionMap |
| .staticInvokeTypeForInvocationExpression(node) |
| .displayName}.${element.displayName}"; |
| } |
| _errorReporter.reportErrorForNode( |
| HintCode.DEPRECATED_MEMBER_USE, node, [displayName]); |
| } |
| } |
| |
| /** |
| * For [SimpleIdentifier]s, only call [checkForDeprecatedMemberUse] |
| * if the node is not in a declaration context. |
| * |
| * Also, if the identifier is a constructor name in a constructor invocation, then calls to the |
| * deprecated constructor will be caught by |
| * [visitInstanceCreationExpression] and |
| * [visitSuperConstructorInvocation], and can be ignored by |
| * this visit method. |
| * |
| * @param identifier some simple identifier to check for deprecated use of |
| * @return `true` if and only if a hint code is generated on the passed node |
| * See [HintCode.DEPRECATED_MEMBER_USE]. |
| */ |
| void _checkForDeprecatedMemberUseAtIdentifier(SimpleIdentifier identifier) { |
| if (identifier.inDeclarationContext()) { |
| return; |
| } |
| AstNode parent = identifier.parent; |
| if ((parent is ConstructorName && identical(identifier, parent.name)) || |
| (parent is ConstructorDeclaration && |
| identical(identifier, parent.returnType)) || |
| (parent is SuperConstructorInvocation && |
| identical(identifier, parent.constructorName)) || |
| parent is HideCombinator) { |
| return; |
| } |
| _checkForDeprecatedMemberUse(identifier.bestElement, identifier); |
| } |
| |
| /** |
| * Check for the passed binary expression for the [HintCode.DIVISION_OPTIMIZATION]. |
| * |
| * @param node the binary expression to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * See [HintCode.DIVISION_OPTIMIZATION]. |
| */ |
| bool _checkForDivisionOptimizationHint(BinaryExpression node) { |
| // Return if the operator is not '/' |
| if (node.operator.type != TokenType.SLASH) { |
| return false; |
| } |
| // Return if the '/' operator is not defined in core, or if we don't know |
| // its static or propagated type |
| MethodElement methodElement = node.bestElement; |
| if (methodElement == null) { |
| return false; |
| } |
| LibraryElement libraryElement = methodElement.library; |
| if (libraryElement != null && !libraryElement.isDartCore) { |
| return false; |
| } |
| // Report error if the (x/y) has toInt() invoked on it |
| AstNode parent = node.parent; |
| if (parent is ParenthesizedExpression) { |
| ParenthesizedExpression parenthesizedExpression = |
| _wrapParenthesizedExpression(parent); |
| AstNode grandParent = parenthesizedExpression.parent; |
| if (grandParent is MethodInvocation) { |
| if (_TO_INT_METHOD_NAME == grandParent.methodName.name && |
| grandParent.argumentList.arguments.isEmpty) { |
| _errorReporter.reportErrorForNode( |
| HintCode.DIVISION_OPTIMIZATION, grandParent); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| void _checkForImmutable(ClassDeclaration node) { |
| /** |
| * Return `true` if the given class [element] is annotated with the |
| * `@immutable` annotation. |
| */ |
| bool isImmutable(ClassElement element) { |
| for (ElementAnnotation annotation in element.metadata) { |
| if (annotation.isImmutable) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return `true` if the given class [element] or any superclass of it is |
| * annotated with the `@immutable` annotation. |
| */ |
| bool isOrInheritsImmutable( |
| ClassElement element, HashSet<ClassElement> visited) { |
| if (visited.add(element)) { |
| if (isImmutable(element)) { |
| return true; |
| } |
| for (InterfaceType interface in element.mixins) { |
| if (isOrInheritsImmutable(interface.element, visited)) { |
| return true; |
| } |
| } |
| for (InterfaceType mixin in element.interfaces) { |
| if (isOrInheritsImmutable(mixin.element, visited)) { |
| return true; |
| } |
| } |
| if (element.supertype != null) { |
| return isOrInheritsImmutable(element.supertype.element, visited); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return `true` if the given class [element] defines a non-final instance |
| * field. |
| */ |
| bool hasNonFinalInstanceField(ClassElement element) { |
| for (FieldElement field in element.fields) { |
| if (!field.isSynthetic && !field.isFinal && !field.isStatic) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return `true` if the given class [element] defines or inherits a |
| * non-final field. |
| */ |
| bool hasOrInheritsNonFinalInstanceField( |
| ClassElement element, HashSet<ClassElement> visited) { |
| if (visited.add(element)) { |
| if (hasNonFinalInstanceField(element)) { |
| return true; |
| } |
| for (InterfaceType mixin in element.mixins) { |
| if (hasNonFinalInstanceField(mixin.element)) { |
| return true; |
| } |
| } |
| if (element.supertype != null) { |
| return hasOrInheritsNonFinalInstanceField( |
| element.supertype.element, visited); |
| } |
| } |
| return false; |
| } |
| |
| ClassElement element = node.element; |
| if (isOrInheritsImmutable(element, new HashSet<ClassElement>()) && |
| hasOrInheritsNonFinalInstanceField( |
| element, new HashSet<ClassElement>())) { |
| _errorReporter.reportErrorForNode(HintCode.MUST_BE_IMMUTABLE, node.name); |
| } |
| } |
| |
| /** |
| * This verifies that the passed left hand side and right hand side represent a valid assignment. |
| * |
| * This method corresponds to ErrorVerifier.checkForInvalidAssignment. |
| * |
| * @param lhs the left hand side expression |
| * @param rhs the right hand side expression |
| * @return `true` if and only if an error code is generated on the passed node |
| * See [HintCode.INVALID_ASSIGNMENT]. |
| */ |
| bool _checkForInvalidAssignment(Expression lhs, Expression rhs) { |
| if (lhs == null || rhs == null) { |
| return false; |
| } |
| VariableElement leftVariableElement = ErrorVerifier.getVariableElement(lhs); |
| DartType leftType = (leftVariableElement == null) |
| ? ErrorVerifier.getStaticType(lhs) |
| : leftVariableElement.type; |
| DartType staticRightType = ErrorVerifier.getStaticType(rhs); |
| if (!_typeSystem.isAssignableTo(staticRightType, leftType)) { |
| // The warning was generated on this rhs |
| return false; |
| } |
| // Test for, and then generate the hint |
| DartType bestRightType = rhs.bestType; |
| if (leftType != null && bestRightType != null) { |
| if (!_typeSystem.isAssignableTo(bestRightType, leftType)) { |
| _errorReporter.reportTypeErrorForNode( |
| HintCode.INVALID_ASSIGNMENT, rhs, [bestRightType, leftType]); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void _checkForInvalidFactory(MethodDeclaration decl) { |
| // Check declaration. |
| // Note that null return types are expected to be flagged by other analyses. |
| DartType returnType = decl.returnType?.type; |
| if (returnType is VoidType) { |
| _errorReporter.reportErrorForNode(HintCode.INVALID_FACTORY_METHOD_DECL, |
| decl.name, [decl.name.toString()]); |
| return; |
| } |
| |
| // Check implementation. |
| |
| FunctionBody body = decl.body; |
| if (body is EmptyFunctionBody) { |
| // Abstract methods are OK. |
| return; |
| } |
| |
| // `new Foo()` or `null`. |
| bool factoryExpression(Expression expression) => |
| expression is InstanceCreationExpression || expression is NullLiteral; |
| |
| if (body is ExpressionFunctionBody && factoryExpression(body.expression)) { |
| return; |
| } else if (body is BlockFunctionBody) { |
| NodeList<Statement> statements = body.block.statements; |
| if (statements.isNotEmpty) { |
| Statement last = statements.last; |
| if (last is ReturnStatement && factoryExpression(last.expression)) { |
| return; |
| } |
| } |
| } |
| |
| _errorReporter.reportErrorForNode(HintCode.INVALID_FACTORY_METHOD_IMPL, |
| decl.name, [decl.name.toString()]); |
| } |
| |
| /** |
| * Produces a hint if the given identifier is a protected closure, field or |
| * getter/setter, method closure or invocation accessed outside a subclass. |
| */ |
| void _checkForInvalidProtectedMemberAccess(SimpleIdentifier identifier) { |
| if (identifier.inDeclarationContext()) { |
| return; |
| } |
| |
| bool isProtected(Element element) { |
| if (element is PropertyAccessorElement && |
| element.enclosingElement is ClassElement && |
| (element.isProtected || element.variable.isProtected)) { |
| return true; |
| } |
| if (element is MethodElement && |
| element.enclosingElement is ClassElement && |
| element.isProtected) { |
| return true; |
| } |
| return false; |
| } |
| |
| bool inCommentReference(SimpleIdentifier identifier) => |
| identifier.getAncestor((AstNode node) => node is CommentReference) != |
| null; |
| |
| bool inCurrentLibrary(Element element) => |
| element.library == _currentLibrary; |
| |
| Element element = identifier.bestElement; |
| if (isProtected(element) && |
| !inCurrentLibrary(element) && |
| !inCommentReference(identifier)) { |
| ClassElement definingClass = element.enclosingElement; |
| ClassDeclaration accessingClass = |
| identifier.getAncestor((AstNode node) => node is ClassDeclaration); |
| if (accessingClass == null || |
| !_hasTypeOrSuperType(accessingClass.element, definingClass.type)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.INVALID_USE_OF_PROTECTED_MEMBER, |
| identifier, |
| [identifier.name.toString(), definingClass.name]); |
| } |
| } |
| } |
| |
| /** |
| * Check that the imported library does not define a loadLibrary function. The import has already |
| * been determined to be deferred when this is called. |
| * |
| * @param node the import directive to evaluate |
| * @param importElement the [ImportElement] retrieved from the node |
| * @return `true` if and only if an error code is generated on the passed node |
| * See [CompileTimeErrorCode.IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION]. |
| */ |
| bool _checkForLoadLibraryFunction( |
| ImportDirective node, ImportElement importElement) { |
| LibraryElement importedLibrary = importElement.importedLibrary; |
| if (importedLibrary == null) { |
| return false; |
| } |
| if (importedLibrary.hasLoadLibraryFunction) { |
| _errorReporter.reportErrorForNode( |
| HintCode.IMPORT_DEFERRED_LIBRARY_WITH_LOAD_FUNCTION, |
| node, |
| [importedLibrary.name]); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Generate a hint for functions or methods that have a return type, but do not have a return |
| * statement on all branches. At the end of blocks with no return, Dart implicitly returns |
| * `null`, avoiding these implicit returns is considered a best practice. |
| * |
| * Note: for async functions/methods, this hint only applies when the |
| * function has a return type that Future<Null> is not assignable to. |
| * |
| * @param node the binary expression to check |
| * @param body the function body |
| * @return `true` if and only if a hint code is generated on the passed node |
| * See [HintCode.MISSING_RETURN]. |
| */ |
| void _checkForMissingReturn(TypeAnnotation returnType, FunctionBody body) { |
| // Check that the method or function has a return type, and a function body |
| if (returnType == null || body == null) { |
| return; |
| } |
| // Check that the body is a BlockFunctionBody |
| if (body is BlockFunctionBody) { |
| // Generators are never required to have a return statement. |
| if (body.isGenerator) { |
| return; |
| } |
| // Check that the type is resolvable, and is not "void" |
| DartType returnTypeType = returnType.type; |
| if (returnTypeType == null || returnTypeType.isVoid) { |
| return; |
| } |
| // For async, give no hint if the return type does not matter, i.e. |
| // dynamic, Future<Null> or Future<dynamic>. |
| if (body.isAsynchronous) { |
| if (returnTypeType.isDynamic) { |
| return; |
| } |
| if (returnTypeType is InterfaceType && |
| returnTypeType.isDartAsyncFuture) { |
| DartType futureArgument = returnTypeType.typeArguments[0]; |
| if (futureArgument.isDynamic || |
| futureArgument.isDartCoreNull || |
| futureArgument.isObject) { |
| return; |
| } |
| } |
| } |
| // Check the block for a return statement, if not, create the hint |
| if (!ExitDetector.exits(body)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.MISSING_RETURN, returnType, [returnTypeType.displayName]); |
| } |
| } |
| } |
| |
| /** |
| * Produce a hint if the given [condition] could have a value of `null`. |
| */ |
| void _checkForPossibleNullCondition(Expression condition) { |
| condition = condition?.unParenthesized; |
| if (condition is BinaryExpression) { |
| _checkForPossibleNullConditionInBinaryExpression(condition); |
| } else if (condition is PrefixExpression) { |
| _checkForPossibleNullConditionInPrefixExpression(condition); |
| } else { |
| _checkForPossibleNullConditionInSimpleExpression(condition); |
| } |
| } |
| |
| /** |
| * Produce a hint if any of the parts of the given binary [condition] could |
| * have a value of `null`. |
| */ |
| void _checkForPossibleNullConditionInBinaryExpression( |
| BinaryExpression condition) { |
| TokenType type = condition.operator?.type; |
| if (type == TokenType.AMPERSAND_AMPERSAND || type == TokenType.BAR_BAR) { |
| _checkForPossibleNullCondition(condition.leftOperand); |
| _checkForPossibleNullCondition(condition.rightOperand); |
| } |
| } |
| |
| /** |
| * Produce a hint if the operand of the given prefix [condition] could |
| * have a value of `null`. |
| */ |
| void _checkForPossibleNullConditionInPrefixExpression( |
| PrefixExpression condition) { |
| if (condition.operator?.type == TokenType.BANG) { |
| _checkForPossibleNullCondition(condition.operand); |
| } |
| } |
| |
| /** |
| * Produce a hint if the given [condition] could have a value of `null`. |
| */ |
| void _checkForPossibleNullConditionInSimpleExpression(Expression condition) { |
| if (condition is MethodInvocation) { |
| if (condition.operator?.type == TokenType.QUESTION_PERIOD) { |
| _errorReporter.reportErrorForNode( |
| HintCode.NULL_AWARE_IN_CONDITION, condition); |
| } |
| } else if (condition is PropertyAccess) { |
| if (condition.operator?.type == TokenType.QUESTION_PERIOD) { |
| _errorReporter.reportErrorForNode( |
| HintCode.NULL_AWARE_IN_CONDITION, condition); |
| } |
| } |
| } |
| |
| /** |
| * Check for the passed as expression for the [HintCode.UNNECESSARY_CAST] hint code. |
| * |
| * @param node the as expression to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * See [HintCode.UNNECESSARY_CAST]. |
| */ |
| bool _checkForUnnecessaryCast(AsExpression node) { |
| // TODO(jwren) After dartbug.com/13732, revisit this, we should be able to |
| // remove the (x is! TypeParameterType) checks. |
| AstNode parent = node.parent; |
| if (parent is ConditionalExpression && |
| (node == parent.thenExpression || node == parent.elseExpression)) { |
| Expression thenExpression = parent.thenExpression; |
| DartType thenType; |
| if (thenExpression is AsExpression) { |
| thenType = thenExpression.expression.staticType; |
| } else { |
| thenType = thenExpression.staticType; |
| } |
| Expression elseExpression = parent.elseExpression; |
| DartType elseType; |
| if (elseExpression is AsExpression) { |
| elseType = elseExpression.expression.staticType; |
| } else { |
| elseType = elseExpression.staticType; |
| } |
| if (thenType != null && |
| elseType != null && |
| !thenType.isDynamic && |
| !elseType.isDynamic && |
| !thenType.isMoreSpecificThan(elseType) && |
| !elseType.isMoreSpecificThan(thenType)) { |
| return false; |
| } |
| } |
| DartType lhsType = node.expression.staticType; |
| DartType rhsType = node.type.type; |
| if (lhsType != null && |
| rhsType != null && |
| !lhsType.isDynamic && |
| !rhsType.isDynamic && |
| lhsType.isMoreSpecificThan(rhsType)) { |
| _errorReporter.reportErrorForNode(HintCode.UNNECESSARY_CAST, node); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Generate a hint for `noSuchMethod` methods that do nothing except of |
| * calling another `noSuchMethod` that is not defined by `Object`. |
| * |
| * @return `true` if and only if a hint code is generated on the passed node |
| * See [HintCode.UNNECESSARY_NO_SUCH_METHOD]. |
| */ |
| bool _checkForUnnecessaryNoSuchMethod(MethodDeclaration node) { |
| if (node.name.name != FunctionElement.NO_SUCH_METHOD_METHOD_NAME) { |
| return false; |
| } |
| bool isNonObjectNoSuchMethodInvocation(Expression invocation) { |
| if (invocation is MethodInvocation && |
| invocation.target is SuperExpression && |
| invocation.argumentList.arguments.length == 1) { |
| SimpleIdentifier name = invocation.methodName; |
| if (name.name == FunctionElement.NO_SUCH_METHOD_METHOD_NAME) { |
| Element methodElement = name.staticElement; |
| Element classElement = methodElement?.enclosingElement; |
| return methodElement is MethodElement && |
| classElement is ClassElement && |
| !classElement.type.isObject; |
| } |
| } |
| return false; |
| } |
| |
| FunctionBody body = node.body; |
| if (body is ExpressionFunctionBody) { |
| if (isNonObjectNoSuchMethodInvocation(body.expression)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.UNNECESSARY_NO_SUCH_METHOD, node); |
| return true; |
| } |
| } else if (body is BlockFunctionBody) { |
| List<Statement> statements = body.block.statements; |
| if (statements.length == 1) { |
| Statement returnStatement = statements.first; |
| if (returnStatement is ReturnStatement && |
| isNonObjectNoSuchMethodInvocation(returnStatement.expression)) { |
| _errorReporter.reportErrorForNode( |
| HintCode.UNNECESSARY_NO_SUCH_METHOD, node); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check for situations where the result of a method or function is used, when |
| * it returns 'void'. |
| * |
| * See [HintCode.USE_OF_VOID_RESULT]. |
| */ |
| void _checkForUseOfVoidResult(Expression expression) { |
| // TODO(jwren) Many other situations of use could be covered. We currently |
| // cover the cases var x = m() and x = m(), but we could also cover cases |
| // such as m().x, m()[k], a + m(), f(m()), return m(). |
| if (expression is MethodInvocation) { |
| if (identical(expression.staticType, VoidTypeImpl.instance)) { |
| SimpleIdentifier methodName = expression.methodName; |
| _errorReporter.reportErrorForNode( |
| HintCode.USE_OF_VOID_RESULT, methodName, [methodName.name]); |
| } |
| } |
| } |
| |
| /** |
| * Check for the passed class declaration for the |
| * [HintCode.OVERRIDE_EQUALS_BUT_NOT_HASH_CODE] hint code. |
| * |
| * @param node the class declaration to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * See [HintCode.OVERRIDE_EQUALS_BUT_NOT_HASH_CODE]. |
| */ |
| // bool _checkForOverrideEqualsButNotHashCode(ClassDeclaration node) { |
| // ClassElement classElement = node.element; |
| // if (classElement == null) { |
| // return false; |
| // } |
| // MethodElement equalsOperatorMethodElement = |
| // classElement.getMethod(sc.TokenType.EQ_EQ.lexeme); |
| // if (equalsOperatorMethodElement != null) { |
| // PropertyAccessorElement hashCodeElement = |
| // classElement.getGetter(_HASHCODE_GETTER_NAME); |
| // if (hashCodeElement == null) { |
| // _errorReporter.reportErrorForNode( |
| // HintCode.OVERRIDE_EQUALS_BUT_NOT_HASH_CODE, |
| // node.name, |
| // [classElement.displayName]); |
| // return true; |
| // } |
| // } |
| // return false; |
| // } |
| |
| bool _hasTypeOrSuperType(ClassElement element, InterfaceType type) { |
| if (element == null) { |
| return false; |
| } |
| ClassElement typeElement = type.element; |
| return element == typeElement || |
| element.allSupertypes |
| .any((InterfaceType t) => t.element == typeElement); |
| } |
| |
| /** |
| * Given a parenthesized expression, this returns the parent (or recursively grand-parent) of the |
| * expression that is a parenthesized expression, but whose parent is not a parenthesized |
| * expression. |
| * |
| * For example given the code `(((e)))`: `(e) -> (((e)))`. |
| * |
| * @param parenthesizedExpression some expression whose parent is a parenthesized expression |
| * @return the first parent or grand-parent that is a parenthesized expression, that does not have |
| * a parenthesized expression parent |
| */ |
| static ParenthesizedExpression _wrapParenthesizedExpression( |
| ParenthesizedExpression parenthesizedExpression) { |
| AstNode parent = parenthesizedExpression.parent; |
| if (parent is ParenthesizedExpression) { |
| return _wrapParenthesizedExpression(parent); |
| } |
| return parenthesizedExpression; |
| } |
| } |
| |
| /** |
| * Utilities for [LibraryElementImpl] building. |
| */ |
| class BuildLibraryElementUtils { |
| /** |
| * Look through all of the compilation units defined for the given [library], |
| * looking for getters and setters that are defined in different compilation |
| * units but that have the same names. If any are found, make sure that they |
| * have the same variable element. |
| */ |
| static void patchTopLevelAccessors(LibraryElementImpl library) { |
| // Without parts getters/setters already share the same variable element. |
| List<CompilationUnitElement> parts = library.parts; |
| if (parts.isEmpty) { |
| return; |
| } |
| // Collect getters and setters. |
| HashMap<String, PropertyAccessorElement> getters = |
| new HashMap<String, PropertyAccessorElement>(); |
| List<PropertyAccessorElement> setters = <PropertyAccessorElement>[]; |
| _collectAccessors(getters, setters, library.definingCompilationUnit); |
| int partLength = parts.length; |
| for (int i = 0; i < partLength; i++) { |
| CompilationUnitElement unit = parts[i]; |
| _collectAccessors(getters, setters, unit); |
| } |
| // Move every setter to the corresponding getter's variable (if exists). |
| int setterLength = setters.length; |
| for (int j = 0; j < setterLength; j++) { |
| PropertyAccessorElement setter = setters[j]; |
| PropertyAccessorElement getter = getters[setter.displayName]; |
| if (getter != null) { |
| TopLevelVariableElementImpl variable = getter.variable; |
| TopLevelVariableElementImpl setterVariable = setter.variable; |
| CompilationUnitElementImpl setterUnit = setterVariable.enclosingElement; |
| setterUnit.replaceTopLevelVariable(setterVariable, variable); |
| variable.setter = setter; |
| (setter as PropertyAccessorElementImpl).variable = variable; |
| } |
| } |
| } |
| |
| /** |
| * Add all of the non-synthetic [getters] and [setters] defined in the given |
| * [unit] that have no corresponding accessor to one of the given collections. |
| */ |
| static void _collectAccessors(Map<String, PropertyAccessorElement> getters, |
| List<PropertyAccessorElement> setters, CompilationUnitElement unit) { |
| List<PropertyAccessorElement> accessors = unit.accessors; |
| int length = accessors.length; |
| for (int i = 0; i < length; i++) { |
| PropertyAccessorElement accessor = accessors[i]; |
| if (accessor.isGetter) { |
| if (!accessor.isSynthetic && accessor.correspondingSetter == null) { |
| getters[accessor.displayName] = accessor; |
| } |
| } else { |
| if (!accessor.isSynthetic && accessor.correspondingGetter == null) { |
| setters.add(accessor); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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<Object> { |
| /** |
| * 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; |
| |
| /** |
| * 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) |
| : _currentLibrary = currentLibrary, |
| _typeSystem = currentLibrary.context.typeSystem { |
| this._boolType = _typeProvider.boolType; |
| this._intType = _typeProvider.intType; |
| this._numType = _typeProvider.numType; |
| this._stringType = _typeProvider.stringType; |
| } |
| |
| @override |
| Object 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 null; |
| } |
| // should have arguments |
| ArgumentList argumentList = node.arguments; |
| if (argumentList == null) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode.NO_ANNOTATION_CONSTRUCTOR_ARGUMENTS, node); |
| return null; |
| } |
| // arguments should be constants |
| _validateConstantArguments(argumentList); |
| } |
| return null; |
| } |
| |
| @override |
| Object visitConstructorDeclaration(ConstructorDeclaration node) { |
| if (node.constKeyword != null) { |
| _validateConstructorInitializers(node); |
| _validateFieldInitializers(node.parent as ClassDeclaration, node); |
| } |
| _validateDefaultValues(node.parameters); |
| return super.visitConstructorDeclaration(node); |
| } |
| |
| @override |
| Object visitFunctionExpression(FunctionExpression node) { |
| super.visitFunctionExpression(node); |
| _validateDefaultValues(node.parameters); |
| return null; |
| } |
| |
| @override |
| Object visitInstanceCreationExpression(InstanceCreationExpression node) { |
| if (node.isConst) { |
| // We need to evaluate the constant to see if any errors occur during its |
| // evaluation. |
| ConstructorElement constructor = node.staticElement; |
| if (constructor != null) { |
| ConstantEvaluationEngine evaluationEngine = |
| new ConstantEvaluationEngine(_typeProvider, declaredVariables, |
| typeSystem: _typeSystem); |
| ConstantVisitor constantVisitor = |
| new ConstantVisitor(evaluationEngine, _errorReporter); |
| evaluationEngine.evaluateConstructorCall( |
| node, |
| node.argumentList.arguments, |
| constructor, |
| constantVisitor, |
| _errorReporter); |
| } |
| } |
| _validateInstanceCreationArguments(node); |
| return super.visitInstanceCreationExpression(node); |
| } |
| |
| @override |
| Object visitListLiteral(ListLiteral node) { |
| super.visitListLiteral(node); |
| if (node.constKeyword != null) { |
| DartObjectImpl result; |
| for (Expression element in node.elements) { |
| result = |
| _validate(element, CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT); |
| if (result != null) { |
| _reportErrorIfFromDeferredLibrary( |
| element, |
| CompileTimeErrorCode |
| .NON_CONSTANT_LIST_ELEMENT_FROM_DEFERRED_LIBRARY); |
| } |
| } |
| } |
| return null; |
| } |
| |
| @override |
| Object visitMapLiteral(MapLiteral node) { |
| super.visitMapLiteral(node); |
| bool isConst = node.constKeyword != null; |
| bool reportEqualKeys = true; |
| HashSet<DartObject> keys = new HashSet<DartObject>(); |
| List<Expression> invalidKeys = new List<Expression>(); |
| for (MapLiteralEntry entry in node.entries) { |
| Expression key = entry.key; |
| if (isConst) { |
| DartObjectImpl keyResult = |
| _validate(key, CompileTimeErrorCode.NON_CONSTANT_MAP_KEY); |
| Expression valueExpression = entry.value; |
| DartObjectImpl valueResult = _validate( |
| valueExpression, CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE); |
| if (valueResult != null) { |
| _reportErrorIfFromDeferredLibrary( |
| valueExpression, |
| CompileTimeErrorCode |
| .NON_CONSTANT_MAP_VALUE_FROM_DEFERRED_LIBRARY); |
| } |
| if (keyResult != null) { |
| _reportErrorIfFromDeferredLibrary(key, |
| CompileTimeErrorCode.NON_CONSTANT_MAP_KEY_FROM_DEFERRED_LIBRARY); |
| if (keys.contains(keyResult)) { |
| invalidKeys.add(key); |
| } else { |
| keys.add(keyResult); |
| } |
| DartType type = keyResult.type; |
| if (_implementsEqualsWhenNotAllowed(type)) { |
| _errorReporter.reportErrorForNode( |
| CompileTimeErrorCode |
| .CONST_MAP_KEY_EXPRESSION_TYPE_IMPLEMENTS_EQUALS, |
| key, |
| [type.displayName]); |
| } |
| } |
| } else { |
| // Note: we throw the errors away because this isn't actually a const. |
| AnalysisErrorListener errorListener = |
| AnalysisErrorListener.NULL_LISTENER; |
| ErrorReporter subErrorReporter = |
| new ErrorReporter(errorListener, _errorReporter.source); |
| DartObjectImpl result = key.accept(new ConstantVisitor( |
| new ConstantEvaluationEngine(_typeProvider, declaredVariables, |
| typeSystem: _typeSystem), |
| subErrorReporter)); |
| if (result != null) { |
| if (keys.contains(result)) { |
| invalidKeys.add(key); |
| } else { |
| keys.add(result); |
| } |
| } else { |
| reportEqualKeys = false; |
| } |
| } |
| } |
| if (reportEqualKeys) { |
| int length = invalidKeys.length; |
| for (int i = 0; i < length; i++) { |
| _errorReporter.reportErrorForNode( |
| StaticWarningCode.EQUAL_KEYS_IN_MAP, invalidKeys[i]); |
| } |
| } |
| return null; |
| } |
| |
| @override |
| Object visitMethodDeclaration(MethodDeclaration node) { |
| super.visitMethodDeclaration(node); |
| _validateDefaultValues(node.parameters); |
| return null; |
| } |
| |
| @override |
| Object 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); |
| } |
| return super.visitSwitchStatement(node); |
| } |
| |
| @override |
| Object visitVariableDeclaration(VariableDeclaration node) { |
| super.visitVariableDeclaration(node); |
| Expression initializer = node.initializer; |
| if (initializer != null && (node.isConst || node.isFinal)) { |
| VariableElementImpl element = node.element 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 null; |
| } |
| _reportErrors(result.errors, |
| CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE); |
| _reportErrorIfFromDeferredLibrary( |
| initializer, |
| CompileTimeErrorCode |
| .CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE_FROM_DEFERRED_LIBRARY); |
| } |
| return null; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * @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_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( |
| new ConstantEvaluationEngine(_typeProvider, declaredVariables, |
| typeSystem: _typeSystem), |
| 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.element 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( |
| ClassDeclaration 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 eavluated 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( |
| new ConstantEvaluationEngine(_typeProvider, declaredVariables, |
| typeSystem: _typeSystem), |
| 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(_typeProvider, |
| subErrorReporter, this, parameterElements, declaredVariables, |
| typeSystem: _typeSystem)); |
| _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); |
| } |
| } |
| |
| /** |
| * Validate that if the passed instance creation is 'const' then all its arguments are constant |
| * expressions. |
| * |
| * @param node the instance creation evaluate |
| */ |
| void _validateInstanceCreationArguments(InstanceCreationExpression node) { |
| if (!node.isConst) { |
| return; |
| } |
| ArgumentList argumentList = node.argumentList; |
| if (argumentList == null) { |
| return; |
| } |
| _validateConstantArguments(argumentList); |
| } |
| } |
| |
| /** |
| * Instances of the class `Dart2JSVerifier` traverse an AST structure looking for hints for |
| * code that will be compiled to JS, such as [HintCode.IS_DOUBLE]. |
| */ |
| class Dart2JSVerifier extends RecursiveAstVisitor<Object> { |
| /** |
| * The name of the `double` type. |
| */ |
| static String _DOUBLE_TYPE_NAME = "double"; |
| |
| /** |
| * The error reporter by which errors will be reported. |
| */ |
| final ErrorReporter _errorReporter; |
| |
| /** |
| * Create a new instance of the [Dart2JSVerifier]. |
| * |
| * @param errorReporter the error reporter |
| */ |
| Dart2JSVerifier(this._errorReporter); |
| |
| @override |
| Object visitIsExpression(IsExpression node) { |
| _checkForIsDoubleHints(node); |
| return super.visitIsExpression(node); |
| } |
| |
| /** |
| * Check for instances of `x is double`, `x is int`, `x is! double` and |
| * `x is! int`. |
| * |
| * @param node the is expression to check |
| * @return `true` if and only if a hint code is generated on the passed node |
| * See [HintCode.IS_DOUBLE], |
| * [HintCode.IS_INT], |
| * [HintCode.IS_NOT_DOUBLE], and |
| * [HintCode.IS_NOT_INT]. |
| */ |
| bool _checkForIsDoubleHints(IsExpression node) { |
| DartType type = node.type.type; |
| Element element = type?.element; |
| if (element != null) { |
| String typeNameStr = element.name; |
| LibraryElement libraryElement = element.library; |
| // if (typeNameStr.equals(INT_TYPE_NAME) && libraryElement != null |
| // && libraryElement.isDartCore()) { |
| // if (node.getNotOperator() == null) { |
| // errorReporter.reportError(HintCode.IS_INT, node); |
| // } else { |
| // errorReporter.reportError(HintCode.IS_NOT_INT, node); |
| // } |
| // return true; |
| // } else |
| if (typeNameStr == _DOUBLE_TYPE_NAME && |
| libraryElement != null && |
| libraryElement.isDartCore) { |
| if (node.notOperator == null) { |
| _errorReporter.reportErrorForNode(HintCode.IS_DOUBLE, node); |
| } else { |
| _errorReporter.reportErrorForNode(HintCode.IS_NOT_DOUBLE, node); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Instances of the class `DeadCodeVerifier` traverse an AST structure looking for cases of |
| * [HintCode.DEAD_CODE]. |
| */ |
| class DeadCodeVerifier extends RecursiveAstVisitor<Object> { |
| /** |
| * The error reporter by which errors will be reported. |
| */ |
| final ErrorReporter _errorReporter; |
| |
| /** |
| * The type system for this visitor |
| */ |
| final TypeSystem _typeSystem; |
| |
| /** |
| * Create a new instance of the [DeadCodeVerifier]. |
| * |
| * @param errorReporter the error reporter |
| */ |
| DeadCodeVerifier(this._errorReporter, {TypeSystem typeSystem}) |
| : this._typeSystem = typeSystem ?? new TypeSystemImpl(null); |
| |
| @override |
| Object visitBinaryExpression(BinaryExpression node) { |
| Token operator = node.operator; |
| bool isAmpAmp = operator.type == TokenType.AMPERSAND_AMPERSAND; |
| bool isBarBar = operator.type == TokenType.BAR_BAR; |
| if (isAmpAmp || isBarBar) { |
| Expression lhsCondition = node.leftOperand; |
| if (!_isDebugConstant(lhsCondition)) { |
| EvaluationResultImpl lhsResult = _getConstantBooleanValue(lhsCondition); |
| if (lhsResult != null) { |
| bool value = lhsResult.value.toBoolValue(); |
| if (value == true && isBarBar) { |
| // report error on else block: true || !e! |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.rightOperand); |
| // only visit the LHS: |
| lhsCondition?.accept(this); |
| return null; |
| } else if (value == false && isAmpAmp) { |
| // report error on if block: false && !e! |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.rightOperand); |
| // only visit the LHS: |
| lhsCondition?.accept(this); |
| return null; |
| } |
| } |
| } |
| // How do we want to handle the RHS? It isn't dead code, but "pointless" |
| // or "obscure"... |
| // Expression rhsCondition = node.getRightOperand(); |
| // ValidResult rhsResult = getConstantBooleanValue(rhsCondition); |
| // if (rhsResult != null) { |
| // if (rhsResult == ValidResult.RESULT_TRUE && isBarBar) { |
| // // report error on else block: !e! || true |
| // errorReporter.reportError(HintCode.DEAD_CODE, node.getRightOperand()); |
| // // only visit the RHS: |
| // rhsCondition?.accept(this); |
| // return null; |
| // } else if (rhsResult == ValidResult.RESULT_FALSE && isAmpAmp) { |
| // // report error on if block: !e! && false |
| // errorReporter.reportError(HintCode.DEAD_CODE, node.getRightOperand()); |
| // // only visit the RHS: |
| // rhsCondition?.accept(this); |
| // return null; |
| // } |
| // } |
| } |
| return super.visitBinaryExpression(node); |
| } |
| |
| /** |
| * For each [Block], this method reports and error on all statements between the end of the |
| * block and the first return statement (assuming there it is not at the end of the block.) |
| * |
| * @param node the block to evaluate |
| */ |
| @override |
| Object visitBlock(Block node) { |
| NodeList<Statement> statements = node.statements; |
| _checkForDeadStatementsInNodeList(statements); |
| return null; |
| } |
| |
| @override |
| Object visitConditionalExpression(ConditionalExpression node) { |
| Expression conditionExpression = node.condition; |
| conditionExpression?.accept(this); |
| if (!_isDebugConstant(conditionExpression)) { |
| EvaluationResultImpl result = |
| _getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (result.value.toBoolValue() == true) { |
| // report error on else block: true ? 1 : !2! |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.elseExpression); |
| node.thenExpression?.accept(this); |
| return null; |
| } else { |
| // report error on if block: false ? !1! : 2 |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.thenExpression); |
| node.elseExpression?.accept(this); |
| return null; |
| } |
| } |
| } |
| return super.visitConditionalExpression(node); |
| } |
| |
| @override |
| Object visitExportDirective(ExportDirective node) { |
| ExportElement exportElement = node.element; |
| if (exportElement != null) { |
| // The element is null when the URI is invalid |
| LibraryElement library = exportElement.exportedLibrary; |
| if (library != null && !library.isSynthetic) { |
| for (Combinator combinator in node.combinators) { |
| _checkCombinator(exportElement.exportedLibrary, combinator); |
| } |
| } |
| } |
| return super.visitExportDirective(node); |
| } |
| |
| @override |
| Object visitIfStatement(IfStatement node) { |
| Expression conditionExpression = node.condition; |
| conditionExpression?.accept(this); |
| if (!_isDebugConstant(conditionExpression)) { |
| EvaluationResultImpl result = |
| _getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (result.value.toBoolValue() == true) { |
| // report error on else block: if(true) {} else {!} |
| Statement elseStatement = node.elseStatement; |
| if (elseStatement != null) { |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, elseStatement); |
| node.thenStatement?.accept(this); |
| return null; |
| } |
| } else { |
| // report error on if block: if (false) {!} else {} |
| _errorReporter.reportErrorForNode( |
| HintCode.DEAD_CODE, node.thenStatement); |
| node.elseStatement?.accept(this); |
| return null; |
| } |
| } |
| } |
| return super.visitIfStatement(node); |
| } |
| |
| @override |
| Object visitImportDirective(ImportDirective node) { |
| ImportElement importElement = node.element; |
| if (importElement != null) { |
| // The element is null when the URI is invalid, but not when the URI is |
| // valid but refers to a non-existent file. |
| LibraryElement library = importElement.importedLibrary; |
| if (library != null && !library.isSynthetic) { |
| for (Combinator combinator in node.combinators) { |
| _checkCombinator(library, combinator); |
| } |
| } |
| } |
| return super.visitImportDirective(node); |
| } |
| |
| @override |
| Object visitSwitchCase(SwitchCase node) { |
| _checkForDeadStatementsInNodeList(node.statements, allowMandated: true); |
| return super.visitSwitchCase(node); |
| } |
| |
| @override |
| Object visitSwitchDefault(SwitchDefault node) { |
| _checkForDeadStatementsInNodeList(node.statements, allowMandated: true); |
| return super.visitSwitchDefault(node); |
| } |
| |
| @override |
| Object visitTryStatement(TryStatement node) { |
| node.body?.accept(this); |
| node.finallyBlock?.accept(this); |
| NodeList<CatchClause> catchClauses = node.catchClauses; |
| int numOfCatchClauses = catchClauses.length; |
| List<DartType> visitedTypes = new List<DartType>(); |
| for (int i = 0; i < numOfCatchClauses; i++) { |
| CatchClause catchClause = catchClauses[i]; |
| if (catchClause.onKeyword != null) { |
| // on-catch clause found, verify that the exception type is not a |
| // subtype of a previous on-catch exception type |
| DartType currentType = catchClause.exceptionType?.type; |
| if (currentType != null) { |
| if (currentType.isObject) { |
| // Found catch clause clause that has Object as an exception type, |
| // this is equivalent to having a catch clause that doesn't have an |
| // exception type, visit the block, but generate an error on any |
| // following catch clauses (and don't visit them). |
| catchClause?.accept(this); |
| if (i + 1 != numOfCatchClauses) { |
| // this catch clause is not the last in the try statement |
| CatchClause nextCatchClause = catchClauses[i + 1]; |
| CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1]; |
| int offset = nextCatchClause.offset; |
| int length = lastCatchClause.end - offset; |
| _errorReporter.reportErrorForOffset( |
| HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH, offset, length); |
| return null; |
| } |
| } |
| int length = visitedTypes.length; |
| for (int j = 0; j < length; j++) { |
| DartType type = visitedTypes[j]; |
| if (_typeSystem.isSubtypeOf(currentType, type)) { |
| CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1]; |
| int offset = catchClause.offset; |
| int length = lastCatchClause.end - offset; |
| _errorReporter.reportErrorForOffset( |
| HintCode.DEAD_CODE_ON_CATCH_SUBTYPE, |
| offset, |
| length, |
| [currentType.displayName, type.displayName]); |
| return null; |
| } |
| } |
| visitedTypes.add(currentType); |
| } |
| catchClause?.accept(this); |
| } else { |
| // Found catch clause clause that doesn't have an exception type, |
| // visit the block, but generate an error on any following catch clauses |
| // (and don't visit them). |
| catchClause?.accept(this); |
| if (i + 1 != numOfCatchClauses) { |
| // this catch clause is not the last in the try statement |
| CatchClause nextCatchClause = catchClauses[i + 1]; |
| CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1]; |
| int offset = nextCatchClause.offset; |
| int length = lastCatchClause.end - offset; |
| _errorReporter.reportErrorForOffset( |
| HintCode.DEAD_CODE_CATCH_FOLLOWING_CATCH, offset, length); |
| return null; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @override |
| Object visitWhileStatement(WhileStatement node) { |
| Expression conditionExpression = node.condition; |
| conditionExpression?.accept(this); |
| if (!_isDebugConstant(conditionExpression)) { |
| EvaluationResultImpl result = |
| _getConstantBooleanValue(conditionExpression); |
| if (result != null) { |
| if (result.value.toBoolValue() == false) { |
| // report error on if block: while (false) {!} |
| _errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.body); |
| return null; |
| } |
| } |
| } |
| node.body?.accept(this); |
| return null; |
| } |
| |
| /** |
| * Resolve the names in the given [combinator] in the scope of the given |
| * [library]. |
| */ |
| void _checkCombinator(LibraryElement library, Combinator combinator) { |
| Namespace namespace = |
| new NamespaceBuilder().createExportNamespaceForLibrary(library); |
| NodeList<SimpleIdentifier> names; |
| ErrorCode hintCode; |
| if (combinator is HideCombinator) { |
| names = combinator.hiddenNames; |
| hintCode = HintCode.UNDEFINED_HIDDEN_NAME; |
| } else { |
| names = (combinator as ShowCombinator).shownNames; |
| hintCode = HintCode.UNDEFINED_SHOWN_NAME; |
| } |
| for (SimpleIdentifier name in names) { |
| String nameStr = name.name; |
| Element element = namespace.get(nameStr); |
| if (element == null) { |
| element = namespace.get("$nameStr="); |
| } |
| if (element == null) { |
| _errorReporter |
| .reportErrorForNode(hintCode, name, [library.identifier, nameStr]); |
| } |
| } |
| } |
| |
| /** |
| * Given some [NodeList] of [Statement]s, from either a [Block] or |
| * [SwitchMember], this loops through the list searching for dead statements. |
| * |
| * @param statements some ordered list of statements in a [Block] or [SwitchMember] |
| * @param allowMandated allow dead statements mandated by the language spec. |
| * This allows for a final break, continue, return, or throw statement |
| * at the end of a switch case, that are mandated by the language spec. |
| */ |
| void _checkForDeadStatementsInNodeList(NodeList<Statement> statements, |
| {bool allowMandated: false}) { |
| bool statementExits(Statement statement) { |
| if (statement is BreakStatement) { |
| return statement.label == null; |
| } else if (statement is ContinueStatement) { |
| return statement.label == null; |
| } |
| return ExitDetector.exits(statement); |
| } |
| |
| int size = statements.length; |
| for (int i = 0; i < size; i++) { |
| Statement currentStatement = statements[i]; |
| currentStatement?.accept(this); |
| if (statementExits(currentStatement) && i != size - 1) { |
| Statement nextStatement = statements[i + 1]; |
| Statement lastStatement = statements[size - 1]; |
| // If mandated statements are allowed, and only the last statement is |
| // dead, and it's a BreakStatement, then assume it is a statement |
| // mandated by the language spec, there to avoid a |
| // CASE_BLOCK_NOT_TERMINATED error. |
| if (allowMandated && i == size - 2 && nextStatement is BreakStatement) { |
| return; |
| } |
| int offset = nextStatement.offset; |
| int length = lastStatement.end - offset; |
| _errorReporter.reportErrorForOffset(HintCode.DEAD_CODE, offset, length); |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Given some [Expression], this method returns [ValidResult.RESULT_TRUE] if it is |
| * `true`, [ValidResult.RESULT_FALSE] if it is `false`, or `null` if the |
| * expression is not a constant boolean value. |
| * |
| * @param expression the expression to evaluate |
| * @return [ValidResult.RESULT_TRUE] if it is `true`, [ValidResult.RESULT_FALSE] |
| * if it is `false`, or `null` if the expression is not a constant boolean |
| * value |
| */ |
| EvaluationResultImpl _getConstantBooleanValue(Expression expression) { |
| if (expression is BooleanLiteral) { |
| if (expression.value) { |
| return new EvaluationResultImpl( |
| new DartObjectImpl(null, BoolState.from(true))); |
| } else { |
| return new EvaluationResultImpl( |
| new DartObjectImpl(null, BoolState.from(false))); |
| } |
| } |
| // Don't consider situations where we could evaluate to a constant boolean |
| // expression with the ConstantVisitor |
| // else { |
| // EvaluationResultImpl result = expression.accept(new ConstantVisitor()); |
| // if (result == ValidResult.RESULT_TRUE) { |
| // return ValidResult.RESULT_TRUE; |
| // } else if (result == ValidResult.RESULT_FALSE) { |
| // return ValidResult.RESULT_FALSE; |
| // } |
| // return null; |
| // } |
| return null; |
| } |
| |
| /** |
| * Return `true` if and only if the passed expression is resolved to a constant variable. |
| * |
| * @param expression some conditional expression |
| * @return `true` if and only if the passed expression is resolved to a constant variable |
| */ |
| bool _isDebugConstant(Expression expression) { |
| Element element = null; |
| if (expression is Identifier) { |
| element = expression.staticElement; |
| } else if (expression is PropertyAccess) { |
| element = expression.propertyName.staticElement; |
| } |
| if (element is PropertyAccessorElement) { |
| PropertyInducingElement variable = element.variable; |
| return variable != null && variable.isConst; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * A visitor that resolves directives in an AST structure to already built |
| * elements. |
| * |
| * The resulting AST must have everything resolved that would have been resolved |
| * by a [DirectiveElementBuilder]. |
| */ |
| class DirectiveResolver extends SimpleAstVisitor { |
| final Map<Source, int> sourceModificationTimeMap; |
| final Map<Source, SourceKind> importSourceKindMap; |
| final Map<Source, SourceKind> exportSourceKindMap; |
| final List<AnalysisError> errors = <AnalysisError>[]; |
| |
| LibraryElement _enclosingLibrary; |
| |
| DirectiveResolver(this.sourceModificationTimeMap, this.importSourceKindMap, |
| this.exportSourceKindMap); |
| |
| @override |
| void visitCompilationUnit(CompilationUnit node) { |
| _enclosingLibrary = |
| resolutionMap.elementDeclaredByCompilationUnit(node).library; |
| for (Directive directive in node.directives) { |
| directive.accept(this); |
| } |
| } |
| |
| @override |
| void visitExportDirective(ExportDirective node) { |
| int nodeOffset = node.offset; |
| node.element = null; |
| for (ExportElement element in _enclosingLibrary.exports) { |
| if (element.nameOffset == nodeOffset) { |
| node.element = element; |
| // Verify the exported source kind. |
| Source exportedSource = element.exportedLibrary.source; |
| int exportedTime = sourceModificationTimeMap[exportedSource] ?? -1; |
| if (exportedTime >= 0 && |
| exportSourceKindMap[exportedSource] != SourceKind.LIBRARY) { |
| StringLiteral uriLiteral = node.uri; |
| errors.add(new AnalysisError( |
| _enclosingLibrary.source, |
| uriLiteral.offset, |
| uriLiteral.length, |
| CompileTimeErrorCode.EXPORT_OF_NON_LIBRARY, |
| [uriLiteral.toSource()])); |
| } |
| break; |
| } |
| } |
| } |
| |
| @override |
| void visitImportDirective(ImportDirective node) { |
| int nodeOffset = node.offset; |
| node.element = null; |
| for (ImportElement element in _enclosingLibrary.imports) { |
| if (element.nameOffset == nodeOffset) { |
| node.element = element; |
| // Verify the imported source kind. |
| Source importedSource = element.importedLibrary.source; |
| int importedTime = sourceModificationTimeMap[importedSource] ?? -1; |
| if (importedTime >= 0 && |
| importSourceKindMap[importedSource] != SourceKind.LIBRARY) { |
| StringLiteral uriLiteral = node.uri; |
| ErrorCode errorCode = element.isDeferred |
| ? StaticWarningCode.IMPORT_OF_NON_LIBRARY |
| : CompileTimeErrorCode.IMPORT_OF_NON_LIBRARY; |
| errors.add(new AnalysisError( |
| _enclosingLibrary.source, |
| uriLiteral.offset, |
| uriLiteral.length, |
| errorCode, |
| [uriLiteral.toSource()])); |
| } |
| break; |
| } |
| } |
| } |
| |
| @override |
| void visitLibraryDirective(LibraryDirective node) { |
| node.element = _enclosingLibrary; |
| } |
| } |
| |
| /** |
| * Instances of the class `ElementHolder` hold on to elements created while traversing an AST |
| * structure so that they can be accessed when creating their enclosing element. |
| */ |
| class ElementHolder { |
| List<PropertyAccessorElement> _accessors; |
| |
| List<ConstructorElement> _constructors; |
| |
| List<ClassElement> _enums; |
| |
| List<FieldElement> _fields; |
| |
| List<FunctionElement> _functions; |
| |
| List<LabelElement> _labels; |
| |
| List<LocalVariableElement> _localVariables; |
| |
| List<MethodElement> _methods; |
| |
| List<ParameterElement> _parameters; |
| |
| List<TopLevelVariableElement> _topLevelVariables; |
| |
| List<ClassElement> _types; |
| |
| List<FunctionTypeAliasElement> _typeAliases; |
| |
| List<TypeParameterElement> _typeParameters; |
| |
| List<PropertyAccessorElement> get accessors { |
| if (_accessors == null) { |
| return PropertyAccessorElement.EMPTY_LIST; |
| } |
| List<PropertyAccessorElement> result = _accessors; |
| _accessors = null; |
| return result; |
| } |
| |
| List<ConstructorElement> get constructors { |
| if (_constructors == null) { |
| return ConstructorElement.EMPTY_LIST; |
| } |
| List<ConstructorElement> result = _constructors; |
| _constructors = null; |
| return result; |
| } |
| |
| List<ClassElement> get enums { |
| if (_enums == null) { |
| return ClassElement.EMPTY_LIST; |
| } |
| List<ClassElement> result = _enums; |
| _enums = null; |
| return result; |
| } |
| |
| List<FieldElement> get fields { |
| if (_fields == null) { |
| return FieldElement.EMPTY_LIST; |
| } |
| List<FieldElement> result = _fields; |
| _fields = null; |
| return result; |
| } |
| |
| List<FieldElement> get fieldsWithoutFlushing { |
| if (_fields == null) { |
| return FieldElement.EMPTY_LIST; |
| } |
| List<FieldElement> result = _fields; |
| return result; |
| } |
| |
| List<FunctionElement> get functions { |
| if (_functions == null) { |
| return FunctionElement.EMPTY_LIST; |
| } |
| List<FunctionElement> result = _functions; |
| _functions = null; |
| return result; |
| } |
| |
| List<LabelElement> get labels { |
| if (_labels == null) { |
| return LabelElement.EMPTY_LIST; |
| } |
| List<LabelElement> result = _labels; |
| _labels = null; |
| return result; |
| } |
| |
| List<LocalVariableElement> get localVariables { |
| if (_localVariables == null) { |
| return LocalVariableElement.EMPTY_LIST; |
| } |
| List<LocalVariableElement> result = _localVariables; |
| _localVariables = null; |
| return result; |
| } |
| |
| List<MethodElement> get methods { |
| if (_methods == null) { |
| return MethodElement.EMPTY_LIST; |
| } |
| List<MethodElement> result = _methods; |
| _methods = null; |
| return result; |
| } |
| |
| List<ParameterElement> get parameters { |
| if (_parameters == null) { |
| return ParameterElement.EMPTY_LIST; |
| } |
| List<ParameterElement> result = _parameters; |
| _parameters = null; |
| return result; |
| } |
| |
| List<TopLevelVariableElement> get topLevelVariables { |
| if (_topLevelVariables == null) { |
| return TopLevelVariableElement.EMPTY_LIST; |
| } |
| List<TopLevelVariableElement> result = _topLevelVariables; |
| _topLevelVariables = null; |
| return result; |
| } |
| |
| List<FunctionTypeAliasElement> get typeAliases { |
| if (_typeAliases == null) { |
| return FunctionTypeAliasElement.EMPTY_LIST; |
| } |
| List<FunctionTypeAliasElement> result = _typeAliases; |
| _typeAliases = null; |
| return result; |
| } |
| |
| List<TypeParameterElement> get typeParameters { |
| if (_typeParameters == null) { |
| return TypeParameterElement.EMPTY_LIST; |
| } |
| List<TypeParameterElement> result = _typeParameters; |
| _typeParameters = null; |
| return result; |
| } |
| |
| List<ClassElement> get types { |
| if (_types == null) { |
| return ClassElement.EMPTY_LIST; |
| } |
| List<ClassElement> result = _types; |
| _types = null; |
| return result; |
| } |
| |
| void addAccessor(PropertyAccessorElement element) { |
| if (_accessors == null) { |
| _accessors = new List<PropertyAccessorElement>(); |
| } |
| _accessors.add(element); |
| } |
| |
| void addConstructor(ConstructorElement element) { |
| if (_constructors == null) { |
| _constructors = new List<ConstructorElement>(); |
| } |
| _constructors.add(element); |
| } |
| |
| void addEnum(ClassElement element) { |
| if (_enums == null) { |
| _enums = new List<ClassElement>(); |
| } |
| _enums.add(element); |
| } |
| |
| void addField(FieldElement element) { |
| if (_fields == null) { |
| _fields = new List<FieldElement>(); |
| } |
| _fields.add(element); |
| } |
| |
| void addFunction(FunctionElement element) { |
| if (_functions == null) { |
| _functions = new List<FunctionElement>(); |
| } |
| _functions.add(element); |
| } |
| |
| void addLabel(LabelElement element) { |
| if (_labels == null) { |
| _labels = new List<LabelElement>(); |
| } |
| _labels.add(element); |
| } |
| |
| void addLocalVariable(LocalVariableElement element) { |
| if (_localVariables == null) { |
| _localVariables = new List<LocalVariableElement>(); |
| } |
| _localVariables.add(element); |
| } |
| |
| void addMethod(MethodElement element) { |
| if (_methods == null) { |
| _methods = new List<MethodElement>(); |
| } |
| _methods.add(element); |
| } |
| |
| void addParameter(ParameterElement element) { |
| if (_parameters == null) { |
| _parameters = new List<ParameterElement>(); |
| } |
| _parameters.add(element); |
| } |
| |
| void addTopLevelVariable(TopLevelVariableElement element) { |
| if (_topLevelVariables == null) { |
| _topLevelVariables = new List<TopLevelVariableElement>(); |
| } |
| _topLevelVariables.add(element); |
| } |
| |
| void addType(ClassElement element) { |
| if (_types == null) { |
| _types = new List<ClassElement>(); |
| } |
| _types.add(element); |
| } |
| |
| void addTypeAlias(FunctionTypeAliasElement element) { |
| if (_typeAliases == null) { |
| _typeAliases = new List<FunctionTypeAliasElement>(); |
| } |
| _typeAliases.add(element); |
| } |
| |
| void addTypeParameter(TypeParameterElement element) { |
| if (_typeParameters == null) { |
| _typeParameters = new List<TypeParameterElement>(); |
| } |
| _typeParameters.add(element); |
| } |
| |
| FieldElement getField(String fieldName, {bool synthetic: false}) { |
| if (_fields == null) { |
| return null; |
| } |
| int length = _fields.length; |
| for (int i = 0; i < length; i++) { |
| FieldElement field = _fields[i]; |
| if (field.name == fieldName && field.isSynthetic == synthetic) { |
| return field; |
| } |
| } |
| return null; |
| } |
| |
| TopLevelVariableElement getTopLevelVariable(String variableName) { |
| if (_topLevelVariables == null) { |
| return null; |
| } |
| int length = _topLevelVariables.length; |
| for (int i = 0; i < length; i++) { |
| TopLevelVariableElement variable = _topLevelVariables[i]; |
| if (variable.name == variableName) { |
| return variable; |
| } |
| } |
| return null; |
| } |
| |
| void validate() { |
| StringBuffer buffer = new StringBuffer(); |
| if (_accessors != null) { |
| buffer.write(_accessors.length); |
| buffer.write(" accessors"); |
| } |
| if (_constructors != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_constructors.length); |
| buffer.write(" constructors"); |
| } |
| if (_fields != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_fields.length); |
| buffer.write(" fields"); |
| } |
| if (_functions != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_functions.length); |
| buffer.write(" functions"); |
| } |
| if (_labels != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_labels.length); |
| buffer.write(" labels"); |
| } |
| if (_localVariables != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_localVariables.length); |
| buffer.write(" local variables"); |
| } |
| if (_methods != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_methods.length); |
| buffer.write(" methods"); |
| } |
| if (_parameters != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_parameters.length); |
| buffer.write(" parameters"); |
| } |
| if (_topLevelVariables != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_topLevelVariables.length); |
| buffer.write(" top-level variables"); |
| } |
| if (_types != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_types.length); |
| buffer.write(" types"); |
| } |
| if (_typeAliases != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_typeAliases.length); |
| buffer.write(" type aliases"); |
| } |
| if (_typeParameters != null) { |
| if (buffer.length > 0) { |
| buffer.write("; "); |
| } |
| buffer.write(_typeParameters.length); |
| buffer.write(" type parameters"); |
| } |
| if (buffer.length > 0) { |
| AnalysisEngine.instance.logger |
| .logError("Failed to capture elements: $buffer"); |
| } |
| } |
| } |
| |
| /** |
| * Instances of the class `EnumMemberBuilder` build the members in enum declarations. |
| */ |
| class EnumMemberBuilder extends RecursiveAstVisitor<Object> { |
| /** |
| * The type provider used to access the types needed to build an element model for enum |
| * declarations. |
| */ |
| final TypeProvider _typeProvider; |
| |
| /** |
| * Initialize a newly created enum member builder. |
| * |
| * @param typeProvider the type provider used to access the types needed to build an element model |
| * for enum declarations |
| */ |
| EnumMemberBuilder(this._typeProvider); |
| |
| @override |
| Object visitEnumDeclaration(EnumDeclaration node) { |
| // |
| // Finish building the enum. |
| // |
| EnumElementImpl enumElement = node.name.staticElement as EnumElementImpl; |
| InterfaceType enumType = enumElement.type; |
| // |
| // Populate the fields. |
| // |
| List<FieldElement> fields = new List<FieldElement>(); |
| List<PropertyAccessorElement> getters = new List<PropertyAccessorElement>(); |
| InterfaceType intType = _typeProvider.intType; |
| String indexFieldName = "index"; |
| FieldElementImpl indexField = new FieldElementImpl(indexFieldName, -1); |
| indexField.isFinal = true; |
| indexField.isSynthetic = true; |
| indexField.type = intType; |
| fields.add(indexField); |
| getters.add(_createGetter(indexField)); |
| ConstFieldElementImpl valuesField = new ConstFieldElementImpl("values", -1); |
| valuesField.isStatic = true; |
| valuesField.isConst = true; |
| valuesField.isSynthetic = true; |
| valuesField.type = _typeProvider.listType.instantiate(<DartType>[enumType]); |
| fields.add(valuesField); |
| getters.add(_createGetter(valuesField)); |
| // |
| // Build the enum constants. |
| // |
| NodeList<EnumConstantDeclaration> constants = node.constants; |
| List<DartObjectImpl> constantValues = new List<DartObjectImpl>(); |
| int constantCount = constants.length; |
| for (int i = 0; i < constantCount; i++) { |
| EnumConstantDeclaration constant = constants[i]; |
| FieldElementImpl constantField = constant.name.staticElement; |
| // |
| // Create a value for the constant. |
| // |
| HashMap<String, DartObjectImpl> fieldMap = |
| new HashMap<String, DartObjectImpl>(); |
| fieldMap[indexFieldName] = new DartObjectImpl(intType, new IntState(i)); |
| DartObjectImpl value = |
| new DartObjectImpl(enumType, new GenericState(fieldMap)); |
| constantValues.add(value); |
| constantField.evaluationResult = new EvaluationResultImpl(value); |
| fields.add(constantField); |
| getters.add(constantField.getter); |
| } |
| // |
| // Build the value of the 'values' field. |
| // |
| valuesField.evaluationResult = new EvaluationResultImpl( |
| new DartObjectImpl(valuesField.type, new ListState(constantValues))); |
| // |
| // Finish building the enum. |
| // |
| enumElement.fields = fields; |
| enumElement.accessors = getters; |
| // Client code isn't allowed to invoke the constructor, so we do not model |
| // it. |
| return super.visitEnumDeclaration(node); |
| } |
| |
| /** |
| * Create a getter that corresponds to the given [field]. |
| */ |
| PropertyAccessorElement _createGetter(FieldElementImpl field) { |
| return new PropertyAccessorElementImpl_ImplicitGetter(field); |
| } |
| } |
| |
| /** |
| * Instances of the class `ExitDetector` determine whether the visited AST node is guaranteed |
| * to terminate by executing a `return` statement, `throw` expression, `rethrow` |
| * expression, or simple infinite loop such as `while(true)`. |
| */ |
| class ExitDetector extends GeneralizingAstVisitor<bool> { |
| /** |
| * Set to `true` when a `break` is encountered, and reset to `false` when a |
| * `do`, `while`, `for` or `switch` block is entered. |
| */ |
| bool _enclosingBlockContainsBreak = false; |
| |
| /** |
| * Set to `true` when a `continue` is encountered, and reset to `false` when a |
| * `do`, `while`, `for` or `switch` block is entered. |
| */ |
| bool _enclosingBlockContainsContinue = false; |
| |
| /** |
| * Add node when a labelled `break` is encountered. |
| */ |
| Set<AstNode> _enclosingBlockBreaksLabel = new Set<AstNode>(); |
| |
| @override |
| bool visitArgumentList(ArgumentList node) => |
| _visitExpressions(node.arguments); |
| |
| @override |
| bool visitAsExpression(AsExpression node) => _nodeExits(node.expression); |
| |
| @override |
| bool visitAssertInitializer(AssertInitializer node) => false; |
| |
| @override |
| bool visitAssertStatement(AssertStatement node) => false; |
| |
| @override |
| bool visitAssignmentExpression(AssignmentExpression node) { |
| Expression leftHandSide = node.leftHandSide; |
| if (_nodeExits(leftHandSide)) { |
| return true; |
| } |
| TokenType operatorType = node.operator.type; |
| if (operatorType == TokenType.AMPERSAND_AMPERSAND_EQ || |
| operatorType == TokenType.BAR_BAR_EQ || |
| operatorType == TokenType.QUESTION_QUESTION_EQ) { |
| return false; |
| } |
| if (leftHandSide is PropertyAccess && |
| leftHandSide.operator.type == TokenType.QUESTION_PERIOD) { |
| return false; |
| } |
| return _nodeExits(node.rightHandSide); |
| } |
| |
| @override |
| bool visitAwaitExpression(AwaitExpression node) => |
| _nodeExits(node.expression); |
| |
| @override |
| bool visitBinaryExpression(BinaryExpression node) { |
| Expression lhsExpression = node.leftOperand; |
| Expression rhsExpression = node.rightOperand; |
| TokenType operatorType = node.operator.type; |
| // If the operator is ||, then only consider the RHS of the binary |
| // expression if the left hand side is the false literal. |
| // TODO(jwren) Do we want to take constant expressions into account, |
| // evaluate if(false) {} differently than if(<condition>), when <condition> |
| // evaluates to a constant false value? |
| if (operatorType == TokenType.BAR_BAR) { |
| if (lhsExpression is BooleanLiteral) { |
| if (!lhsExpression.value) { |
| return _nodeExits(rhsExpression); |
| } |
| } |
| return _nodeExits(lhsExpression); |
| } |
| // If the operator is &&, then only consider the RHS of the binary |
| // expression if the left hand side is the true literal. |
| if (operatorType == TokenType.AMPERSAND_AMPERSAND) { |
| if (lhsExpression is BooleanLiteral) { |
| if (lhsExpression.value) { |
| return _nodeExits(rhsExpression); |
| } |
| } |
| return _nodeExits(lhsExpression); |
| } |
| // If the operator is ??, then don't consider the RHS of the binary |
| // expression. |
| if (operatorType == TokenType.QUESTION_QUESTION) { |
| return _nodeExits(lhsExpression); |
| } |
| return _nodeExits(lhsExpression) || _nodeExits(rhsExpression); |
| } |
| |
| @override |
| bool visitBlock(Block node) => _visitStatements(node.statements); |
| |
| @override |
| bool visitBlockFunctionBody(BlockFunctionBody node) => _nodeExits(node.block); |
| |
| @override |
| bool visitBreakStatement(BreakStatement node) { |
| _enclosingBlockContainsBreak = true; |
| if (node.label != null) { |
| _enclosingBlockBreaksLabel.add(node.target); |
| } |
| return false; |
| } |
| |
| @override |
| bool visitCascadeExpression(CascadeExpression node) => |
| _nodeExits(node.target) || _visitExpressions(node.cascadeSections); |
| |
| @override |
| bool visitConditionalExpression(ConditionalExpression node) { |
| Expression conditionExpression = node.condition; |
| Expression thenStatement = node.thenExpression; |
| Expression elseStatement = node.elseExpression; |
| // TODO(jwren) Do we want to take constant expressions into account, |
| // evaluate if(false) {} differently than if(<condition>), when <condition> |
| // evaluates to a constant false value? |
| if (_nodeExits(conditionExpression)) { |
| return true; |
| } |
| if (thenStatement == null || elseStatement == null) { |
| return false; |
| } |
| return thenStatement.accept(this) && elseStatement.accept(this); |
| } |
| |
| @override |
| bool visitContinueStatement(ContinueStatement node) { |
| _enclosingBlockContainsContinue = true; |
| return false; |
| } |
| |
| @override |
| bool visitDoStatement(DoStatement node) { |
| bool outerBreakValue = _enclosingBlockContainsBreak; |
| bool outerContinueValue = _enclosingBlockContainsContinue; |
| _enclosingBlockContainsBreak = false; |
| _enclosingBlockContainsContinue = false; |
| try { |
| bool bodyExits = _nodeExits(node.body); |
| bool containsBreakOrContinue = |
| _enclosingBlockContainsBreak || _enclosingBlockContainsContinue; |
| // Even if we determine that the body "exits", there might be break or |
| // continue statements that actually mean it _doesn't_ always exit. |
| if (bodyExits && !containsBreakOrContinue) { |
| return true; |
| } |
|