blob: b65279b4c1fabe521d8537d2c6b05b02080929f3 [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 engine.resolver;
import "dart:math" as math;
import 'dart:collection';
import 'package:analyzer/src/generated/utilities_collection.dart';
import 'ast.dart';
import 'constant.dart';
import 'element.dart';
import 'element_resolver.dart';
import 'engine.dart';
import 'error.dart';
import 'error_verifier.dart';
import 'html.dart' as ht;
import 'java_core.dart';
import 'java_engine.dart';
import 'scanner.dart' as sc;
import 'sdk.dart' show DartSdk, SdkLibrary;
import 'source.dart';
import 'static_type_analyzer.dart';
import 'utilities_dart.dart';
/**
* Callback signature used by ImplicitConstructorBuilder to register
* computations to be performed, and their dependencies. A call to this
* callback indicates that [computation] may be used to compute implicit
* constructors for [classElement], but that the computation may not be invoked
* until after implicit constructors have been built for [superclassElement].
*/
typedef void ImplicitConstructorBuilderCallback(ClassElement classElement,
ClassElement superclassElement, void computation());
typedef LibraryResolver LibraryResolverFactory(AnalysisContext context);
typedef ResolverVisitor ResolverVisitorFactory(
Library library, Source source, TypeProvider typeProvider);
typedef StaticTypeAnalyzer StaticTypeAnalyzerFactory(ResolverVisitor visitor);
typedef TypeResolverVisitor TypeResolverVisitorFactory(
Library library, Source source, TypeProvider typeProvider);
typedef void VoidFunction();
/**
* 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.
*/
ClassElement _enclosingClass;
/**
* The error reporter by which errors will be reported.
*/
final ErrorReporter _errorReporter;
/**
* 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;
/**
* Create a new instance of the [BestPracticesVerifier].
*
* @param errorReporter the error reporter
*/
BestPracticesVerifier(this._errorReporter, TypeProvider typeProvider)
: _futureNullType = typeProvider.futureNullType;
@override
Object visitArgumentList(ArgumentList node) {
_checkForArgumentTypesNotAssignableInList(node);
return super.visitArgumentList(node);
}
@override
Object visitAsExpression(AsExpression node) {
_checkForUnnecessaryCast(node);
return super.visitAsExpression(node);
}
@override
Object visitAssignmentExpression(AssignmentExpression node) {
sc.TokenType operatorType = node.operator.type;
if (operatorType == sc.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) {
ClassElement outerClass = _enclosingClass;
try {
_enclosingClass = node.element;
// Commented out until we decide that we want this hint in the analyzer
// checkForOverrideEqualsButNotHashCode(node);
return super.visitClassDeclaration(node);
} finally {
_enclosingClass = outerClass;
}
}
@override
Object visitExportDirective(ExportDirective node) {
_checkForDeprecatedMemberUse(node.uriElement, node);
return super.visitExportDirective(node);
}
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
_checkForMissingReturn(node.returnType, node.functionExpression.body);
return super.visitFunctionDeclaration(node);
}
@override
Object visitImportDirective(ImportDirective node) {
_checkForDeprecatedMemberUse(node.uriElement, node);
ImportElement importElement = node.element;
if (importElement != null) {
if (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) {
// This was determined to not be a good hint, see: dartbug.com/16029
//checkForOverridingPrivateMember(node);
_checkForMissingReturn(node.returnType, node.body);
return super.visitMethodDeclaration(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 visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
return super.visitRedirectingConstructorInvocation(node);
}
@override
Object visitSimpleIdentifier(SimpleIdentifier node) {
_checkForDeprecatedMemberUseAtIdentifier(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);
}
/**
* 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;
TypeName typeName = node.type;
DartType lhsType = expression.staticType;
DartType rhsType = typeName.type;
if (lhsType == null || rhsType == null) {
return false;
}
String rhsNameStr = typeName.name.name;
// if x is dynamic
if (rhsType.isDynamic && rhsNameStr == sc.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 != null ? rhsElement.library : null;
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;
}
/**
* 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 (!actualStaticType.isAssignableTo(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 != null
? expectedPropagatedType
: expectedStaticType;
DartType actualBestType =
actualPropagatedType != null ? actualPropagatedType : actualStaticType;
if (actualBestType != null && expectedBestType != null) {
if (!actualBestType.isAssignableTo(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 == null ? null : staticParameterElement.type;
ParameterElement propagatedParameterElement =
argument.propagatedParameterElement;
DartType propagatedParameterType = propagatedParameterElement == null
? null
: 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;
}
/**
* 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].
*/
bool _checkForDeprecatedMemberUse(Element element, AstNode node) {
if (element != null && element.isDeprecated) {
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.
ConstructorElement constructorElement = element;
displayName = constructorElement.enclosingElement.displayName;
if (!constructorElement.displayName.isEmpty) {
displayName = "$displayName.${constructorElement.displayName}";
}
}
_errorReporter.reportErrorForNode(
HintCode.DEPRECATED_MEMBER_USE, node, [displayName]);
return true;
}
return false;
}
/**
* 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].
*/
bool _checkForDeprecatedMemberUseAtIdentifier(SimpleIdentifier identifier) {
if (identifier.inDeclarationContext()) {
return false;
}
AstNode parent = identifier.parent;
if ((parent is ConstructorName && identical(identifier, parent.name)) ||
(parent is SuperConstructorInvocation &&
identical(identifier, parent.constructorName)) ||
parent is HideCombinator) {
return false;
}
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 != sc.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
if (node.parent is ParenthesizedExpression) {
ParenthesizedExpression parenthesizedExpression =
_wrapParenthesizedExpression(node.parent as ParenthesizedExpression);
if (parenthesizedExpression.parent is MethodInvocation) {
MethodInvocation methodInvocation =
parenthesizedExpression.parent as MethodInvocation;
if (_TO_INT_METHOD_NAME == methodInvocation.methodName.name &&
methodInvocation.argumentList.arguments.isEmpty) {
_errorReporter.reportErrorForNode(
HintCode.DIVISION_OPTIMIZATION, methodInvocation);
return true;
}
}
}
return false;
}
/**
* 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 (!staticRightType.isAssignableTo(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 (!bestRightType.isAssignableTo(leftType)) {
_errorReporter.reportTypeErrorForNode(
HintCode.INVALID_ASSIGNMENT, rhs, [bestRightType, leftType]);
return true;
}
}
return false;
}
/**
* 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].
*/
bool _checkForMissingReturn(TypeName returnType, FunctionBody body) {
// Check that the method or function has a return type, and a function body
if (returnType == null || body == null) {
return false;
}
// Check that the body is a BlockFunctionBody
if (body is! BlockFunctionBody) {
return false;
}
// Generators are never required to have a return statement.
if (body.isGenerator) {
return false;
}
// Check that the type is resolvable, and is not "void"
DartType returnTypeType = returnType.type;
if (returnTypeType == null || returnTypeType.isVoid) {
return false;
}
// For async, give no hint if Future<Null> is assignable to the return
// type.
if (body.isAsynchronous && _futureNullType.isAssignableTo(returnTypeType)) {
return false;
}
// Check the block for a return statement, if not, create the hint
BlockFunctionBody blockFunctionBody = body as BlockFunctionBody;
if (!ExitDetector.exits(blockFunctionBody)) {
_errorReporter.reportErrorForNode(
HintCode.MISSING_RETURN, returnType, [returnTypeType.displayName]);
return true;
}
return false;
}
/**
* 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;
// }
/**
* 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;
}
/**
* Check for situations where the result of a method or function is used, when it returns 'void'.
*
* 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().
*
* @param node expression on the RHS of some assignment
* @return `true` if and only if a hint code is generated on the passed node
* See [HintCode.USE_OF_VOID_RESULT].
*/
bool _checkForUseOfVoidResult(Expression expression) {
if (expression == null || expression is! MethodInvocation) {
return false;
}
MethodInvocation methodInvocation = expression as MethodInvocation;
if (identical(methodInvocation.staticType, VoidTypeImpl.instance)) {
SimpleIdentifier methodName = methodInvocation.methodName;
_errorReporter.reportErrorForNode(
HintCode.USE_OF_VOID_RESULT, methodName, [methodName.name]);
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) {
if (parenthesizedExpression.parent is ParenthesizedExpression) {
return _wrapParenthesizedExpression(
parenthesizedExpression.parent as ParenthesizedExpression);
}
return parenthesizedExpression;
}
}
/**
* Instances of the class `ClassScope` implement the scope defined by a class.
*/
class ClassScope extends EnclosedScope {
/**
* Initialize a newly created scope enclosed within another scope.
*
* @param enclosingScope the scope in which this scope is lexically enclosed
* @param typeElement the element representing the type represented by this scope
*/
ClassScope(Scope enclosingScope, ClassElement typeElement)
: super(enclosingScope) {
if (typeElement == null) {
throw new IllegalArgumentException("class element cannot be null");
}
_defineMembers(typeElement);
}
@override
AnalysisError getErrorForDuplicate(Element existing, Element duplicate) {
if (existing is PropertyAccessorElement && duplicate is MethodElement) {
if (existing.nameOffset < duplicate.nameOffset) {
return new AnalysisError.con2(duplicate.source, duplicate.nameOffset,
duplicate.displayName.length,
CompileTimeErrorCode.METHOD_AND_GETTER_WITH_SAME_NAME,
[existing.displayName]);
} else {
return new AnalysisError.con2(existing.source, existing.nameOffset,
existing.displayName.length,
CompileTimeErrorCode.GETTER_AND_METHOD_WITH_SAME_NAME,
[existing.displayName]);
}
}
return super.getErrorForDuplicate(existing, duplicate);
}
/**
* Define the instance members defined by the class.
*
* @param typeElement the element representing the type represented by this scope
*/
void _defineMembers(ClassElement typeElement) {
for (PropertyAccessorElement accessor in typeElement.accessors) {
define(accessor);
}
for (MethodElement method in typeElement.methods) {
define(method);
}
}
}
/**
* A `CompilationUnitBuilder` builds an element model for a single compilation
* unit.
*/
class CompilationUnitBuilder {
/**
* Build the compilation unit element for the given [source] based on the
* compilation [unit] associated with the source. Throw an AnalysisException
* if the element could not be built.
*/
CompilationUnitElementImpl buildCompilationUnit(
Source source, CompilationUnit unit) {
return PerformanceStatistics.resolve.makeCurrentWhile(() {
if (unit == null) {
return null;
}
ElementHolder holder = new ElementHolder();
ElementBuilder builder = new ElementBuilder(holder);
unit.accept(builder);
CompilationUnitElementImpl element =
new CompilationUnitElementImpl(source.shortName);
element.accessors = holder.accessors;
element.enums = holder.enums;
element.functions = holder.functions;
element.source = source;
element.typeAliases = holder.typeAliases;
element.types = holder.types;
element.topLevelVariables = holder.topLevelVariables;
unit.element = element;
holder.validate();
return element;
});
}
}
/**
* 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 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, this._currentLibrary, this._typeProvider) {
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) {
ConstructorElement constructorElement = element;
// should 'const' constructor
if (!constructorElement.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) {
EvaluationResultImpl evaluationResult = node.evaluationResult;
// Note: evaluationResult might be null if there are circular references
// among constants.
if (evaluationResult != null) {
_reportErrors(evaluationResult.errors, null);
}
}
_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.con1(_typeProvider, subErrorReporter));
if (result != null) {
if (keys.contains(result)) {
invalidKeys.add(key);
} else {
keys.add(result);
}
} else {
reportEqualKeys = false;
}
}
}
if (reportEqualKeys) {
for (Expression key in invalidKeys) {
_errorReporter.reportErrorForNode(
StaticWarningCode.EQUAL_KEYS_IN_MAP, key);
}
}
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) {
SwitchCase switchCase = switchMember;
Expression expression = switchCase.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) {
VariableElementImpl element = node.element as VariableElementImpl;
EvaluationResultImpl result = element.evaluationResult;
if (result == null) {
//
// Normally we don't need to visit const variable declarations because
// we have already computed their values. But if we missed it for some
// reason, this gives us a second chance.
//
result = new EvaluationResultImpl.con1(_validate(initializer,
CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE));
element.evaluationResult = result;
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) {
return false;
}
ClassElement classElement = element as ClassElement;
// lookup for ==
MethodElement method =
classElement.lookUpConcreteMethod("==", _currentLibrary);
if (method == null || method.enclosingElement.type.isObject) {
return false;
}
// there is == that we don't like
return true;
}
/**
* 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) {
for (AnalysisError data in errors) {
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,
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.con2(
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.con1(_typeProvider, 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) {
if (argument is NamedExpression) {
argument = (argument as NamedExpression).expression;
}
_validate(
argument, CompileTimeErrorCode.CONST_WITH_NON_CONSTANT_ARGUMENT);
}
}
/**
* Validates that the expressions of the given initializers (of a constant constructor) are all
* compile time constants.
*
* @param constructor the constant constructor declaration to validate
*/
void _validateConstructorInitializers(ConstructorDeclaration constructor) {
List<ParameterElement> parameterElements =
constructor.parameters.parameterElements;
NodeList<ConstructorInitializer> initializers = constructor.initializers;
for (ConstructorInitializer initializer in initializers) {
if (initializer is ConstructorFieldInitializer) {
ConstructorFieldInitializer fieldInitializer = initializer;
_validateInitializerExpression(
parameterElements, fieldInitializer.expression);
}
if (initializer is RedirectingConstructorInvocation) {
RedirectingConstructorInvocation invocation = initializer;
_validateInitializerInvocationArguments(
parameterElements, invocation.argumentList);
}
if (initializer is SuperConstructorInvocation) {
SuperConstructorInvocation invocation = initializer;
_validateInitializerInvocationArguments(
parameterElements, invocation.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) {
DefaultFormalParameter defaultParameter = parameter;
Expression defaultValue = defaultParameter.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.con1(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) {
FieldDeclaration fieldDeclaration = member;
if (!fieldDeclaration.isStatic) {
for (VariableDeclaration variableDeclaration
in fieldDeclaration.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.con1(_typeProvider, 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));
_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) {
TypeName typeName = node.type;
DartType type = typeName.type;
if (type != null && type.element != null) {
Element element = type.element;
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;
/**
* Create a new instance of the [DeadCodeVerifier].
*
* @param errorReporter the error reporter
*/
DeadCodeVerifier(this._errorReporter);
@override
Object visitBinaryExpression(BinaryExpression node) {
sc.Token operator = node.operator;
bool isAmpAmp = operator.type == sc.TokenType.AMPERSAND_AMPERSAND;
bool isBarBar = operator.type == sc.TokenType.BAR_BAR;
if (isAmpAmp || isBarBar) {
Expression lhsCondition = node.leftOperand;
if (!_isDebugConstant(lhsCondition)) {
EvaluationResultImpl lhsResult = _getConstantBooleanValue(lhsCondition);
if (lhsResult != null) {
if (lhsResult.value.isTrue && isBarBar) {
// report error on else block: true || !e!
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.rightOperand);
// only visit the LHS:
_safelyVisit(lhsCondition);
return null;
} else if (lhsResult.value.isFalse && isAmpAmp) {
// report error on if block: false && !e!
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.rightOperand);
// only visit the LHS:
_safelyVisit(lhsCondition);
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:
// safelyVisit(rhsCondition);
// 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:
// safelyVisit(rhsCondition);
// 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;
_safelyVisit(conditionExpression);
if (!_isDebugConstant(conditionExpression)) {
EvaluationResultImpl result =
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.isTrue) {
// report error on else block: true ? 1 : !2!
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.elseExpression);
_safelyVisit(node.thenExpression);
return null;
} else {
// report error on if block: false ? !1! : 2
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.thenExpression);
_safelyVisit(node.elseExpression);
return null;
}
}
}
return super.visitConditionalExpression(node);
}
@override
Object visitIfStatement(IfStatement node) {
Expression conditionExpression = node.condition;
_safelyVisit(conditionExpression);
if (!_isDebugConstant(conditionExpression)) {
EvaluationResultImpl result =
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.isTrue) {
// report error on else block: if(true) {} else {!}
Statement elseStatement = node.elseStatement;
if (elseStatement != null) {
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, elseStatement);
_safelyVisit(node.thenStatement);
return null;
}
} else {
// report error on if block: if (false) {!} else {}
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.thenStatement);
_safelyVisit(node.elseStatement);
return null;
}
}
}
return super.visitIfStatement(node);
}
@override
Object visitSwitchCase(SwitchCase node) {
_checkForDeadStatementsInNodeList(node.statements);
return super.visitSwitchCase(node);
}
@override
Object visitSwitchDefault(SwitchDefault node) {
_checkForDeadStatementsInNodeList(node.statements);
return super.visitSwitchDefault(node);
}
@override
Object visitTryStatement(TryStatement node) {
_safelyVisit(node.body);
_safelyVisit(node.finallyBlock);
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
TypeName typeName = catchClause.exceptionType;
if (typeName != null && typeName.type != null) {
DartType currentType = typeName.type;
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).
_safelyVisit(catchClause);
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;
}
}
for (DartType type in visitedTypes) {
if (currentType.isSubtypeOf(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);
}
_safelyVisit(catchClause);
} 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).
_safelyVisit(catchClause);
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;
_safelyVisit(conditionExpression);
if (!_isDebugConstant(conditionExpression)) {
EvaluationResultImpl result =
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.isFalse) {
// report error on if block: while (false) {!}
_errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.body);
return null;
}
}
}
_safelyVisit(node.body);
return null;
}
/**
* Given some [NodeList] of [Statement]s, from either a [Block] or
* [SwitchMember], this loops through the list in reverse order searching for statements
* after a return, unlabeled break or unlabeled continue statement to mark them as dead code.
*
* @param statements some ordered list of statements in a [Block] or [SwitchMember]
*/
void _checkForDeadStatementsInNodeList(NodeList<Statement> statements) {
int size = statements.length;
for (int i = 0; i < size; i++) {
Statement currentStatement = statements[i];
_safelyVisit(currentStatement);
bool returnOrBreakingStatement = currentStatement is ReturnStatement ||
(currentStatement is BreakStatement &&
currentStatement.label == null) ||
(currentStatement is ContinueStatement &&
currentStatement.label == null);
if (returnOrBreakingStatement && i != size - 1) {
Statement nextStatement = statements[i + 1];
Statement lastStatement = statements[size - 1];
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.con1(
new DartObjectImpl(null, BoolState.from(true)));
} else {
return new EvaluationResultImpl.con1(
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) {
Identifier identifier = expression;
element = identifier.staticElement;
} else if (expression is PropertyAccess) {
PropertyAccess propertyAccess = expression;
element = propertyAccess.propertyName.staticElement;
}
if (element is PropertyAccessorElement) {
PropertyInducingElement variable = element.variable;
return variable != null && variable.isConst;
}
return false;
}
/**
* If the given node is not `null`, visit this instance of the dead code verifier.
*
* @param node the node to be visited
*/
void _safelyVisit(AstNode node) {
if (node != null) {
node.accept(this);
}
}
}
/**
* Instances of the class `DeclarationResolver` are used to resolve declarations in an AST
* structure to already built elements.
*/
class DeclarationResolver extends RecursiveAstVisitor<Object> {
/**
* The compilation unit containing the AST nodes being visited.
*/
CompilationUnitElement _enclosingUnit;
/**
* The function type alias containing the AST nodes being visited, or `null` if we are not
* in the scope of a function type alias.
*/
FunctionTypeAliasElement _enclosingAlias;
/**
* The class containing the AST nodes being visited, or `null` if we are not in the scope of
* a class.
*/
ClassElement _enclosingClass;
/**
* The method or function containing the AST nodes being visited, or `null` if we are not in
* the scope of a method or function.
*/
ExecutableElement _enclosingExecutable;
/**
* The parameter containing the AST nodes being visited, or `null` if we are not in the
* scope of a parameter.
*/
ParameterElement _enclosingParameter;
/**
* Resolve the declarations within the given compilation unit to the elements rooted at the given
* element.
*
* @param unit the compilation unit to be resolved
* @param element the root of the element model used to resolve the AST nodes
*/
void resolve(CompilationUnit unit, CompilationUnitElement element) {
_enclosingUnit = element;
unit.element = element;
unit.accept(this);
}
@override
Object visitCatchClause(CatchClause node) {
SimpleIdentifier exceptionParameter = node.exceptionParameter;
if (exceptionParameter != null) {
List<LocalVariableElement> localVariables =
_enclosingExecutable.localVariables;
_findIdentifier(localVariables, exceptionParameter);
SimpleIdentifier stackTraceParameter = node.stackTraceParameter;
if (stackTraceParameter != null) {
_findIdentifier(localVariables, stackTraceParameter);
}
}
return super.visitCatchClause(node);
}
@override
Object visitClassDeclaration(ClassDeclaration node) {
ClassElement outerClass = _enclosingClass;
try {
SimpleIdentifier className = node.name;
_enclosingClass = _findIdentifier(_enclosingUnit.types, className);
return super.visitClassDeclaration(node);
} finally {
_enclosingClass = outerClass;
}
}
@override
Object visitClassTypeAlias(ClassTypeAlias node) {
ClassElement outerClass = _enclosingClass;
try {
SimpleIdentifier className = node.name;
_enclosingClass = _findIdentifier(_enclosingUnit.types, className);
return super.visitClassTypeAlias(node);
} finally {
_enclosingClass = outerClass;
}
}
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
ExecutableElement outerExecutable = _enclosingExecutable;
try {
SimpleIdentifier constructorName = node.name;
if (constructorName == null) {
_enclosingExecutable = _enclosingClass.unnamedConstructor;
} else {
_enclosingExecutable =
_enclosingClass.getNamedConstructor(constructorName.name);
constructorName.staticElement = _enclosingExecutable;
}
node.element = _enclosingExecutable as ConstructorElement;
return super.visitConstructorDeclaration(node);
} finally {
_enclosingExecutable = outerExecutable;
}
}
@override
Object visitDeclaredIdentifier(DeclaredIdentifier node) {
SimpleIdentifier variableName = node.identifier;
_findIdentifier(_enclosingExecutable.localVariables, variableName);
return super.visitDeclaredIdentifier(node);
}
@override
Object visitDefaultFormalParameter(DefaultFormalParameter node) {
SimpleIdentifier parameterName = node.parameter.identifier;
ParameterElement element = _getElementForParameter(node, parameterName);
Expression defaultValue = node.defaultValue;
if (defaultValue != null) {
ExecutableElement outerExecutable = _enclosingExecutable;
try {
if (element == null) {
// TODO(brianwilkerson) Report this internal error.
} else {
_enclosingExecutable = element.initializer;
}
defaultValue.accept(this);
} finally {
_enclosingExecutable = outerExecutable;
}
}
ParameterElement outerParameter = _enclosingParameter;
try {
_enclosingParameter = element;
return super.visitDefaultFormalParameter(node);
} finally {
_enclosingParameter = outerParameter;
}
}
@override
Object visitEnumDeclaration(EnumDeclaration node) {
ClassElement enclosingEnum =
_findIdentifier(_enclosingUnit.enums, node.name);
List<FieldElement> constants = enclosingEnum.fields;
for (EnumConstantDeclaration constant in node.constants) {
_findIdentifier(constants, constant.name);
}
return super.visitEnumDeclaration(node);
}
@override
Object visitExportDirective(ExportDirective node) {
String uri = _getStringValue(node.uri);
if (uri != null) {
LibraryElement library = _enclosingUnit.library;
ExportElement exportElement = _findExport(library.exports,
_enclosingUnit.context.sourceFactory.resolveUri(
_enclosingUnit.source, uri));
node.element = exportElement;
}
return super.visitExportDirective(node);
}
@override
Object visitFieldFormalParameter(FieldFormalParameter node) {
if (node.parent is! DefaultFormalParameter) {
SimpleIdentifier parameterName = node.identifier;
ParameterElement element = _getElementForParameter(node, parameterName);
ParameterElement outerParameter = _enclosingParameter;
try {
_enclosingParameter = element;
return super.visitFieldFormalParameter(node);
} finally {
_enclosingParameter = outerParameter;
}
} else {
return super.visitFieldFormalParameter(node);
}
}
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement outerExecutable = _enclosingExecutable;
try {
SimpleIdentifier functionName = node.name;
sc.Token property = node.propertyKeyword;
if (property == null) {
if (_enclosingExecutable != null) {
_enclosingExecutable =
_findIdentifier(_enclosingExecutable.functions, functionName);
} else {
_enclosingExecutable =
_findIdentifier(_enclosingUnit.functions, functionName);
}
} else {
PropertyAccessorElement accessor =
_findIdentifier(_enclosingUnit.accessors, functionName);
if ((property as sc.KeywordToken).keyword == sc.Keyword.SET) {
accessor = accessor.variable.setter;
functionName.staticElement = accessor;
}
_enclosingExecutable = accessor;
}
node.functionExpression.element = _enclosingExecutable;
return super.visitFunctionDeclaration(node);
} finally {
_enclosingExecutable = outerExecutable;
}
}
@override
Object visitFunctionExpression(FunctionExpression node) {
if (node.parent is! FunctionDeclaration) {
FunctionElement element =
_findAtOffset(_enclosingExecutable.functions, node.beginToken.offset);
node.element = element;
}
ExecutableElement outerExecutable = _enclosingExecutable;
try {
_enclosingExecutable = node.element;
return super.visitFunctionExpression(node);
} finally {
_enclosingExecutable = outerExecutable;
}
}
@override
Object visitFunctionTypeAlias(FunctionTypeAlias node) {
FunctionTypeAliasElement outerAlias = _enclosingAlias;
try {
SimpleIdentifier aliasName = node.name;
_enclosingAlias =
_findIdentifier(_enclosingUnit.functionTypeAliases, aliasName);
return super.visitFunctionTypeAlias(node);
} finally {
_enclosingAlias = outerAlias;
}
}
@override
Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
if (node.parent is! DefaultFormalParameter) {
SimpleIdentifier parameterName = node.identifier;
ParameterElement element = _getElementForParameter(node, parameterName);
ParameterElement outerParameter = _enclosingParameter;
try {
_enclosingParameter = element;
return super.visitFunctionTypedFormalParameter(node);
} finally {
_enclosingParameter = outerParameter;
}
} else {
return super.visitFunctionTypedFormalParameter(node);
}
}
@override
Object visitImportDirective(ImportDirective node) {
String uri = _getStringValue(node.uri);
if (uri != null) {
LibraryElement library = _enclosingUnit.library;
ImportElement importElement = _findImport(library.imports,
_enclosingUnit.context.sourceFactory.resolveUri(
_enclosingUnit.source, uri), node.prefix);
node.element = importElement;
}
return super.visitImportDirective(node);
}
@override
Object visitLabeledStatement(LabeledStatement node) {
for (Label label in node.labels) {
SimpleIdentifier labelName = label.label;
_findIdentifier(_enclosingExecutable.labels, labelName);
}
return super.visitLabeledStatement(node);
}
@override
Object visitLibraryDirective(LibraryDirective node) {
node.element = _enclosingUnit.library;
return super.visitLibraryDirective(node);
}
@override
Object visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement outerExecutable = _enclosingExecutable;
try {
sc.Token property = node.propertyKeyword;
SimpleIdentifier methodName = node.name;
String nameOfMethod = methodName.name;
if (nameOfMethod == sc.TokenType.MINUS.lexeme &&
node.parameters.parameters.length == 0) {
nameOfMethod = "unary-";
}
if (property == null) {
_enclosingExecutable = _findWithNameAndOffset(
_enclosingClass.methods, nameOfMethod, methodName.offset);
methodName.staticElement = _enclosingExecutable;
} else {
PropertyAccessorElement accessor =
_findIdentifier(_enclosingClass.accessors, methodName);
if ((property as sc.KeywordToken).keyword == sc.Keyword.SET) {
accessor = accessor.variable.setter;
methodName.staticElement = accessor;
}
_enclosingExecutable = accessor;
}
return super.visitMethodDeclaration(node);
} finally {
_enclosingExecutable = outerExecutable;
}
}
@override
Object visitPartDirective(PartDirective node) {
String uri = _getStringValue(node.uri);
if (uri != null) {
Source partSource = _enclosingUnit.context.sourceFactory.resolveUri(
_enclosingUnit.source, uri);
node.element = _findPart(_enclosingUnit.library.parts, partSource);
}
return super.visitPartDirective(node);
}
@override
Object visitPartOfDirective(PartOfDirective node) {
node.element = _enclosingUnit.library;
return super.visitPartOfDirective(node);
}
@override
Object visitSimpleFormalParameter(SimpleFormalParameter node) {
if (node.parent is! DefaultFormalParameter) {
SimpleIdentifier parameterName = node.identifier;
ParameterElement element = _getElementForParameter(node, parameterName);
ParameterElement outerParameter = _enclosingParameter;
try {
_enclosingParameter = element;
return super.visitSimpleFormalParameter(node);
} finally {
_enclosingParameter = outerParameter;
}
} else {}
return super.visitSimpleFormalParameter(node);
}
@override
Object visitSwitchCase(SwitchCase node) {
for (Label label in node.labels) {
SimpleIdentifier labelName = label.label;
_findIdentifier(_enclosingExecutable.labels, labelName);
}
return super.visitSwitchCase(node);
}
@override
Object visitSwitchDefault(SwitchDefault node) {
for (Label label in node.labels) {
SimpleIdentifier labelName = label.label;
_findIdentifier(_enclosingExecutable.labels, labelName);
}
return super.visitSwitchDefault(node);
}
@override
Object visitTypeParameter(TypeParameter node) {
SimpleIdentifier parameterName = node.name;
if (_enclosingClass != null) {
_findIdentifier(_enclosingClass.typeParameters, parameterName);
} else if (_enclosingAlias != null) {
_findIdentifier(_enclosingAlias.typeParameters, parameterName);
}
return super.visitTypeParameter(node);
}
@override
Object visitVariableDeclaration(VariableDeclaration node) {
VariableElement element = null;
SimpleIdentifier variableName = node.name;
if (_enclosingExecutable != null) {
element =
_findIdentifier(_enclosingExecutable.localVariables, variableName);
}
if (element == null && _enclosingClass != null) {
element = _findIdentifier(_enclosingClass.fields, variableName);
}
if (element == null && _enclosingUnit != null) {
element = _findIdentifier(_enclosingUnit.topLevelVariables, variableName);
}
Expression initializer = node.initializer;
if (initializer != null) {
ExecutableElement outerExecutable = _enclosingExecutable;
try {
if (element == null) {
// TODO(brianwilkerson) Report this internal error.
} else {
_enclosingExecutable = element.initializer;
}
return super.visitVariableDeclaration(node);
} finally {
_enclosingExecutable = outerExecutable;
}
}
return super.visitVariableDeclaration(node);
}
/**
* Return the element in the given array of elements that was created for the declaration at the
* given offset. This method should only be used when there is no name
*
* @param elements the elements of the appropriate kind that exist in the current context
* @param offset the offset of the name of the element to be returned
* @return the element at the given offset
*/
Element _findAtOffset(List<Element> elements, int offset) =>
_findWithNameAndOffset(elements, "", offset);
/**
* Return the export element from the given array whose library has the given source, or
* `null` if there is no such export.
*
* @param exports the export elements being searched
* @param source the source of the library associated with the export element to being searched
* for
* @return the export element whose library has the given source
*/
ExportElement _findExport(List<ExportElement> exports, Source source) {
for (ExportElement export in exports) {
if (export.exportedLibrary.source == source) {
return export;
}
}
return null;
}
/**
* Return the element in the given array of elements that was created for the declaration with the
* given name.
*
* @param elements the elements of the appropriate kind that exist in the current context
* @param identifier the name node in the declaration of the element to be returned
* @return the element created for the declaration with the given name
*/
Element _findIdentifier(List<Element> elements, SimpleIdentifier identifier) {
Element element =
_findWithNameAndOffset(elements, identifier.name, identifier.offset);
identifier.staticElement = element;
return element;
}
/**
* Return the import element from the given array whose library has the given source and that has
* the given prefix, or `null` if there is no such import.
*
* @param imports the import elements being searched
* @param source the source of the library associated with the import element to being searched
* for
* @param prefix the prefix with which the library was imported
* @return the import element whose library has the given source and prefix
*/
ImportElement _findImport(
List<ImportElement> imports, Source source, SimpleIdentifier prefix) {
for (ImportElement element in imports) {
if (element.importedLibrary.source == source) {
PrefixElement prefixElement = element.prefix;
if (prefix == null) {
if (prefixElement == null) {
return element;
}
} else {
if (prefixElement != null &&
prefix.name == prefixElement.displayName) {
return element;
}
}
}
}
return null;
}
/**
* Return the element for the part with the given source, or `null` if there is no element
* for the given source.
*
* @param parts the elements for the parts
* @param partSource the source for the part whose element is to be returned
* @return the element for the part with the given source
*/
CompilationUnitElement _findPart(
List<CompilationUnitElement> parts, Source partSource) {
for (CompilationUnitElement part in parts) {
if (part.source == partSource) {
return part;
}
}
return null;
}
/**
* Return the element in the given array of elements that was created for the declaration with the
* given name at the given offset.
*
* @param elements the elements of the appropriate kind that exist in the current context
* @param name the name of the element to be returned
* @param offset the offset of the name of the element to be returned
* @return the element with the given name and offset
*/
Element _findWithNameAndOffset(
List<Element> elements, String name, int offset) {
for (Element element in elements) {
if (element.displayName == name && element.nameOffset == offset) {
return element;
}
}
return null;
}
/**
* Search the most closely enclosing list of parameters for a parameter with the given name.
*
* @param node the node defining the parameter with the given name
* @param parameterName the name of the parameter being searched for
* @return the element representing the parameter with that name
*/
ParameterElement _getElementForParameter(
FormalParameter node, SimpleIdentifier parameterName) {
List<ParameterElement> parameters = null;
if (_enclosingParameter != null) {
parameters = _enclosingParameter.parameters;
}
if (parameters == null && _enclosingExecutable != null) {
parameters = _enclosingExecutable.parameters;
}
if (parameters == null && _enclosingAlias != null) {
parameters = _enclosingAlias.parameters;
}
ParameterElement element =
parameters == null ? null : _findIdentifier(parameters, parameterName);
if (element == null) {
StringBuffer buffer = new StringBuffer();
buffer.writeln("Invalid state found in the Analysis Engine:");
buffer.writeln(
"DeclarationResolver.getElementForParameter() is visiting a parameter that does not appear to be in a method or function.");
buffer.writeln("Ancestors:");
AstNode parent = node.parent;
while (parent != null) {
buffer.writeln(parent.runtimeType.toString());
buffer.writeln("---------");
parent = parent.parent;
}
AnalysisEngine.instance.logger.logError(buffer.toString(),
new CaughtException(new AnalysisException(), null));
}
return element;
}
/**
* Return the value of the given string literal, or `null` if the string is not a constant
* string without any string interpolation.
*
* @param literal the string literal whose value is to be returned
* @return the value of the given string literal
*/
String _getStringValue(StringLiteral literal) {
if (literal is StringInterpolation) {
return null;
}
return literal.stringValue;
}
}
/**
* Instances of the class `ElementBuilder` traverse an AST structure and build the element
* model representing the AST structure.
*/
class ElementBuilder extends RecursiveAstVisitor<Object> {
/**
* The element holder associated with the element that is currently being built.
*/
ElementHolder _currentHolder;
/**
* A flag indicating whether a variable declaration is in the context of a field declaration.
*/
bool _inFieldContext = false;
/**
* A flag indicating whether a variable declaration is within the body of a method or function.
*/
bool _inFunction = false;
/**
* A flag indicating whether the class currently being visited can be used as a mixin.
*/
bool _isValidMixin = false;
/**
* A collection holding the function types defined in a class that need to have their type
* arguments set to the types of the type parameters for the class, or `null` if we are not
* currently processing nodes within a class.
*/
List<FunctionTypeImpl> _functionTypesToFix = null;
/**
* A table mapping field names to field elements for the fields defined in the current class, or
* `null` if we are not in the scope of a class.
*/
HashMap<String, FieldElement> _fieldMap;
/**
* Initialize a newly created element builder to build the elements for a compilation unit.
*
* @param initialHolder the element holder associated with the compilation unit being built
*/
ElementBuilder(ElementHolder initialHolder) {
_currentHolder = initialHolder;
}
@override
Object visitBlock(Block node) {
bool wasInField = _inFieldContext;
_inFieldContext = false;
try {
node.visitChildren(this);
} finally {
_inFieldContext = wasInField;
}
return null;
}
@override
Object visitCatchClause(CatchClause node) {
SimpleIdentifier exceptionParameter = node.exceptionParameter;
if (exceptionParameter != null) {
// exception
LocalVariableElementImpl exception =
new LocalVariableElementImpl.forNode(exceptionParameter);
_currentHolder.addLocalVariable(exception);
exceptionParameter.staticElement = exception;
// stack trace
SimpleIdentifier stackTraceParameter = node.stackTraceParameter;
if (stackTraceParameter != null) {
LocalVariableElementImpl stackTrace =
new LocalVariableElementImpl.forNode(stackTraceParameter);
_currentHolder.addLocalVariable(stackTrace);
stackTraceParameter.staticElement = stackTrace;
}
}
return super.visitCatchClause(node);
}
@override
Object visitClassDeclaration(ClassDeclaration node) {
ElementHolder holder = new ElementHolder();
_isValidMixin = true;
_functionTypesToFix = new List<FunctionTypeImpl>();
//
// Process field declarations before constructors and methods so that field
// formal parameters can be correctly resolved to their fields.
//
ElementHolder previousHolder = _currentHolder;
_currentHolder = holder;
try {
List<ClassMember> nonFields = new List<ClassMember>();
node.visitChildren(
new _ElementBuilder_visitClassDeclaration(this, nonFields));
_buildFieldMap(holder.fieldsWithoutFlushing);
int count = nonFields.length;
for (int i = 0; i < count; i++) {
nonFields[i].accept(this);
}
} finally {
_currentHolder = previousHolder;
}
SimpleIdentifier className = node.name;
ClassElementImpl element = new ClassElementImpl.forNode(className);
List<TypeParameterElement> typeParameters = holder.typeParameters;
List<DartType> typeArguments = _createTypeParameterTypes(typeParameters);
InterfaceTypeImpl interfaceType = new InterfaceTypeImpl.con1(element);
interfaceType.typeArguments = typeArguments;
element.type = interfaceType;
List<ConstructorElement> constructors = holder.constructors;
if (constructors.length == 0) {
//
// Create the default constructor.
//
constructors = _createDefaultConstructors(interfaceType);
}
element.abstract = node.isAbstract;
element.accessors = holder.accessors;
element.constructors = constructors;
element.fields = holder.fields;
element.methods = holder.methods;
element.typeParameters = typeParameters;
element.validMixin = _isValidMixin;
int functionTypeCount = _functionTypesToFix.length;
for (int i = 0; i < functionTypeCount; i++) {
_functionTypesToFix[i].typeArguments = typeArguments;
}
_functionTypesToFix = null;
_currentHolder.addType(element);
className.staticElement = element;
_fieldMap = null;
holder.validate();
return null;
}
/**
* Implementation of this method should be synchronized with
* [visitClassDeclaration].
*/
void visitClassDeclarationIncrementally(ClassDeclaration node) {
//
// Process field declarations before constructors and methods so that field
// formal parameters can be correctly resolved to their fields.
//
ClassElement classElement = node.element;
_buildFieldMap(classElement.fields);
}
@override
Object visitClassTypeAlias(ClassTypeAlias node) {
ElementHolder holder = new ElementHolder();
_functionTypesToFix = new List<FunctionTypeImpl>();
_visitChildren(holder, node);
SimpleIdentifier className = node.name;
ClassElementImpl element = new ClassElementImpl.forNode(className);
element.abstract = node.abstractKeyword != null;
element.typedef = true;
List<TypeParameterElement> typeParameters = holder.typeParameters;
element.typeParameters = typeParameters;
List<DartType> typeArguments = _createTypeParameterTypes(typeParameters);
InterfaceTypeImpl interfaceType = new InterfaceTypeImpl.con1(element);
interfaceType.typeArguments = typeArguments;
element.type = interfaceType;
// set default constructor
element.constructors = _createDefaultConstructors(interfaceType);
for (FunctionTypeImpl functionType in _functionTypesToFix) {
functionType.typeArguments = typeArguments;
}
_functionTypesToFix = null;
_currentHolder.addType(element);
className.staticElement = element;
holder.validate();
return null;
}
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
_isValidMixin = false;
ElementHolder holder = new ElementHolder();
bool wasInFunction = _inFunction;
_inFunction = true;
try {
_visitChildren(holder, node);
} finally {
_inFunction = wasInFunction;
}
FunctionBody body = node.body;
SimpleIdentifier constructorName = node.name;
ConstructorElementImpl element =
new ConstructorElementImpl.forNode(constructorName);
if (node.factoryKeyword != null) {
element.factory = true;
}
element.functions = holder.functions;
element.labels = holder.labels;
element.localVariables = holder.localVariables;
element.parameters = holder.parameters;
element.const2 = node.constKeyword != null;
if (body.isAsynchronous) {
element.asynchronous = true;
}
if (body.isGenerator) {
element.generator = true;
}
_currentHolder.addConstructor(element);
node.element = element;
if (constructorName == null) {
Identifier returnType = node.returnType;
if (returnType != null) {
element.nameOffset = returnType.offset;
element.nameEnd = returnType.end;
}
} else {
constructorName.staticElement = element;
element.periodOffset = node.period.offset;
element.nameEnd = constructorName.end;
}
holder.validate();
return null;
}
@override
Object visitDeclaredIdentifier(DeclaredIdentifier node) {
SimpleIdentifier variableName = node.identifier;
LocalVariableElementImpl element =
new LocalVariableElementImpl.forNode(variableName);
ForEachStatement statement = node.parent as ForEachStatement;
int declarationEnd = node.offset + node.length;
int statementEnd = statement.offset + statement.length;
element.setVisibleRange(declarationEnd, statementEnd - declarationEnd - 1);
element.const3 = node.isConst;
element.final2 = node.isFinal;
_currentHolder.addLocalVariable(element);
variableName.staticElement = element;
return super.visitDeclaredIdentifier(node);
}
@override
Object visitDefaultFormalParameter(DefaultFormalParameter node) {
ElementHolder holder = new ElementHolder();
NormalFormalParameter normalParameter = node.parameter;
SimpleIdentifier parameterName = normalParameter.identifier;
ParameterElementImpl parameter;
if (normalParameter is FieldFormalParameter) {
parameter = new DefaultFieldFormalParameterElementImpl(parameterName);
FieldElement field =
_fieldMap == null ? null : _fieldMap[parameterName.name];
if (field != null) {
(parameter as DefaultFieldFormalParameterElementImpl).field = field;
}
} else {
parameter = new DefaultParameterElementImpl(parameterName);
}
parameter.const3 = node.isConst;
parameter.final2 = node.isFinal;
parameter.parameterKind = node.kind;
// set initializer, default value range
Expression defaultValue = node.defaultValue;
if (defaultValue != null) {
_visit(holder, defaultValue);
FunctionElementImpl initializer =
new FunctionElementImpl.forOffset(defaultValue.beginToken.offset);
initializer.functions = holder.functions;
initializer.labels = holder.labels;
initializer.localVariables = holder.localVariables;
initializer.parameters = holder.parameters;
initializer.synthetic = true;
parameter.initializer = initializer;
parameter.defaultValueCode = defaultValue.toSource();
}
// visible range
_setParameterVisibleRange(node, parameter);
_currentHolder.addParameter(parameter);
parameterName.staticElement = parameter;
normalParameter.accept(this);
holder.validate();
return null;
}
@override
Object visitEnumDeclaration(EnumDeclaration node) {
SimpleIdentifier enumName = node.name;
ClassElementImpl enumElement = new ClassElementImpl.forNode(enumName);
enumElement.enum2 = true;
InterfaceTypeImpl enumType = new InterfaceTypeImpl.con1(enumElement);
enumElement.type = enumType;
_currentHolder.addEnum(enumElement);
enumName.staticElement = enumElement;
return super.visitEnumDeclaration(node);
}
@override
Object visitFieldDeclaration(FieldDeclaration node) {
bool wasInField = _inFieldContext;
_inFieldContext = true;
try {
node.visitChildren(this);
} finally {
_inFieldContext = wasInField;
}
return null;
}
@override
Object visitFieldFormalParameter(FieldFormalParameter node) {
if (node.parent is! DefaultFormalParameter) {
SimpleIdentifier parameterName = node.identifier;
FieldElement field =
_fieldMap == null ? null : _fieldMap[parameterName.name];
FieldFormalParameterElementImpl parameter =
new FieldFormalParameterElementImpl(parameterName);
parameter.const3 = node.isConst;
parameter.final2 = node.isFinal;
parameter.parameterKind = node.kind;
if (field != null) {
parameter.field = field;
}
_currentHolder.addParameter(parameter);
parameterName.staticElement = parameter;
}
//
// The children of this parameter include any parameters defined on the type
// of this parameter.
//
ElementHolder holder = new ElementHolder();
_visitChildren(holder, node);
(node.element as ParameterElementImpl).parameters = holder.parameters;
holder.validate();
return null;
}
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
FunctionExpression expression = node.functionExpression;
if (expression != null) {
ElementHolder holder = new ElementHolder();
bool wasInFunction = _inFunction;
_inFunction = true;
try {
_visitChildren(holder, expression);
} finally {
_inFunction = wasInFunction;
}
FunctionBody body = expression.body;
sc.Token property = node.propertyKeyword;
if (property == null || _inFunction) {
SimpleIdentifier functionName = node.name;
FunctionElementImpl element =
new FunctionElementImpl.forNode(functionName);
element.functions = holder.functions;
element.labels = holder.labels;
element.localVariables = holder.localVariables;
element.parameters = holder.parameters;
if (body.isAsynchronous) {
element.asynchronous = true;
}
if (body.isGenerator) {
element.generator = true;
}
if (_inFunction) {
Block enclosingBlock = node.getAncestor((node) => node is Block);
if (enclosingBlock != null) {
int functionEnd = node.offset + node.length;
int blockEnd = enclosingBlock.offset + enclosingBlock.length;
element.setVisibleRange(functionEnd, blockEnd - functionEnd - 1);
}
}
_currentHolder.addFunction(element);
expression.element = element;
functionName.staticElement = element;
} else {
SimpleIdentifier propertyNameNode = node.name;
if (propertyNameNode == null) {
// TODO(brianwilkerson) Report this internal error.
return null;
}
String propertyName = propertyNameNode.name;
TopLevelVariableElementImpl variable = _currentHolder
.getTopLevelVariable(propertyName) as TopLevelVariableElementImpl;
if (variable == null) {
variable = new TopLevelVariableElementImpl(node.name.name, -1);
variable.final2 = true;
variable.synthetic = true;
_currentHolder.addTopLevelVariable(variable);
}
if (node.isGetter) {
PropertyAccessorElementImpl getter =
new PropertyAccessorElementImpl.forNode(propertyNameNode);
getter.functions = holder.functions;
getter.labels = holder.labels;
getter.localVariables = holder.localVariables;
if (body.isAsynchronous) {
getter.asynchronous = true;
}
if (body.isGenerator) {
getter.generator = true;
}
getter.variable = variable;
getter.getter = true;
getter.static = true;
variable.getter = getter;
_currentHolder.addAccessor(getter);
expression.element = getter;
propertyNameNode.staticElement = getter;
} else {
PropertyAccessorElementImpl setter =
new PropertyAccessorElementImpl.forNode(propertyNameNode);
setter.functions = holder.functions;
setter.labels = holder.labels;
setter.localVariables = holder.localVariables;
setter.parameters = holder.parameters;
if (body.isAsynchronous) {
setter.asynchronous = true;
}
if (body.isGenerator) {
setter.generator = true;
}
setter.variable = variable;
setter.setter = true;
setter.static = true;
variable.setter = setter;
variable.final2 = false;
_currentHolder.addAccessor(setter);
expression.element = setter;
propertyNameNode.staticElement = setter;
}
}
holder.validate();
}
return null;
}
@override
Object visitFunctionExpression(FunctionExpression node) {
ElementHolder holder = new ElementHolder();
bool wasInFunction = _inFunction;
_inFunction = true;
try {
_visitChildren(holder, node);
} finally {
_inFunction = wasInFunction;
}
FunctionBody body = node.body;
FunctionElementImpl element =
new FunctionElementImpl.forOffset(node.beginToken.offset);
element.fun