blob: ccfa988cb5533de362480dc8fe7e2e8e53811427 [file] [log] [blame]
// 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;
}