blob: 8eed8dacc3671ff8792e64a5fde4020657ca53b5 [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.
import 'dart:collection';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/ast_factory.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/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/ast_factory.dart';
import 'package:analyzer/src/dart/ast/token.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:path/path.dart' as path;
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';
/**
* A visitor that will re-write an AST to support the optional `new` and `const`
* feature.
*/
class AstRewriteVisitor extends ScopedVisitor {
final bool addConstKeyword;
final TypeSystem typeSystem;
/**
* Initialize a newly created visitor.
*/
AstRewriteVisitor(
this.typeSystem,
LibraryElement definingLibrary,
Source source,
TypeProvider typeProvider,
AnalysisErrorListener errorListener,
{Scope nameScope,
this.addConstKeyword: false})
: super(definingLibrary, source, typeProvider, errorListener,
nameScope: nameScope);
@override
Object visitMethodInvocation(MethodInvocation node) {
super.visitMethodInvocation(node);
SimpleIdentifier methodName = node.methodName;
if (methodName.isSynthetic) {
// This isn't a constructor invocation because the method name is
// synthetic.
return null;
}
Expression target = node.target;
if (target == null) {
// Possible cases: C() or C<>()
if (node.realTarget != null) {
// This isn't a constructor invocation because it's in a cascade.
return null;
}
Element element = nameScope.lookup(methodName, definingLibrary);
if (element is ClassElement) {
AstFactory astFactory = new AstFactoryImpl();
TypeName typeName = astFactory.typeName(methodName, node.typeArguments);
ConstructorName constructorName =
astFactory.constructorName(typeName, null, null);
InstanceCreationExpression instanceCreationExpression =
astFactory.instanceCreationExpression(
_getKeyword(node), constructorName, node.argumentList);
InterfaceType type = _getType(element, node.typeArguments);
ConstructorElement constructorElement =
type.lookUpConstructor(null, definingLibrary);
methodName.staticElement = element;
methodName.staticType = type;
typeName.type = type;
constructorName.staticElement = constructorElement;
instanceCreationExpression.staticType = type;
instanceCreationExpression.staticElement = constructorElement;
NodeReplacer.replace(node, instanceCreationExpression);
}
} else if (target is SimpleIdentifier) {
// Possible cases: C.n(), p.C() or p.C<>()
if (node.operator.type == TokenType.QUESTION_PERIOD) {
// This isn't a constructor invocation because a null aware operator is
// being used.
}
Element element = nameScope.lookup(target, definingLibrary);
if (element is ClassElement) {
// Possible case: C.n()
var constructorElement = element.getNamedConstructor(methodName.name);
if (constructorElement != null) {
var typeArguments = node.typeArguments;
if (typeArguments != null) {
errorReporter.reportErrorForNode(
StaticTypeWarningCode
.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR,
node,
[element.name, constructorElement.name]);
}
AstFactory astFactory = new AstFactoryImpl();
TypeName typeName = astFactory.typeName(target, typeArguments);
ConstructorName constructorName =
astFactory.constructorName(typeName, node.operator, methodName);
InstanceCreationExpression instanceCreationExpression =
astFactory.instanceCreationExpression(
_getKeyword(node), constructorName, node.argumentList);
InterfaceType type = _getType(element, typeArguments);
constructorElement =
type.lookUpConstructor(methodName.name, definingLibrary);
methodName.staticElement = element;
methodName.staticType = type;
target.staticElement = element;
target.staticType = type; // TODO(scheglov) remove this
typeName.type = type;
constructorName.staticElement = constructorElement;
instanceCreationExpression.staticType = type;
instanceCreationExpression.staticElement = constructorElement;
NodeReplacer.replace(node, instanceCreationExpression);
}
} else if (element is PrefixElement) {
// Possible cases: p.C() or p.C<>()
AstFactory astFactory = new AstFactoryImpl();
Identifier identifier = astFactory.prefixedIdentifier(
astFactory.simpleIdentifier(target.token),
null,
astFactory.simpleIdentifier(methodName.token));
Element prefixedElement = nameScope.lookup(identifier, definingLibrary);
if (prefixedElement is ClassElement) {
TypeName typeName = astFactory.typeName(
astFactory.prefixedIdentifier(target, node.operator, methodName),
node.typeArguments);
ConstructorName constructorName =
astFactory.constructorName(typeName, null, null);
InstanceCreationExpression instanceCreationExpression =
astFactory.instanceCreationExpression(
_getKeyword(node), constructorName, node.argumentList);
InterfaceType type = _getType(prefixedElement, node.typeArguments);
ConstructorElement constructorElement =
type.lookUpConstructor(null, definingLibrary);
methodName.staticElement = element;
methodName.staticType = type;
typeName.type = type;
constructorName.staticElement = constructorElement;
instanceCreationExpression.staticType = type;
instanceCreationExpression.staticElement = constructorElement;
NodeReplacer.replace(node, instanceCreationExpression);
}
}
} else if (target is PrefixedIdentifier) {
// Possible case: p.C.n()
Element prefixElement = nameScope.lookup(target.prefix, definingLibrary);
target.prefix.staticElement = prefixElement;
if (prefixElement is PrefixElement) {
Element element = nameScope.lookup(target, definingLibrary);
if (element is ClassElement) {
var constructorElement = element.getNamedConstructor(methodName.name);
if (constructorElement != null) {
var typeArguments = node.typeArguments;
if (typeArguments != null) {
errorReporter.reportErrorForNode(
StaticTypeWarningCode
.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR,
node,
[element.name, constructorElement.name]);
}
AstFactory astFactory = new AstFactoryImpl();
TypeName typeName = astFactory.typeName(target, typeArguments);
ConstructorName constructorName =
astFactory.constructorName(typeName, node.operator, methodName);
InstanceCreationExpression instanceCreationExpression =
astFactory.instanceCreationExpression(
_getKeyword(node), constructorName, node.argumentList);
InterfaceType type = _getType(element, typeArguments);
constructorElement =
type.lookUpConstructor(methodName.name, definingLibrary);
methodName.staticElement = element;
methodName.staticType = type;
target.identifier.staticElement = element;
typeName.type = type;
constructorName.staticElement = constructorElement;
instanceCreationExpression.staticType = type;
instanceCreationExpression.staticElement = constructorElement;
NodeReplacer.replace(node, instanceCreationExpression);
}
}
}
}
return null;
}
/**
* Return the token that should be used in the [InstanceCreationExpression]
* that corresponds to the given invocation [node].
*/
Token _getKeyword(MethodInvocation node) {
return addConstKeyword
? new KeywordToken(Keyword.CONST, node.offset)
: null;
}
/**
* Return the type of the given class [element] after substituting any type
* arguments from the list of [typeArguments] for the class' type parameters.
*/
DartType _getType(ClassElement element, TypeArgumentList typeArguments) {
DartType type = element.type;
List<TypeParameterElement> typeParameters = element.typeParameters;
if (typeArguments != null &&
typeParameters != null &&
typeArguments.arguments.length == typeParameters.length) {
List<DartType> argumentTypes = typeArguments.arguments
.map((TypeAnnotation argument) => argument.type)
.toList();
List<DartType> parameterTypes = typeParameters
.map((TypeParameterElement parameter) => parameter.type)
.toList();
type = type.substitute2(argumentTypes, parameterTypes);
} else if (typeArguments == null && typeParameters != null) {
type = typeSystem.instantiateToBounds(type);
}
return type;
}
}
/**
* 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";
static final _templateExtension = '.template';
static final _testDir = '${path.separator}test${path.separator}';
static final _testingDir = '${path.separator}testing${path.separator}';
/**
* 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 StrongTypeSystemImpl(typeProvider) {
inDeprecatedMember = _currentLibrary.hasDeprecated;
}
@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.staticParameterElement;
if (parameter?.isOptionalPositional == true) {
_checkForDeprecatedMemberUse(parameter, argument);
}
}
return super.visitArgumentList(node);
}
@override
Object visitAsExpression(AsExpression node) {
_checkForUnnecessaryCast(node);
return super.visitAsExpression(node);
}
@override
Object visitAssignmentExpression(AssignmentExpression node) {
TokenType operatorType = node.operator.type;
if (operatorType == TokenType.EQ) {
_checkForInvalidAssignment(node.leftHandSide, node.rightHandSide);
} else {
_checkForDeprecatedMemberUse(node.staticElement, node);
}
return super.visitAssignmentExpression(node);
}
@override
Object visitBinaryExpression(BinaryExpression node) {
_checkForDivisionOptimizationHint(node);
_checkForDeprecatedMemberUse(node.staticElement, node);
return super.visitBinaryExpression(node);
}
@override
Object visitClassDeclaration(ClassDeclaration node) {
ClassElementImpl outerClass = _enclosingClass;
bool wasInDeprecatedMember = inDeprecatedMember;
ClassElement element =
AbstractClassElementImpl.getImpl(node.declaredElement);
if (element != null && element.hasDeprecated) {
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 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 visitExportDirective(ExportDirective node) {
_checkForDeprecatedMemberUse(node.uriElement, node);
return super.visitExportDirective(node);
}
@override
Object visitFormalParameterList(FormalParameterList node) {
_checkRequiredParameter(node);
return super.visitFormalParameterList(node);
}
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
bool wasInDeprecatedMember = inDeprecatedMember;
ExecutableElement element = node.declaredElement;
if (element != null && element.hasDeprecated) {
inDeprecatedMember = true;
}
try {
_checkForMissingReturn(
node.returnType, node.functionExpression.body, element, node);
return super.visitFunctionDeclaration(node);
} finally {
inDeprecatedMember = wasInDeprecatedMember;
}
}
@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.staticElement, 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.declaredElement;
if (element != null && element.hasDeprecated) {
inDeprecatedMember = true;
}
try {
// This was determined to not be a good hint, see: dartbug.com/16029
//checkForOverridingPrivateMember(node);
_checkForMissingReturn(node.returnType, node.body, element, node);
_checkForUnnecessaryNoSuchMethod(node);
return super.visitMethodDeclaration(node);
} finally {
inDeprecatedMember = wasInDeprecatedMember;
}
}
@override
Object visitMethodInvocation(MethodInvocation node) {
_checkForNullAwareHints(node, node.operator);
DartType staticInvokeType = node.staticInvokeType;
Element callElement = staticInvokeType?.element;
if (callElement is MethodElement &&
callElement.name == FunctionElement.CALL_METHOD_NAME) {
_checkForDeprecatedMemberUse(callElement, node);
}
return super.visitMethodInvocation(node);
}
@override
Object visitPostfixExpression(PostfixExpression node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
return super.visitPostfixExpression(node);
}
@override
Object visitPrefixExpression(PrefixExpression node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
return super.visitPrefixExpression(node);
}
@override
Object visitPropertyAccess(PropertyAccess node) {
_checkForNullAwareHints(node, node.operator);
return super.visitPropertyAccess(node);
}
@override
Object visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
return super.visitRedirectingConstructorInvocation(node);
}
@override
Object visitSimpleIdentifier(SimpleIdentifier node) {
_checkForDeprecatedMemberUseAtIdentifier(node);
_checkForInvalidAccess(node);
return super.visitSimpleIdentifier(node);
}
@override
Object visitSuperConstructorInvocation(SuperConstructorInvocation node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
return super.visitSuperConstructorInvocation(node);
}
@override
Object visitVariableDeclaration(VariableDeclaration node) {
_checkForInvalidAssignment(node.name, node.initializer);
return super.visitVariableDeclaration(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.lexeme) {
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;
}
/**
* 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
* See [HintCode.DEPRECATED_MEMBER_USE].
*/
void _checkForDeprecatedMemberUse(Element element, AstNode node) {
bool isDeprecated(Element element) {
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.hasDeprecated;
}
return element.hasDeprecated;
}
bool isLocalParameter(Element element, AstNode node) {
if (element is ParameterElement) {
ExecutableElement definingFunction = element.enclosingElement;
FunctionBody body =
node.getAncestor((ancestor) => ancestor is FunctionBody);
while (body != null) {
ExecutableElement enclosingFunction;
AstNode parent = body.parent;
if (parent is ConstructorDeclaration) {
enclosingFunction = parent.declaredElement;
} else if (parent is FunctionExpression) {
enclosingFunction = parent.declaredElement;
} else if (parent is MethodDeclaration) {
enclosingFunction = parent.declaredElement;
}
if (enclosingFunction == definingFunction) {
return true;
}
body = parent?.getAncestor((ancestor) => ancestor is FunctionBody);
}
}
return false;
}
if (!inDeprecatedMember &&
element != null &&
isDeprecated(element) &&
!isLocalParameter(element, node)) {
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.staticElement, 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 type
MethodElement methodElement = node.staticElement;
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.declaredElement;
if (isOrInheritsImmutable(element, new HashSet<ClassElement>()) &&
hasOrInheritsNonFinalInstanceField(
element, new HashSet<ClassElement>())) {
_errorReporter.reportErrorForNode(HintCode.MUST_BE_IMMUTABLE, node.name);
}
}
/// Produces a hint if [identifier] is accessed from an invalid location. In
/// particular:
///
/// * if the given identifier is a protected closure, field or
/// getter/setter, method closure or invocation accessed outside a subclass,
/// or accessed outside the library wherein the identifier is declared, or
/// * if the given identifier is a closure, field, getter, setter, method
/// closure or invocation which is annotated with `visibleForTemplate`, and
/// is accessed outside of the defining library, and the current library
/// does not have the suffix '.template' in its source path, or
/// * if the given identifier is a closure, field, getter, setter, method
/// closure or invocation which is annotated with `visibleForTesting`, and
/// is accessed outside of the defining library, and the current library
/// does not have a directory named 'test' or 'testing' in its path.
void _checkForInvalidAccess(SimpleIdentifier identifier) {
if (identifier.inDeclarationContext()) {
return;
}
bool isProtected(Element element) {
if (element is PropertyAccessorElement &&
element.enclosingElement is ClassElement &&
(element.hasProtected || element.variable.hasProtected)) {
return true;
}
if (element is MethodElement &&
element.enclosingElement is ClassElement &&
element.hasProtected) {
return true;
}
return false;
}
bool isVisibleForTemplate(Element element) {
if (element == null) {
return false;
}
if (element.hasVisibleForTemplate) {
return true;
}
if (element is PropertyAccessorElement &&
element.enclosingElement is ClassElement &&
element.variable.hasVisibleForTemplate) {
return true;
}
return false;
}
bool isVisibleForTesting(Element element) {
if (element == null) {
return false;
}
if (element.hasVisibleForTesting) {
return true;
}
if (element is PropertyAccessorElement &&
element.enclosingElement is ClassElement &&
element.variable.hasVisibleForTesting) {
return true;
}
return false;
}
bool inCommentReference(SimpleIdentifier identifier) =>
identifier.getAncestor((AstNode node) => node is CommentReference) !=
null;
bool inCurrentLibrary(Element element) =>
element.library == _currentLibrary;
bool inExportDirective(SimpleIdentifier identifier) =>
identifier.parent is Combinator &&
identifier.parent.parent is ExportDirective;
bool inTemplateSource(LibraryElement library) =>
library.definingCompilationUnit.source.fullName
.contains(_templateExtension);
bool inTestDirectory(LibraryElement library) =>
library.definingCompilationUnit.source.fullName.contains(_testDir) ||
library.definingCompilationUnit.source.fullName.contains(_testingDir);
Element element = identifier.staticElement;
if (!isProtected(element) &&
!isVisibleForTemplate(element) &&
!isVisibleForTesting(element)) {
// Without any of these annotations, the access is valid.
return;
}
if (isProtected(element)) {
if (inCurrentLibrary(element) || inCommentReference(identifier)) {
// The access is valid; even if [element] is also marked
// `visibleForTesting`, the "visibilities" are unioned.
return;
}
ClassElement definingClass = element.enclosingElement;
ClassDeclaration accessingClass =
identifier.getAncestor((AstNode node) => node is ClassDeclaration);
if (_hasTypeOrSuperType(
accessingClass?.declaredElement, definingClass.type)) {
return;
}
}
if (isVisibleForTemplate(element)) {
if (inCurrentLibrary(element) ||
inTemplateSource(_currentLibrary) ||
inExportDirective(identifier) ||
inCommentReference(identifier)) {
// The access is valid; even if [element] is also marked `protected`,
// the "visibilities" are unioned.
return;
}
}
if (isVisibleForTesting(element)) {
if (inCurrentLibrary(element) ||
inTestDirectory(_currentLibrary) ||
inExportDirective(identifier) ||
inCommentReference(identifier)) {
// The access is valid; even if [element] is also marked `protected`,
// the "visibilities" are unioned.
return;
}
}
// At this point, [identifier] was not cleared as protected access, nor
// cleared as access for templates or testing. Report the appropriate
// violation(s).
Element definingClass = element.enclosingElement;
if (isProtected(element)) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_USE_OF_PROTECTED_MEMBER,
identifier,
[identifier.name.toString(), definingClass.name]);
}
if (isVisibleForTemplate(element)) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_USE_OF_VISIBLE_FOR_TEMPLATE_MEMBER,
identifier,
[identifier.name.toString(), definingClass.name]);
}
if (isVisibleForTesting(element)) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_USE_OF_VISIBLE_FOR_TESTING_MEMBER,
identifier,
[identifier.name.toString(), definingClass.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,
isDeclarationCast: true)) {
// The warning was generated on this rhs
return false;
}
// Test for, and then generate the hint
DartType bestRightType = rhs.staticType;
if (leftType != null && bestRightType != null) {
if (!_typeSystem.isAssignableTo(bestRightType, leftType,
isDeclarationCast: true)) {
_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()]);
}
/**
* 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 returnNode, FunctionBody body,
ExecutableElement element, AstNode functionNode) {
if (body is BlockFunctionBody) {
// Prefer the type from the element model, in case we've inferred one.
DartType returnType = element?.returnType ?? returnNode?.type;
AstNode errorNode = returnNode ?? functionNode;
// Skip the check if we're missing a return type (e.g. erroneous code).
// Generators are never required to have a return statement.
if (returnType == null || body.isGenerator) {
return;
}
if (_typeSystem is StrongTypeSystemImpl) {
var flattenedType = body.isAsynchronous
? returnType.flattenFutures(_typeSystem)
: returnType;
// dynamic/Null/void are allowed to omit a return.
if (flattenedType.isDynamic ||
flattenedType.isDartCoreNull ||
flattenedType.isVoid) {
return;
}
// Otherwise issue a warning if the block doesn't have a return.
if (!ExitDetector.exits(body)) {
_errorReporter.reportErrorForNode(
HintCode.MISSING_RETURN, errorNode, [returnType.displayName]);
}
return;
}
// TODO(leafp): Delete this non-strong mode path
// Check that the type is resolvable and not "void"
if (returnType.isVoid ||
(body.isAsynchronous && _isFutureVoid(returnType))) {
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 (returnType.isDynamic) {
return;
}
if (returnType is InterfaceType && returnType.isDartAsyncFuture) {
DartType futureArgument = returnType.typeArguments[0];
if (futureArgument.isDynamic ||
futureArgument.isDartCoreNull ||
futureArgument.isVoid ||
futureArgument.isObject) {
return;
}
}
}
// Check the block for a return statement, if not, create the hint
if (!ExitDetector.exits(body)) {
_errorReporter.reportErrorForNode(
HintCode.MISSING_RETURN, errorNode, [returnType.displayName]);
}
}
}
/**
* Produce several null-aware related hints.
*/
void _checkForNullAwareHints(Expression node, Token operator) {
if (operator == null || operator.type != TokenType.QUESTION_PERIOD) {
return;
}
// childOfParent is used to know from which branch node comes.
var childOfParent = node;
var parent = node.parent;
while (parent is ParenthesizedExpression) {
childOfParent = parent;
parent = parent.parent;
}
// CAN_BE_NULL_AFTER_NULL_AWARE
if (parent is MethodInvocation &&
parent.operator.type != TokenType.QUESTION_PERIOD &&
_nullType.lookUpMethod(parent.methodName.name, _currentLibrary) ==
null) {
_errorReporter.reportErrorForNode(
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, childOfParent);
return;
}
if (parent is PropertyAccess &&
parent.operator.type != TokenType.QUESTION_PERIOD &&
_nullType.lookUpGetter(parent.propertyName.name, _currentLibrary) ==
null) {
_errorReporter.reportErrorForNode(
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, childOfParent);
return;
}
if (parent is CascadeExpression && parent.target == childOfParent) {
_errorReporter.reportErrorForNode(
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, childOfParent);
return;
}
// NULL_AWARE_IN_CONDITION
if (parent is IfStatement && parent.condition == childOfParent ||
parent is ForStatement && parent.condition == childOfParent ||
parent is DoStatement && parent.condition == childOfParent ||
parent is WhileStatement && parent.condition == childOfParent ||
parent is ConditionalExpression && parent.condition == childOfParent ||
parent is AssertStatement && parent.condition == childOfParent) {
_errorReporter.reportErrorForNode(
HintCode.NULL_AWARE_IN_CONDITION, childOfParent);
return;
}
// NULL_AWARE_IN_LOGICAL_OPERATOR
if (parent is PrefixExpression && parent.operator.type == TokenType.BANG ||
parent is BinaryExpression &&
[TokenType.BAR_BAR, TokenType.AMPERSAND_AMPERSAND]
.contains(parent.operator.type)) {
_errorReporter.reportErrorForNode(
HintCode.NULL_AWARE_IN_LOGICAL_OPERATOR, childOfParent);
return;
}
// NULL_AWARE_BEFORE_OPERATOR
if (parent is BinaryExpression &&
![TokenType.EQ_EQ, TokenType.BANG_EQ, TokenType.QUESTION_QUESTION]
.contains(parent.operator.type) &&
parent.leftOperand == childOfParent) {
_errorReporter.reportErrorForNode(
HintCode.NULL_AWARE_BEFORE_OPERATOR, childOfParent);
return;
}
}
/**
* 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 &&
_typeSystem.isMoreSpecificThan(lhsType, 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;
}
void _checkRequiredParameter(FormalParameterList node) {
final requiredParameters =
node.parameters.where((p) => p.declaredElement?.hasRequired == true);
final nonNamedParamsWithRequired =
requiredParameters.where((p) => !p.isNamed);
final namedParamsWithRequiredAndDefault = requiredParameters
.where((p) => p.isNamed)
.where((p) => p.declaredElement.defaultValueCode != null);
final paramsToHint = [
nonNamedParamsWithRequired,
namedParamsWithRequiredAndDefault
].expand((e) => e);
for (final param in paramsToHint) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_REQUIRED_PARAM, param, [param.identifier.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);
}
/**
* Return `true` if the given [type] represents `Future<void>`.
*/
bool _isFutureVoid(DartType type) {
if (type.isDartAsyncFuture) {
List<DartType> typeArgs = (type as InterfaceType).typeArguments;
if (typeArgs.length == 1 && typeArgs[0].isVoid) {
return true;
}
}
return false;
}
/**
* 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.
Map<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);
}
if (node.elementAnnotation?.isSealed == true &&
!(node.parent is ClassDeclaration ||
node.parent is ClassTypeAlias ||
node.parent is MixinDeclaration)) {
_errorReporter.reportErrorForNode(
HintCode.INVALID_SEALED_ANNOTATION, node.parent, [node.element.name]);
}
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) {
TypeName typeName = node.constructorName.type;
_checkForConstWithTypeParameters(typeName);
// We need to evaluate the constant to see if any errors occur during its
// evaluation.
ConstructorElement constructor = node.staticElement;
if (constructor != null) {
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.isConst) {
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.isConst;
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.declaredElement as VariableElementImpl;
EvaluationResultImpl result = element.evaluationResult;
if (result == null) {
// Variables marked "const" should have had their values computed by
// ConstantValueComputer. Other variables will only have had their
// values computed if the value was needed (e.g. final variables in a
// class containing const constructors).
assert(!node.isConst);
return 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;
}
/**
* Verify that the given [type] does not reference any type parameters.
*
* See [CompileTimeErrorCode.CONST_WITH_TYPE_PARAMETERS].
*/
void _checkForConstWithTypeParameters(TypeAnnotation type) {
// something wrong with AST
if (type is! TypeName) {
return;
}
TypeName typeName = type;
Identifier name = typeName.name;
if (name == null) {
return;
}
// should not be a type parameter
if (name.staticElement is TypeParameterElement) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_WITH_TYPE_PARAMETERS, name);
}
// check type arguments
TypeArgumentList typeArguments = typeName.typeArguments;
if (typeArguments != null) {
for (TypeAnnotation argument in typeArguments.arguments) {
_checkForConstWithTypeParameters(argument);
}
}
}
/**
* @return `true` if given [Type] implements operator <i>==</i>, and it is not
* <i>int</i> or <i>String</i>.
*/
bool _implementsEqualsWhenNotAllowed(DartType type) {
// ignore int or String
if (type == null || type == _intType || type == _typeProvider.stringType) {
return false;
} else if (type == _typeProvider.doubleType) {
return true;
}
// prepare ClassElement
Element element = type.element;
if (element is ClassElement) {
// lookup for ==
MethodElement method =
element.lookUpConcreteMethod("==", _currentLibrary);
if (method == null || method.enclosingElement.type.isObject) {
return false;
}
// there is == that we don't like
return true;
}
return false;
}
/**
* Given some computed [Expression], this method generates the passed [ErrorCode] on
* the node if its' value consists of information from a deferred library.
*
* @param expression the expression to be tested for a deferred library reference
* @param errorCode the error code to be used if the expression is or consists of a reference to a
* deferred library
*/
void _reportErrorIfFromDeferredLibrary(
Expression expression, ErrorCode errorCode) {
DeferredLibraryReferenceDetector referenceDetector =
new DeferredLibraryReferenceDetector();
expression.accept(referenceDetector);
if (referenceDetector.result) {
_errorReporter.reportErrorForNode(errorCode, expression);
}
}
/**
* Report any errors in the given list. Except for special cases, use the given error code rather
* than the one reported in the error.
*
* @param errors the errors that need to be reported
* @param errorCode the error code to be used
*/
void _reportErrors(List<AnalysisError> errors, ErrorCode errorCode) {
int length = errors.length;
for (int i = 0; i < length; i++) {
AnalysisError data = errors[i];
ErrorCode dataErrorCode = data.errorCode;
if (identical(dataErrorCode,
CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION) ||
identical(
dataErrorCode, CompileTimeErrorCode.CONST_EVAL_THROWS_IDBZE) ||
identical(dataErrorCode,
CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING) ||
identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL) ||
identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_INT) ||
identical(dataErrorCode, CompileTimeErrorCode.CONST_EVAL_TYPE_NUM) ||
identical(dataErrorCode,
CompileTimeErrorCode.RECURSIVE_COMPILE_TIME_CONSTANT) ||
identical(dataErrorCode,
CheckedModeCompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION) ||
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.declaredElement as VariableElementImpl;
element.evaluationResult = new EvaluationResultImpl(result);
}
}
}
/**
* Validates that the expressions of any field initializers in the class declaration are all
* compile time constants. Since this is only required if the class has a constant constructor,
* the error is reported at the constructor site.
*
* @param classDeclaration the class which should be validated
* @param errorSite the site at which errors should be reported.
*/
void _validateFieldInitializers(
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 evaluated we'll just report a single error.
AnalysisErrorListener errorListener =
AnalysisErrorListener.NULL_LISTENER;
ErrorReporter subErrorReporter =
new ErrorReporter(errorListener, _errorReporter.source);
DartObjectImpl result = initializer.accept(new ConstantVisitor(
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;
}
}
/**
* A visitor that finds dead code and unused labels.
*/
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;
/**
* The object used to track the usage of labels within a given label scope.
*/
_LabelTracker labelTracker;
/**
* Initialize a newly created dead code verifier that will report dead code to
* the given [errorReporter] and will use the given [typeSystem] if one is
* provided.
*/
DeadCodeVerifier(this._errorReporter, {TypeSystem typeSystem})
: this._typeSystem = typeSystem ?? new StrongTypeSystemImpl(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.)
*/
@override
Object visitBlock(Block node) {
NodeList<Statement> statements = node.statements;
_checkForDeadStatementsInNodeList(statements);
return null;
}
@override
Object visitBreakStatement(BreakStatement node) {
labelTracker?.recordUsage(node.label?.name);
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 visitContinueStatement(ContinueStatement node) {
labelTracker?.recordUsage(node.label?.name);
return null;
}
@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(library, 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 visitLabeledStatement(LabeledStatement node) {
_pushLabels(node.labels);
try {
super.visitLabeledStatement(node);
} finally {
_popLabels();
}
return null;
}
@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 visitSwitchStatement(SwitchStatement node) {
List<Label> labels = <Label>[];
for (SwitchMember member in node.members) {
labels.addAll(member.labels);
}
_pushLabels(labels);
try {
super.visitSwitchStatement(node);
} finally {
_popLabels();
}
return null;
}
@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) {
// An on-catch clause was 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 while 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 list of [statements], loop through the list searching for dead
* statements. If [allowMandated] is true, then allow dead statements that are
* 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], 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 the given [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;
}
/**
* Exit the most recently entered label scope after reporting any labels that
* were not referenced within that scope.
*/
void _popLabels() {
for (Label label in labelTracker.unusedLabels()) {
_errorReporter
.reportErrorForNode(HintCode.UNUSED_LABEL, label, [label.label.name]);
}
labelTracker = labelTracker.outerTracker;
}
/**
* Enter a new label scope in which the given [labels] are defined.
*/
void _pushLabels(List<Label> labels) {
labelTracker = new _LabelTracker(labelTracker, labels);
}
}
/**
* 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.
LibraryElement exportedLibrary = element.exportedLibrary;
if (exportedLibrary != null) {
Source exportedSource = 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.
LibraryElement importedLibrary = element.importedLibrary;
if (importedLibrary != null) {
Source importedSource = 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;