blob: 0d856c8b44edeb1428993b486380530b7093537d [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library analyzer.src.generated.resolver;
import 'dart:collection';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/ast.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.dart';
import 'package:analyzer/src/generated/error_verifier.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/java_engine.dart';
import 'package:analyzer/src/generated/scanner.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/static_type_analyzer.dart';
import 'package:analyzer/src/generated/type_system.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/task/strong/info.dart'
show InferredType, StaticInfo;
export 'package:analyzer/src/generated/type_system.dart';
/**
* Instances of the class `BestPracticesVerifier` traverse an AST structure looking for
* violations of Dart best practices.
*/
class BestPracticesVerifier extends RecursiveAstVisitor<Object> {
// static String _HASHCODE_GETTER_NAME = "hashCode";
static String _NULL_TYPE_NAME = "Null";
static String _TO_INT_METHOD_NAME = "toInt";
/**
* The class containing the AST nodes being visited, or `null` if we are not in the scope of
* a class.
*/
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;
/**
* The type system primitives
*/
TypeSystem _typeSystem;
/**
* Create a new instance of the [BestPracticesVerifier].
*
* @param errorReporter the error reporter
*/
BestPracticesVerifier(this._errorReporter, TypeProvider typeProvider,
{TypeSystem typeSystem})
: _futureNullType = typeProvider.futureNullType,
_typeSystem = (typeSystem != null) ? typeSystem : new TypeSystemImpl();
@override
Object visitArgumentList(ArgumentList node) {
_checkForArgumentTypesNotAssignableInList(node);
return super.visitArgumentList(node);
}
@override
Object visitAsExpression(AsExpression node) {
_checkForUnnecessaryCast(node);
return super.visitAsExpression(node);
}
@override
Object visitAssertStatement(AssertStatement node) {
_checkForPossibleNullCondition(node.condition);
return super.visitAssertStatement(node);
}
@override
Object visitAssignmentExpression(AssignmentExpression node) {
TokenType operatorType = node.operator.type;
if (operatorType == TokenType.EQ) {
_checkForUseOfVoidResult(node.rightHandSide);
_checkForInvalidAssignment(node.leftHandSide, node.rightHandSide);
} else {
_checkForDeprecatedMemberUse(node.bestElement, node);
}
return super.visitAssignmentExpression(node);
}
@override
Object visitBinaryExpression(BinaryExpression node) {
_checkForDivisionOptimizationHint(node);
_checkForDeprecatedMemberUse(node.bestElement, node);
return super.visitBinaryExpression(node);
}
@override
Object visitClassDeclaration(ClassDeclaration node) {
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 visitConditionalExpression(ConditionalExpression node) {
_checkForPossibleNullCondition(node.condition);
return super.visitConditionalExpression(node);
}
@override
Object visitDoStatement(DoStatement node) {
_checkForPossibleNullCondition(node.condition);
return super.visitDoStatement(node);
}
@override
Object visitExportDirective(ExportDirective node) {
_checkForDeprecatedMemberUse(node.uriElement, node);
return super.visitExportDirective(node);
}
@override
Object visitForStatement(ForStatement node) {
_checkForPossibleNullCondition(node.condition);
return super.visitForStatement(node);
}
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
_checkForMissingReturn(node.returnType, node.functionExpression.body);
return super.visitFunctionDeclaration(node);
}
@override
Object visitIfStatement(IfStatement node) {
_checkForPossibleNullCondition(node.condition);
return super.visitIfStatement(node);
}
@override
Object visitImportDirective(ImportDirective node) {
_checkForDeprecatedMemberUse(node.uriElement, node);
ImportElement importElement = node.element;
if (importElement != null) {
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);
_checkForUnnecessaryNoSuchMethod(node);
return super.visitMethodDeclaration(node);
}
@override
Object visitMethodInvocation(MethodInvocation node) {
_checkForCanBeNullAfterNullAware(node.realTarget, node.operator);
return super.visitMethodInvocation(node);
}
@override
Object visitPostfixExpression(PostfixExpression node) {
_checkForDeprecatedMemberUse(node.bestElement, node);
return super.visitPostfixExpression(node);
}
@override
Object visitPrefixExpression(PrefixExpression node) {
_checkForDeprecatedMemberUse(node.bestElement, node);
return super.visitPrefixExpression(node);
}
@override
Object visitPropertyAccess(PropertyAccess node) {
_checkForCanBeNullAfterNullAware(node.realTarget, 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);
return super.visitSimpleIdentifier(node);
}
@override
Object visitSuperConstructorInvocation(SuperConstructorInvocation node) {
_checkForDeprecatedMemberUse(node.staticElement, node);
return super.visitSuperConstructorInvocation(node);
}
@override
Object visitVariableDeclaration(VariableDeclaration node) {
_checkForUseOfVoidResult(node.initializer);
_checkForInvalidAssignment(node.name, node.initializer);
return super.visitVariableDeclaration(node);
}
@override
Object visitWhileStatement(WhileStatement node) {
_checkForPossibleNullCondition(node.condition);
return super.visitWhileStatement(node);
}
/**
* Check for the passed is expression for the unnecessary type check hint codes as well as null
* checks expressed using an is expression.
*
* @param node the is expression to check
* @return `true` if and only if a hint code is generated on the passed node
* See [HintCode.TYPE_CHECK_IS_NOT_NULL], [HintCode.TYPE_CHECK_IS_NULL],
* [HintCode.UNNECESSARY_TYPE_CHECK_TRUE], and
* [HintCode.UNNECESSARY_TYPE_CHECK_FALSE].
*/
bool _checkAllTypeChecks(IsExpression node) {
Expression expression = node.expression;
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 == 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 (!_typeSystem.isAssignableTo(actualStaticType, expectedStaticType)) {
// A warning was created in the ErrorVerifier, return false, don't
// create a hint when a warning has already been created.
return false;
}
}
//
// Hint case: test propagated type information
//
// Compute the best types to use.
DartType expectedBestType = expectedPropagatedType != null
? expectedPropagatedType
: expectedStaticType;
DartType actualBestType =
actualPropagatedType != null ? actualPropagatedType : actualStaticType;
if (actualBestType != null && expectedBestType != null) {
if (!_typeSystem.isAssignableTo(actualBestType, expectedBestType)) {
_errorReporter.reportTypeErrorForNode(
hintCode, expression, [actualBestType, expectedBestType]);
return true;
}
}
return false;
}
/**
* This verifies that the passed argument can be assigned to its corresponding parameter.
*
* This method corresponds to ErrorCode.checkForArgumentTypeNotAssignableForArgument.
*
* @param argument the argument to evaluate
* @return `true` if and only if an hint code is generated on the passed node
* See [HintCode.ARGUMENT_TYPE_NOT_ASSIGNABLE].
*/
bool _checkForArgumentTypeNotAssignableForArgument(Expression argument) {
if (argument == null) {
return false;
}
ParameterElement staticParameterElement = argument.staticParameterElement;
DartType staticParameterType =
staticParameterElement == 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;
}
/**
* Produce a hint if the given [target] could have a value of `null`.
*/
void _checkForCanBeNullAfterNullAware(Expression target, Token operator) {
if (operator?.type == TokenType.QUESTION_PERIOD) {
return;
}
while (target is ParenthesizedExpression) {
target = (target as ParenthesizedExpression).expression;
}
if (target is MethodInvocation) {
if (target.operator?.type == TokenType.QUESTION_PERIOD) {
_errorReporter.reportErrorForNode(
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, target);
}
} else if (target is PropertyAccess) {
if (target.operator.type == TokenType.QUESTION_PERIOD) {
_errorReporter.reportErrorForNode(
HintCode.CAN_BE_NULL_AFTER_NULL_AWARE, target);
}
}
}
/**
* Given some [Element], look at the associated metadata and report the use of the member if
* it is declared as deprecated.
*
* @param element some element to check for deprecated use of
* @param node the node use for the location of the error
* @return `true` if and only if a hint code is generated on the passed node
* See [HintCode.DEPRECATED_MEMBER_USE].
*/
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 != 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 (!_typeSystem.isAssignableTo(staticRightType, leftType)) {
// The warning was generated on this rhs
return false;
}
// Test for, and then generate the hint
DartType bestRightType = rhs.bestType;
if (leftType != null && bestRightType != null) {
if (!_typeSystem.isAssignableTo(bestRightType, leftType)) {
_errorReporter.reportTypeErrorForNode(
HintCode.INVALID_ASSIGNMENT, rhs, [bestRightType, leftType]);
return true;
}
}
return false;
}
/**
* 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 &&
_typeSystem.isAssignableTo(_futureNullType, 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;
}
/**
* Produce a hint if the given [condition] could have a value of `null`.
*/
void _checkForPossibleNullCondition(Expression condition) {
while (condition is ParenthesizedExpression) {
condition = (condition as ParenthesizedExpression).expression;
}
if (condition is BinaryExpression) {
_checkForPossibleNullConditionInBinaryExpression(condition);
} else if (condition is PrefixExpression) {
_checkForPossibleNullConditionInPrefixExpression(condition);
} else {
_checkForPossibleNullConditionInSimpleExpression(condition);
}
}
/**
* Produce a hint if any of the parts of the given binary [condition] could
* have a value of `null`.
*/
void _checkForPossibleNullConditionInBinaryExpression(
BinaryExpression condition) {
Token operator = condition.operator;
if (operator != null &&
(operator.type == TokenType.AMPERSAND_AMPERSAND ||
operator.type == TokenType.BAR_BAR)) {
_checkForPossibleNullCondition(condition.leftOperand);
_checkForPossibleNullCondition(condition.rightOperand);
}
}
/**
* Produce a hint if the operand of the given prefix [condition] could
* have a value of `null`.
*/
void _checkForPossibleNullConditionInPrefixExpression(
PrefixExpression condition) {
if (condition.operator?.type == TokenType.BANG) {
_checkForPossibleNullCondition(condition.operand);
}
}
/**
* Produce a hint if the given [condition] could have a value of `null`.
*/
void _checkForPossibleNullConditionInSimpleExpression(Expression condition) {
if (condition is MethodInvocation) {
Token operator = condition.operator;
if (operator != null && operator.type == TokenType.QUESTION_PERIOD) {
_errorReporter.reportErrorForNode(
HintCode.NULL_AWARE_IN_CONDITION, condition);
}
} else if (condition is PropertyAccess) {
Token operator = condition.operator;
if (operator != null && operator.type == TokenType.QUESTION_PERIOD) {
_errorReporter.reportErrorForNode(
HintCode.NULL_AWARE_IN_CONDITION, condition);
}
}
}
/**
* Check for the passed as expression for the [HintCode.UNNECESSARY_CAST] hint code.
*
* @param node the as expression to check
* @return `true` if and only if a hint code is generated on the passed node
* See [HintCode.UNNECESSARY_CAST].
*/
bool _checkForUnnecessaryCast(AsExpression node) {
// TODO(jwren) After dartbug.com/13732, revisit this, we should be able to
// remove the (x is! TypeParameterType) checks.
AstNode parent = node.parent;
if (parent is ConditionalExpression &&
(node == parent.thenExpression || node == parent.elseExpression)) {
Expression thenExpression = parent.thenExpression;
DartType thenType;
if (thenExpression is AsExpression) {
thenType = thenExpression.expression.staticType;
} else {
thenType = thenExpression.staticType;
}
Expression elseExpression = parent.elseExpression;
DartType elseType;
if (elseExpression is AsExpression) {
elseType = elseExpression.expression.staticType;
} else {
elseType = elseExpression.staticType;
}
if (thenType != null &&
elseType != null &&
!thenType.isDynamic &&
!elseType.isDynamic &&
!thenType.isMoreSpecificThan(elseType) &&
!elseType.isMoreSpecificThan(thenType)) {
return false;
}
}
DartType lhsType = node.expression.staticType;
DartType rhsType = node.type.type;
if (lhsType != null &&
rhsType != null &&
!lhsType.isDynamic &&
!rhsType.isDynamic &&
lhsType.isMoreSpecificThan(rhsType)) {
_errorReporter.reportErrorForNode(HintCode.UNNECESSARY_CAST, node);
return true;
}
return false;
}
/**
* 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;
// }
/**
* Generate a hint for `noSuchMethod` methods that do nothing except of
* calling another `noSuchMethod` that is not defined by `Object`.
*
* @return `true` if and only if a hint code is generated on the passed node
* See [HintCode.UNNECESSARY_NO_SUCH_METHOD].
*/
bool _checkForUnnecessaryNoSuchMethod(MethodDeclaration node) {
if (node.name.name != FunctionElement.NO_SUCH_METHOD_METHOD_NAME) {
return false;
}
bool isNonObjectNoSuchMethodInvocation(Expression invocation) {
if (invocation is MethodInvocation &&
invocation.target is SuperExpression &&
invocation.argumentList.arguments.length == 1) {
SimpleIdentifier name = invocation.methodName;
if (name.name == FunctionElement.NO_SUCH_METHOD_METHOD_NAME) {
Element methodElement = name.staticElement;
Element classElement = methodElement?.enclosingElement;
return methodElement is MethodElement &&
classElement is ClassElement &&
!classElement.type.isObject;
}
}
return false;
}
FunctionBody body = node.body;
if (body is ExpressionFunctionBody) {
if (isNonObjectNoSuchMethodInvocation(body.expression)) {
_errorReporter.reportErrorForNode(
HintCode.UNNECESSARY_NO_SUCH_METHOD, node);
return true;
}
} else if (body is BlockFunctionBody) {
List<Statement> statements = body.block.statements;
if (statements.length == 1) {
Statement returnStatement = statements.first;
if (returnStatement is ReturnStatement &&
isNonObjectNoSuchMethodInvocation(returnStatement.expression)) {
_errorReporter.reportErrorForNode(
HintCode.UNNECESSARY_NO_SUCH_METHOD, node);
return true;
}
}
}
return false;
}
/**
* Check for situations where the result of a method or function is used, when it returns 'void'.
*
* 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(
duplicate.source,
duplicate.nameOffset,
duplicate.nameLength,
CompileTimeErrorCode.METHOD_AND_GETTER_WITH_SAME_NAME,
[existing.displayName]);
} else {
return new AnalysisError(
existing.source,
existing.nameOffset,
existing.nameLength,
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. [librarySource] is the source for the
* containing library.
*/
CompilationUnitElementImpl buildCompilationUnit(
Source source, CompilationUnit unit, Source librarySource) {
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.librarySource = librarySource;
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 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) {
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) {
// We need to evaluate the constant to see if any errors occur during its
// evaluation.
ConstructorElement constructor = node.staticElement;
if (constructor != null) {
ConstantEvaluationEngine evaluationEngine =
new ConstantEvaluationEngine(_typeProvider, declaredVariables,
typeSystem: _typeSystem);
ConstantVisitor constantVisitor =
new ConstantVisitor(evaluationEngine, _errorReporter);
evaluationEngine.evaluateConstructorCall(
node,
node.argumentList.arguments,
constructor,
constantVisitor,
_errorReporter);
}
}
_validateInstanceCreationArguments(node);
return super.visitInstanceCreationExpression(node);
}
@override
Object visitListLiteral(ListLiteral node) {
super.visitListLiteral(node);
if (node.constKeyword != null) {
DartObjectImpl result;
for (Expression element in node.elements) {
result =
_validate(element, CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT);
if (result != null) {
_reportErrorIfFromDeferredLibrary(
element,
CompileTimeErrorCode
.NON_CONSTANT_LIST_ELEMENT_FROM_DEFERRED_LIBRARY);
}
}
}
return null;
}
@override
Object visitMapLiteral(MapLiteral node) {
super.visitMapLiteral(node);
bool isConst = node.constKeyword != null;
bool reportEqualKeys = true;
HashSet<DartObject> keys = new HashSet<DartObject>();
List<Expression> invalidKeys = new List<Expression>();
for (MapLiteralEntry entry in node.entries) {
Expression key = entry.key;
if (isConst) {
DartObjectImpl keyResult =
_validate(key, CompileTimeErrorCode.NON_CONSTANT_MAP_KEY);
Expression valueExpression = entry.value;
DartObjectImpl valueResult = _validate(
valueExpression, CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE);
if (valueResult != null) {
_reportErrorIfFromDeferredLibrary(
valueExpression,
CompileTimeErrorCode
.NON_CONSTANT_MAP_VALUE_FROM_DEFERRED_LIBRARY);
}
if (keyResult != null) {
_reportErrorIfFromDeferredLibrary(key,
CompileTimeErrorCode.NON_CONSTANT_MAP_KEY_FROM_DEFERRED_LIBRARY);
if (keys.contains(keyResult)) {
invalidKeys.add(key);
} else {
keys.add(keyResult);
}
DartType type = keyResult.type;
if (_implementsEqualsWhenNotAllowed(type)) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode
.CONST_MAP_KEY_EXPRESSION_TYPE_IMPLEMENTS_EQUALS,
key,
[type.displayName]);
}
}
} else {
// Note: we throw the errors away because this isn't actually a const.
AnalysisErrorListener errorListener =
AnalysisErrorListener.NULL_LISTENER;
ErrorReporter subErrorReporter =
new ErrorReporter(errorListener, _errorReporter.source);
DartObjectImpl result = key.accept(new ConstantVisitor(
new ConstantEvaluationEngine(_typeProvider, declaredVariables,
typeSystem: _typeSystem),
subErrorReporter));
if (result != null) {
if (keys.contains(result)) {
invalidKeys.add(key);
} else {
keys.add(result);
}
} else {
reportEqualKeys = false;
}
}
}
if (reportEqualKeys) {
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 || node.isFinal)) {
VariableElementImpl element = node.element as VariableElementImpl;
EvaluationResultImpl result = element.evaluationResult;
if (result == null) {
// Variables marked "const" should have had their values computed by
// ConstantValueComputer. Other variables will only have had their
// values computed if the value was needed (e.g. final variables in a
// class containing const constructors).
assert(!node.isConst);
return null;
}
_reportErrors(result.errors,
CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE);
_reportErrorIfFromDeferredLibrary(
initializer,
CompileTimeErrorCode
.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE_FROM_DEFERRED_LIBRARY);
}
return null;
}
/**
* This verifies that the passed switch statement does not have a case expression with the
* operator '==' overridden.
*
* @param node the switch statement to evaluate
* @param type the common type of all 'case' expressions
* @return `true` if and only if an error code is generated on the passed node
* See [CompileTimeErrorCode.CASE_EXPRESSION_TYPE_IMPLEMENTS_EQUALS].
*/
bool _checkForCaseExpressionTypeImplementsEquals(
SwitchStatement node, DartType type) {
if (!_implementsEqualsWhenNotAllowed(type)) {
return false;
}
// report error
_errorReporter.reportErrorForToken(
CompileTimeErrorCode.CASE_EXPRESSION_TYPE_IMPLEMENTS_EQUALS,
node.switchKeyword,
[type.displayName]);
return true;
}
/**
* @return `true` if given [Type] implements operator <i>==</i>, and it is not
* <i>int</i> or <i>String</i>.
*/
bool _implementsEqualsWhenNotAllowed(DartType type) {
// ignore int or String
if (type == null || type == _intType || type == _typeProvider.stringType) {
return false;
} else if (type == _typeProvider.doubleType) {
return true;
}
// prepare ClassElement
Element element = type.element;
if (element is! ClassElement) {
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,
CompileTimeErrorCode.RECURSIVE_COMPILE_TIME_CONSTANT) ||
identical(
dataErrorCode,
CheckedModeCompileTimeErrorCode
.CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH) ||
identical(
dataErrorCode,
CheckedModeCompileTimeErrorCode
.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH) ||
identical(dataErrorCode,
CheckedModeCompileTimeErrorCode.VARIABLE_TYPE_MISMATCH)) {
_errorReporter.reportError(data);
} else if (errorCode != null) {
_errorReporter.reportError(new AnalysisError(
data.source, data.offset, data.length, errorCode));
}
}
}
/**
* Validate that the given expression is a compile time constant. Return the value of the compile
* time constant, or `null` if the expression is not a compile time constant.
*
* @param expression the expression to be validated
* @param errorCode the error code to be used if the expression is not a compile time constant
* @return the value of the compile time constant
*/
DartObjectImpl _validate(Expression expression, ErrorCode errorCode) {
RecordingErrorListener errorListener = new RecordingErrorListener();
ErrorReporter subErrorReporter =
new ErrorReporter(errorListener, _errorReporter.source);
DartObjectImpl result = expression.accept(new ConstantVisitor(
new ConstantEvaluationEngine(_typeProvider, declaredVariables,
typeSystem: _typeSystem),
subErrorReporter));
_reportErrors(errorListener.errors, errorCode);
return result;
}
/**
* Validate that if the passed arguments are constant expressions.
*
* @param argumentList the argument list to evaluate
*/
void _validateConstantArguments(ArgumentList argumentList) {
for (Expression argument in argumentList.arguments) {
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(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(
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) {
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;
/**
* The type system for this visitor
*/
final TypeSystem _typeSystem;
/**
* Create a new instance of the [DeadCodeVerifier].
*
* @param errorReporter the error reporter
*/
DeadCodeVerifier(this._errorReporter, {TypeSystem typeSystem})
: this._typeSystem =
(typeSystem != null) ? typeSystem : new TypeSystemImpl();
@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) {
if (lhsResult.value.toBoolValue() == true && 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.toBoolValue() == false && 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.toBoolValue() == true) {
// 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.toBoolValue() == true) {
// 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 (_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);
}
_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.toBoolValue() == false) {
// 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(
new DartObjectImpl(null, BoolState.from(true)));
} else {
return new EvaluationResultImpl(
new DartObjectImpl(null, BoolState.from(false)));
}
}
// Don't consider situations where we could evaluate to a constant boolean
// expression with the ConstantVisitor
// else {
// EvaluationResultImpl result = expression.accept(new ConstantVisitor());
// if (result == ValidResult.RESULT_TRUE) {
// return ValidResult.RESULT_TRUE;
// } else if (result == ValidResult.RESULT_FALSE) {
// return ValidResult.RESULT_FALSE;
// }
// return null;
// }
return null;
}
/**
* Return `true` if and only if the passed expression is resolved to a constant variable.
*
* @param expression some conditional expression
* @return `true` if and only if the passed expression is resolved to a constant variable
*/
bool _isDebugConstant(Expression expression) {
Element element = null;
if (expression is Identifier) {
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;
Token property = node.propertyKeyword;
if (property == null) {
if (_enclosingExecutable != null) {
_enclosingExecutable =
_findIdentifier(_enclosingExecutable.functions, functionName);
} else {
_enclosingExecutable =
_findIdentifier(_enclosingUnit.functions, functionName);
}
} else {
if (_enclosingExecutable != null) {
_enclosingExecutable =
_findIdentifier(_enclosingExecutable.functions, functionName);
} else {
PropertyAccessorElement accessor =
_findIdentifier(_enclosingUnit.accessors, functionName);
if ((property as KeywordToken).keyword == 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 {
Token property = node.propertyKeyword;
SimpleIdentifier methodName = node.name;
String nameOfMethod = methodName.name;
if (property == null) {
_enclosingExecutable = _findWithNameAndOffset(
_enclosingClass.methods, nameOfMethod, methodName.offset);
methodName.staticElement = _enclosingExecutable;
} else {
PropertyAccessorElement accessor =
_findIdentifier(_enclosingClass.accessors, methodName);
if ((property as KeywordToken).keyword == 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;
Element element;
if (_enclosingExecutable != null) {
element =
_findIdentifier(_enclosingExecutable.typeParameters, parameterName);
}
if (element == null) {
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.nameOffset == offset && element.displayName == name) {
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 elements defined in a class that need to have
* their function type fixed to take into account type parameters of the
* enclosing class, or `null` if we are not currently processing nodes within
* a class.
*/
List<ExecutableElementImpl> _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);
if (node.exceptionType == null) {
exception.hasImplicitType = true;
}
_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<ExecutableElementImpl>();
//
// 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(element);
interfaceType.typeArguments = typeArguments;
element.type = interfaceType;
element.typeParameters = typeParameters;
_setDoc(element, node);
element.abstract = node.isAbstract;
element.accessors = holder.accessors;
List<ConstructorElement> constructors = holder.constructors;
if (constructors.isEmpty) {
constructors = _createDefaultConstructors(element);
}
element.constructors = constructors;
element.fields = holder.fields;
element.methods = holder.methods;
element.validMixin = _isValidMixin;
// Function types must be initialized after the enclosing element has been
// set, for them to pick up the type parameters.
for (ExecutableElementImpl e in _functionTypesToFix) {
e.type = new FunctionTypeImpl(e);
}
_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();
_visitChildren(holder, node);
SimpleIdentifier className = node.name;
ClassElementImpl element = new ClassElementImpl.forNode(className);
element.abstract = node.abstractKeyword != null;
element.mixinApplication = true;
List<TypeParameterElement> typeParameters = holder.typeParameters;
element.typeParameters = typeParameters;
List<DartType> typeArguments = _createTypeParameterTypes(typeParameters);
InterfaceTypeImpl interfaceType = new InterfaceTypeImpl(element);
interfaceType.typeArguments = typeArguments;
element.type = interfaceType;
_setDoc(element, node);
_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);
_setDoc(element, node);
if (node.externalKeyword != null) {
element.external = true;
}
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;
if (node.type == null) {
element.hasImplicitType = true;
}
_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);
if (normalParameter is SimpleFormalParameter &&
normalParameter.type == null) {
parameter.hasImplicitType = true;
}
_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;
_setDoc(enumElement, node);
InterfaceTypeImpl enumType = new InterfaceTypeImpl(enumElement);
enumElement.type = enumType;
// The equivalent code for enums in the spec shows a single constructor,
// but that constructor is not callable (since it is a compile-time error
// to subclass, mix-in, implement, or explicitly instantiate an enum). So
// we represent this as having no constructors.
enumElement.constructors = ConstructorElement.EMPTY_LIST;
_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);
ParameterElementImpl element = node.element;
element.parameters = holder.parameters;
element.typeParameters = holder.typeParameters;
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, node);
} finally {
_inFunction = wasInFunction;
}
FunctionBody body = expression.body;
Token property = node.propertyKeyword;
if (property == null || _inFunction) {
SimpleIdentifier functionName = node.name;
FunctionElementImpl element =
new FunctionElementImpl.forNode(functionName);
_setDoc(element, node);
if (node.externalKeyword != null) {
element.external = true;
}
element.functions = holder.functions;
element.labels = holder.labels;
element.localVariables = holder.localVariables;
element.parameters = holder.parameters;
element.typeParameters = holder.typeParameters;
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);
}
}
if (node.returnType == null) {
element.hasImplicitReturnType = true;
}
_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);
_setDoc(getter, node);
if (node.externalKeyword != null) {
getter.external = true;
}
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;
if (node.returnType == null) {
getter.hasImplicitReturnType = true;
}
_currentHolder.addAccessor(getter);
expression.element = getter;
propertyNameNode.staticElement = getter;
} else {
PropertyAccessorElementImpl setter =
new PropertyAccessorElementImpl.forNode(propertyNameNode);
_setDoc(setter, node);
if (node.externalKeyword != null) {
setter.external = true;
}
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;
if (node.returnType == null) {
setter.hasImplicitReturnType = 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) {
if (node.parent is FunctionDeclaration) {
// visitFunctionDeclaration has already created the element for the
// declaration. We just need to visit children.
return super.visitFunctionExpression(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.functions = holder.functions;
element.labels = holder.labels;
element.localVariables = holder.localVariables;
element.parameters = holder.parameters;
element.typeParameters = holder.typeParameters;
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);
}
}
if (_functionTypesToFix != null) {
_functionTypesToFix.add(element);
} else {
element.type = new FunctionTypeImpl(element);
}
element.hasImplicitReturnType = true;
_currentHolder.addFunction(element);
node.element = element;
holder.validate();
return null;
}
@override
Object visitFunctionTypeAlias(FunctionTypeAlias node) {
ElementHolder holder = new ElementHolder();
_visitChildren(holder, node);
SimpleIdentifier aliasName = node.name;
List<ParameterElement> parameters = holder.parameters;
List<TypeParameterElement> typeParameters = holder.typeParameters;
FunctionTypeAliasElementImpl element =
new FunctionTypeAliasElementImpl.forNode(aliasName);
_setDoc(element, node);
element.parameters = parameters;
element.typeParameters = typeParameters;
_createTypeParameterTypes(typeParameters);
element.type = new FunctionTypeImpl.forTypedef(element);
_currentHolder.addTypeAlias(element);
aliasName.staticElement = element;
holder.validate();
return null;
}
@override
Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
if (node.parent is! DefaultFormalParameter) {
SimpleIdentifier parameterName = node.identifier;
ParameterElementImpl parameter =
new ParameterElementImpl.forNode(parameterName);
parameter.parameterKind = node.kind;
_setParameterVisibleRange(node, parameter);
_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);
ParameterElementImpl element = node.element;
element.parameters = holder.parameters;
element.typeParameters = holder.typeParameters;
holder.validate();
return null;
}
@override
Object visitLabeledStatement(LabeledStatement node) {
bool onSwitchStatement = node.statement is SwitchStatement;
for (Label label in node.labels) {
SimpleIdentifier labelName = label.label;
LabelElementImpl element =
new LabelElementImpl(labelName, onSwitchStatement, false);
_currentHolder.addLabel(element);
labelName.staticElement = element;
}
return super.visitLabeledStatement(node);
}
@override
Object visitMethodDeclaration(MethodDeclaration node) {
try {
ElementHolder holder = new ElementHolder();
bool wasInFunction = _inFunction;
_inFunction = true;
try {
_visitChildren(holder, node);
} finally {
_inFunction = wasInFunction;
}
bool isStatic = node.isStatic;
Token property = node.propertyKeyword;
FunctionBody body = node.body;
if (property == null) {
SimpleIdentifier methodName = node.name;
String nameOfMethod = methodName.name;
if (nameOfMethod == TokenType.MINUS.lexeme &&
node.parameters.parameters.length == 0) {
nameOfMethod = "unary-";
}
MethodElementImpl element =
new MethodElementImpl(nameOfMethod, methodName.offset);
_setDoc(element, node);
element.abstract = node.isAbstract;
if (node.externalKeyword != null) {
element.external = true;
}
element.functions = holder.functions;
element.labels = holder.labels;
element.localVariables = holder.localVariables;
element.parameters = holder.parameters;
element.static = isStatic;
element.typeParameters = holder.typeParameters;
if (body.isAsynchronous) {
element.asynchronous = true;
}
if (body.isGenerator) {
element.generator = true;
}
if (node.returnType == null) {
element.hasImplicitReturnType = true;
}
_currentHolder.addMethod(element);
methodName.staticElement = element;
} else {
SimpleIdentifier propertyNameNode = node.name;
String propertyName = propertyNameNode.name;
FieldElementImpl field =
_currentHolder.getField(propertyName) as FieldElementImpl;
if (field == null) {
field = new FieldElementImpl(node.name.name, -1);
field.final2 = true;
field.static = isStatic;
field.synthetic = true;
_currentHolder.addField(field);
}
if (node.isGetter) {
PropertyAccessorElementImpl getter =
new PropertyAccessorElementImpl.forNode(propertyNameNode);
_setDoc(getter, node);
if (node.externalKeyword != null) {
getter.external = true;
}
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 = field;
getter.abstract = node.isAbstract;
getter.getter = true;
getter.static = isStatic;
field.getter = getter;
if (node.returnType == null) {
getter.hasImplicitReturnType = true;
}
_currentHolder.addAccessor(getter);
propertyNameNode.staticElement = getter;
} else {
PropertyAccessorElementImpl setter =
new PropertyAccessorElementImpl.forNode(propertyNameNode);
_setDoc(setter, node);
if (node.externalKeyword != null) {
setter.external = true;
}
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 = field;
setter.abstract = node.isAbstract;
setter.setter = true;
setter.static = isStatic;
if (node.returnType == null) {
setter.hasImplicitReturnType = true;
}
field.setter = setter;
field.final2 = false;
_currentHolder.addAccessor(setter);
propertyNameNode.staticElement = setter;
}
}
holder.validate();
} catch (exception, stackTrace) {
if (node.name.staticElement == null) {
ClassDeclaration classNode =
node.getAncestor((node) => node is ClassDeclaration);
StringBuffer buffer = new StringBuffer();
buffer.write("The element for the method ");
buffer.write(node.name);
buffer.write(" in ");
buffer.write(classNode.name);
buffer.write(" was not set while trying to build the element model.");
AnalysisEngine.instance.logger.logError(
buffer.toString(), new CaughtException(exception, stackTrace));
} else {
String message =
"Exception caught in ElementBuilder.visitMethodDeclaration()";
AnalysisEngine.instance.logger
.logError(message, new CaughtException(exception, stackTrace));
}
} finally {
if (node.name.staticElement == null) {
ClassDeclaration classNode =
node.getAncestor((node) => node is ClassDeclaration);
StringBuffer buffer = new StringBuffer();
buffer.write("The element for the method ");
buffer.write(node.name);
buffer.write(" in ");
buffer.write(classNode.name);
buffer.write(" was not set while trying to resolve types.");
AnalysisEngine.instance.logger.logError(
buffer.toString(),
new CaughtException(
new AnalysisException(buffer.toString()), null));
}
}
return null;
}
@override
Object visitSimpleFormalParameter(SimpleFormalParameter node) {
if (node.parent is! DefaultFormalParameter) {
SimpleIdentifier parameterName = node.identifier;
ParameterElementImpl parameter =
new ParameterElementImpl.forNode(parameterName);
parameter.const3 = node.isConst;
parameter.final2 = node.isFinal;
parameter.parameterKind = node.kind;
_setParameterVisibleRange(node, parameter);
if (node.type == null) {
parameter.hasImplicitType = true;
}
_currentHolder.addParameter(parameter);
parameterName.staticElement = parameter;
}
return super.visitSimpleFormalParameter(node);
}
@override
Object visitSuperExpression(SuperExpression node) {
_isValidMixin = false;
return super.visitSuperExpression(node);
}
@override
Object visitSwitchCase(SwitchCase node) {
for (Label label in node.labels) {
SimpleIdentifier labelName = label.label;
LabelElementImpl element = new LabelElementImpl(labelName, false, true);
_currentHolder.addLabel(element);
labelName.staticElement = element;
}
return super.visitSwitchCase(node);
}
@override
Object visitSwitchDefault(SwitchDefault node) {
for (Label label in node.labels) {
SimpleIdentifier labelName = label.label;
LabelElementImpl element = new LabelElementImpl(labelName, false, true);
_currentHolder.addLabel(element);
labelName.staticElement = element;
}
return super.visitSwitchDefault(node);
}
@override
Object visitTypeParameter(TypeParameter node) {
SimpleIdentifier parameterName = node.name;
TypeParameterElementImpl typeParameter =
new TypeParameterElementImpl.forNode(parameterName);
TypeParameterTypeImpl typeParameterType =
new TypeParameterTypeImpl(typeParameter);
typeParameter.type = typeParameterType;
_currentHolder.addTypeParameter(typeParameter);
parameterName.staticElement = typeParameter;
return super.visitTypeParameter(node);
}
@override
Object visitVariableDeclaration(VariableDeclaration node) {
bool isConst = node.isConst;
bool isFinal = node.isFinal;
bool hasInitializer = node.initializer != null;
VariableElementImpl element;
if (_inFieldContext) {
SimpleIdentifier fieldName = node.name;
FieldElementImpl field;
if ((isConst || isFinal) && hasInitializer) {
field = new ConstFieldElementImpl.forNode(fieldName);
} else {
field = new FieldElementImpl.forNode(fieldName);
}
element = field;
if (node.parent.parent is FieldDeclaration) {
_setDoc(element, node.parent.parent);
}
if ((node.parent as VariableDeclarationList).type == null) {
field.hasImplicitType = true;
}
_currentHolder.addField(field);
fieldName.staticElement = field;
} else if (_inFunction) {
SimpleIdentifier variableName = node.name;
LocalVariableElementImpl variable;
if (isConst && hasInitializer) {
variable = new ConstLocalVariableElementImpl.forNode(variableName);
} else {
variable = new LocalVariableElementImpl.forNode(variableName);
}
element = variable;
Block enclosingBlock = node.getAncestor((node) => node is Block);
// TODO(brianwilkerson) This isn't right for variables declared in a for
// loop.
variable.setVisibleRange(enclosingBlock.offset, enclosingBlock.length);
if ((node.parent as VariableDeclarationList).type == null) {
variable.hasImplicitType = true;
}
_currentHolder.addLocalVariable(variable);
variableName.staticElement = element;
} else {
SimpleIdentifier variableName = node.name;
TopLevelVariableElementImpl variable;
if (isConst && hasInitializer) {
variable = new ConstTopLevelVariableElementImpl(variableName);
} else {
variable = new TopLevelVariableElementImpl.forNode(variableName);
}
element = variable;
if (node.parent.parent is TopLevelVariableDeclaration) {
_setDoc(element, node.parent.parent);
}
if ((node.parent as VariableDeclarationList).type == null) {
variable.hasImplicitType = true;
}
_currentHolder.addTopLevelVariable(variable);
variableName.staticElement = element;
}
element.const3 = isConst;
element.final2 = isFinal;
if (hasInitializer) {
ElementHolder holder = new ElementHolder();
bool wasInFieldContext = _inFieldContext;
_inFieldContext = false;
try {
_visit(holder, node.initializer);
} finally {
_inFieldContext = wasInFieldContext;
}
FunctionElementImpl initializer =
new FunctionElementImpl.forOffset(node.initializer.beginToken.offset);
initializer.functions = holder.functions;
initializer.labels = holder.labels;
initializer.localVariables = holder.localVariables;
initializer.synthetic = true;
element.initializer = initializer;
holder.validate();
}
if (element is PropertyInducingElementImpl) {
if (_inFieldContext) {
(element as FieldElementImpl).static =
(node.parent.parent as FieldDeclaration).isStatic;
}
PropertyAccessorElementImpl getter =
new PropertyAccessorElementImpl.forVariable(element);
getter.getter = true;
if (element.hasImplicitType) {
getter.hasImplicitReturnType = true;
}
_currentHolder.addAccessor(getter);
element.getter = getter;
if (!isConst && !isFinal) {
PropertyAccessorElementImpl setter =
new PropertyAccessorElementImpl.forVariable(element);
setter.setter = true;
ParameterElementImpl parameter =
new ParameterElementImpl("_${element.name}", element.nameOffset);
parameter.synthetic = true;
parameter.parameterKind = ParameterKind.REQUIRED;
setter.parameters = <ParameterElement>[parameter];
_currentHolder.addAccessor(setter);
element.setter = setter;
}
}
return null;
}
/**
* Build the table mapping field names to field elements for the fields defined in the current
* class.
*
* @param fields the field elements defined in the current class
*/
void _buildFieldMap(List<FieldElement> fields) {
_fieldMap = new HashMap<String, FieldElement>();
int count = fields.length;
for (int i = 0; i < count; i++) {
FieldElement field = fields[i];
_fieldMap[field.name] = field;
}
}
/**
* Creates the [ConstructorElement]s array with the single default constructor element.
*
* @param interfaceType the interface type for which to create a default constructor
* @return the [ConstructorElement]s array with the single default constructor element
*/
List<ConstructorElement> _createDefaultConstructors(
ClassElementImpl definingClass) {
ConstructorElementImpl constructor =
new ConstructorElementImpl.forNode(null);
constructor.synthetic = true;
constructor.returnType = definingClass.type;
constructor.enclosingElement = definingClass;
constructor.type = new FunctionTypeImpl(constructor);
return <ConstructorElement>[constructor];
}
/**
* Create the types associated with the given type parameters, setting the type of each type
* parameter, and return an array of types corresponding to the given parameters.
*
* @param typeParameters the type parameters for which types are to be created
* @return an array of types corresponding to the given parameters
*/
List<DartType> _createTypeParameterTypes(
List<TypeParameterElement> typeParameters) {
int typeParameterCount = typeParameters.length;
List<DartType> typeArguments = new List<DartType>(typeParameterCount);
for (int i = 0; i < typeParameterCount; i++) {
TypeParameterElementImpl typeParameter =
typeParameters[i] as TypeParameterElementImpl;
TypeParameterTypeImpl typeParameterType =
new TypeParameterTypeImpl(typeParameter);
typeParameter.type = typeParameterType;
typeArguments[i] = typeParameterType;
}
return typeArguments;
}
/**
* Return the body of the function that contains the given parameter, or `null` if no
* function body could be found.
*
* @param node the parameter contained in the function whose body is to be returned
* @return the body of the function that contains the given parameter
*/
FunctionBody _getFunctionBody(FormalParameter node) {
AstNode parent = node.parent;
while (parent != null) {
if (parent is ConstructorDeclaration) {
return parent.body;
} else if (parent is FunctionExpression) {
return parent.body;
} else if (parent is MethodDeclaration) {
return parent.body;
}
parent = parent.parent;
}
return null;
}
/**
* If the given [node] has a documentation comment, remember its content
* and range into the given [element].
*/
void _setDoc(ElementImpl element, AnnotatedNode node) {
Comment comment = node.documentationComment;
if (comment != null && comment.isDocumentation) {
element.documentationComment =
comment.tokens.map((Token t) => t.lexeme).join('\n');
element.setDocRange(comment.offset, comment.length);
}
}
/**
* Sets the visible source range for formal parameter.
*/
void _setParameterVisibleRange(
FormalParameter node, ParameterElementImpl element) {
FunctionBody body = _getFunctionBody(node);
if (body != null) {
element.setVisibleRange(body.offset, body.length);
}
}
/**
* Make the given holder be the current holder while visiting the given node.
*
* @param holder the holder that will gather elements that are built while visiting the children
* @param node the node to be visited
*/
void _visit(ElementHolder holder, AstNode node) {
if (node != null) {
ElementHolder previousHolder = _currentHolder;
_currentHolder = holder;
try {
node.accept(this);
} finally {
_currentHolder = previousHolder;
}
}
}
/**
* Make the given holder be the current holder while visiting the children of the given node.
*
* @param holder the holder that will gather elements that are built while visiting the children
* @param node the node whose children are to be visited
*/
void _visitChildren(ElementHolder holder, AstNode node) {
if (node != null) {
ElementHolder previousHolder = _currentHolder;
_currentHolder = holder;
try {
node.visitChildren(this);
} finally {
_currentHolder = previousHolder;
}
}
}
}
/**
* Instances of the class `ElementHolder` hold on to elements created while traversing an AST
* structure so that they can be accessed when creating their enclosing element.
*/
class ElementHolder {
List<PropertyAccessorElement> _accessors;
List<ConstructorElement> _constructors;
List<ClassElement> _enums;
List<FieldElement> _fields;
List<FunctionElement> _functions;
List<LabelElement> _labels;
List<LocalVariableElement> _localVariables;
List<MethodElement> _methods;
List<ParameterElement> _parameters;
List<TopLevelVariableElement> _topLevelVariables;
List<ClassElement> _types;
List<FunctionTypeAliasElement> _typeAliases;
List<TypeParameterElement> _typeParameters;
List<PropertyAccessorElement> get accessors {
if (_accessors == null) {
return PropertyAccessorElement.EMPTY_LIST;
}
List<PropertyAccessorElement> result = _accessors;
_accessors = null;
return result;
}
List<ConstructorElement> get constructors {
if (_constructors == null) {
return ConstructorElement.EMPTY_LIST;
}
List<ConstructorElement> result = _constructors;
_constructors = null;
return result;
}
List<ClassElement> get enums {
if (_enums == null) {
return ClassElement.EMPTY_LIST;
}
List<ClassElement> result = _enums;
_enums = null;
return result;
}
List<FieldElement> get fields {
if (_fields == null) {
return FieldElement.EMPTY_LIST;
}
List<FieldElement> result = _fields;
_fields = null;
return result;
}
List<FieldElement> get fieldsWithoutFlushing {
if (_fields == null) {
return FieldElement.EMPTY_LIST;
}
List<FieldElement> result = _fields;
return result;
}
List<FunctionElement> get functions {
if (_functions == null) {
return FunctionElement.EMPTY_LIST;
}
List<FunctionElement> result = _functions;
_functions = null;
return result;
}
List<LabelElement> get labels {
if (_labels == null) {
return LabelElement.EMPTY_LIST;
}
List<LabelElement> result = _labels;
_labels = null;
return result;
}
List<LocalVariableElement> get localVariables {
if (_localVariables == null) {
return LocalVariableElement.EMPTY_LIST;
}
List<LocalVariableElement> result = _localVariables;
_localVariables = null;
return result;
}
List<MethodElement> get methods {
if (_methods == null) {
return MethodElement.EMPTY_LIST;
}
List<MethodElement> result = _methods;
_methods = null;
return result;
}
List<ParameterElement> get parameters {
if (_parameters == null) {
return ParameterElement.EMPTY_LIST;
}
List<ParameterElement> result = _parameters;
_parameters = null;
return result;
}
List<TopLevelVariableElement> get topLevelVariables {
if (_topLevelVariables == null) {
return TopLevelVariableElement.EMPTY_LIST;
}
List<TopLevelVariableElement> result = _topLevelVariables;
_topLevelVariables = null;
return result;
}
List<FunctionTypeAliasElement> get typeAliases {
if (_typeAliases == null) {
return FunctionTypeAliasElement.EMPTY_LIST;
}
List<FunctionTypeAliasElement> result = _typeAliases;
_typeAliases = null;
return result;
}
List<TypeParameterElement> get typeParameters {
if (_typeParameters == null) {
return TypeParameterElement.EMPTY_LIST;
}
List<TypeParameterElement> result = _typeParameters;
_typeParameters = null;
return result;
}
List<ClassElement> get types {
if (_types == null) {
return ClassElement.EMPTY_LIST;
}
List<ClassElement> result = _types;
_types = null;
return result;
}
void addAccessor(PropertyAccessorElement element) {
if (_accessors == null) {
_accessors = new List<PropertyAccessorElement>();
}
_accessors.add(element);
}
void addConstructor(ConstructorElement element) {
if (_constructors == null) {
_constructors = new List<ConstructorElement>();
}
_constructors.add(element);
}
void addEnum(ClassElement element) {
if (_enums == null) {
_enums = new List<ClassElement>();
}
_enums.add(element);
}
void addField(FieldElement element) {
if (_fields == null) {
_fields = new List<FieldElement>();
}
_fields.add(element);
}
void addFunction(FunctionElement element) {
if (_functions == null) {
_functions = new List<FunctionElement>();
}
_functions.add(element);
}
void addLabel(LabelElement element) {
if (_labels == null) {
_labels = new List<LabelElement>();
}
_labels.add(element);
}
void addLocalVariable(LocalVariableElement element) {
if (_localVariables == null) {
_localVariables = new List<LocalVariableElement>();
}
_localVariables.add(element);
}
void addMethod(MethodElement element) {
if (_methods == null) {
_methods = new List<MethodElement>();
}
_methods.add(element);
}
void addParameter(ParameterElement element) {
if (_parameters == null) {
_parameters = new List<ParameterElement>();
}
_parameters.add(element);
}
void addTopLevelVariable(TopLevelVariableElement element) {
if (_topLevelVariables == null) {
_topLevelVariables = new List<TopLevelVariableElement>();
}
_topLevelVariables.add(element);
}
void addType(ClassElement element) {
if (_types == null) {
_types = new List<ClassElement>();
}
_types.add(element);
}
void addTypeAlias(FunctionTypeAliasElement element) {
if (_typeAliases == null) {
_typeAliases = new List<FunctionTypeAliasElement>();
}
_typeAliases.add(element);
}
void addTypeParameter(TypeParameterElement element) {
if (_typeParameters == null) {
_typeParameters = new List<TypeParameterElement>();
}
_typeParameters.add(element);
}
FieldElement getField(String fieldName) {
if (_fields == null) {
return null;
}
for (FieldElement field in _fields) {
if (field.name == fieldName) {
return field;
}
}
return null;
}
TopLevelVariableElement getTopLevelVariable(String variableName) {
if (_topLevelVariables == null) {
return null;
}
for (TopLevelVariableElement variable in _topLevelVariables) {
if (variable.name == variableName) {
return variable;
}
}
return null;
}
void validate() {
StringBuffer buffer = new StringBuffer();
if (_accessors != null) {
buffer.write(_accessors.length);
buffer.write(" accessors");
}
if (_constructors != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_constructors.length);
buffer.write(" constructors");
}
if (_fields != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_fields.length);
buffer.write(" fields");
}
if (_functions != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_functions.length);
buffer.write(" functions");
}
if (_labels != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_labels.length);
buffer.write(" labels");
}
if (_localVariables != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_localVariables.length);
buffer.write(" local variables");
}
if (_methods != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_methods.length);
buffer.write(" methods");
}
if (_parameters != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_parameters.length);
buffer.write(" parameters");
}
if (_topLevelVariables != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_topLevelVariables.length);
buffer.write(" top-level variables");
}
if (_types != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_types.length);
buffer.write(" types");
}
if (_typeAliases != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_typeAliases.length);
buffer.write(" type aliases");
}
if (_typeParameters != null) {
if (buffer.length > 0) {
buffer.write("; ");
}
buffer.write(_typeParameters.length);
buffer.write(" type parameters");
}
if (buffer.length > 0) {
AnalysisEngine.instance.logger
.logError("Failed to capture elements: $buffer");
}
}
}
/**
* Instances of the class `EnclosedScope` implement a scope that is lexically enclosed in
* another scope.
*/
class EnclosedScope extends Scope {
/**
* The scope in which this scope is lexically enclosed.
*/
final Scope enclosingScope;
/**
* A table mapping names that will be defined in this scope, but right now are not initialized.
* According to the scoping rules these names are hidden, even if they were defined in an outer
* scope.
*/
HashMap<String, Element> _hiddenElements = new HashMap<String, Element>();
/**
* A flag indicating whether there are any names defined in this scope.
*/
bool _hasHiddenName = false;
/**
* Initialize a newly created scope enclosed within another scope.
*
* @param enclosingScope the scope in which this scope is lexically enclosed
*/
EnclosedScope(this.enclosingScope);
@override
AnalysisErrorListener get errorListener => enclosingScope.errorListener;
/**
* Record that given element is declared in this scope, but hasn't been initialized yet, so it is
* error to use. If there is already an element with the given name defined in an outer scope,
* then it will become unavailable.
*
* @param element the element declared, but not initialized in this scope
*/
void hide(Element element) {
if (element != null) {
String name = element.name;
if (name != null && !name.isEmpty) {
_hiddenElements[name] = element;
_hasHiddenName = true;
}
}
}
@override
Element internalLookup(
Identifier identifier, String name, LibraryElement referencingLibrary) {
Element element = localLookup(name, referencingLibrary);
if (element != null) {
return element;
}
// May be there is a hidden Element.
if (_hasHiddenName) {
Element hiddenElement = _hiddenElements[name];
if (hiddenElement != null) {
errorListener.onError(new AnalysisError(
getSource(identifier),
identifier.offset,
identifier.length,
CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, []));
return hiddenElement;
}
}
// Check enclosing scope.
return enclosingScope.internalLookup(identifier, name, referencingLibrary);
}
}
/**
* Instances of the class `EnumMemberBuilder` build the members in enum declarations.
*/
class EnumMemberBuilder extends RecursiveAstVisitor<Object> {
/**
* The type provider used to access the types needed to build an element model for enum
* declarations.
*/
final TypeProvider _typeProvider;
/**
* Initialize a newly created enum member builder.
*
* @param typeProvider the type provider used to access the types needed to build an element model
* for enum declarations
*/
EnumMemberBuilder(this._typeProvider);
@override
Object visitEnumDeclaration(EnumDeclaration node) {
//
// Finish building the enum.
//
ClassElementImpl enumElement = node.name.staticElement as ClassElementImpl;
InterfaceType enumType = enumElement.type;
enumElement.supertype = _typeProvider.objectType;
//
// Populate the fields.
//
List<FieldElement> fields = new List<FieldElement>();
List<PropertyAccessorElement> getters = new List<PropertyAccessorElement>();
InterfaceType intType = _typeProvider.intType;
String indexFieldName = "index";
FieldElementImpl indexField = new FieldElementImpl(indexFieldName, -1);
indexField.final2 = true;
indexField.synthetic = true;
indexField.type = intType;
fields.add(indexField);
getters.add(_createGetter(indexField));
ConstFieldElementImpl valuesField = new ConstFieldElementImpl("values", -1);
valuesField.static = true;
valuesField.const3 = true;
valuesField.synthetic = true;
valuesField.type = _typeProvider.listType.substitute4(<DartType>[enumType]);
fields.add(valuesField);
getters.add(_createGetter(valuesField));
//
// Build the enum constants.
//
NodeList<EnumConstantDeclaration> constants = node.constants;
List<DartObjectImpl> constantValues = new List<DartObjectImpl>();
int constantCount = constants.length;
for (int i = 0; i < constantCount; i++) {
SimpleIdentifier constantName = constants[i].name;
FieldElementImpl constantField =
new ConstFieldElementImpl.forNode(constantName);
constantField.static = true;
constantField.const3 = true;
constantField.type = enumType;
//
// Create a value for the constant.
//
HashMap<String, DartObjectImpl> fieldMap =
new HashMap<String, DartObjectImpl>();
fieldMap[indexFieldName] = new DartObjectImpl(intType, new IntState(i));
DartObjectImpl value =
new DartObjectImpl(enumType, new GenericState(fieldMap));
constantValues.add(value);
constantField.evaluationResult = new EvaluationResultImpl(value);
fields.add(constantField);
getters.add(_createGetter(constantField));
constantName.staticElement = constantField;
}
//
// Build the value of the 'values' field.
//
valuesField.evaluationResult = new EvaluationResultImpl(
new DartObjectImpl(valuesField.type, new ListState(constantValues)));
//
// Finish building the enum.
//
enumElement.fields = fields;
enumElement.accessors = getters;
// Client code isn't allowed to invoke the constructor, so we do not model
// it.
return super.visitEnumDeclaration(node);
}
/**
* Create a getter that corresponds to the given field.
*
* @param field the field for which a getter is to be created
* @return the getter that was created
*/
PropertyAccessorElement _createGetter(FieldElementImpl field) {
PropertyAccessorElementImpl getter =
new PropertyAccessorElementImpl.forVariable(field);
getter.getter = true;
getter.returnType = field.type;
getter.type = new FunctionTypeImpl(getter);
field.getter = getter;
return getter;
}
}
/**
* Instances of the class `ExitDetector` determine whether the visited AST node is guaranteed
* to terminate by executing a `return` statement, `throw` expression, `rethrow`
* expression, or simple infinite loop such as `while(true)`.
*/
class ExitDetector extends GeneralizingAstVisitor<bool> {
/**
* Set to `true` when a `break` is encountered, and reset to `false` when a
* `do`, `while`, `for` or `switch` block is entered.
*/
bool _enclosingBlockContainsBreak = false;
@override
bool visitArgumentList(ArgumentList node) =>
_visitExpressions(node.arguments);
@override
bool visitAsExpression(AsExpression node) => _nodeExits(node.expression);
@override
bool visitAssertStatement(AssertStatement node) => false;
@override
bool visitAssignmentExpression(AssignmentExpression node) {
Expression leftHandSide = node.leftHandSide;
if (_nodeExits(leftHandSide)) {
return true;
}
if (node.operator.type == TokenType.QUESTION_QUESTION_EQ) {
return false;
}
if (leftHandSide is PropertyAccess &&
leftHandSide.operator.type == TokenType.QUESTION_PERIOD) {
return false;
}
return _nodeExits(node.rightHandSide);
}
@override
bool visitAwaitExpression(AwaitExpression node) =>
_nodeExits(node.expression);
@override
bool visitBinaryExpression(BinaryExpression node) {
Expression lhsExpression = node.leftOperand;
Expression rhsExpression = node.rightOperand;
TokenType operatorType = node.operator.type;
// If the operator is ||, then only consider the RHS of the binary
// expression if the left hand side is the false literal.
// TODO(jwren) Do we want to take constant expressions into account,
// evaluate if(false) {} differently than if(<condition>), when <condition>
// evaluates to a constant false value?
if (operatorType == TokenType.BAR_BAR) {
if (lhsExpression is BooleanLiteral) {
BooleanLiteral booleanLiteral = lhsExpression;
if (!booleanLiteral.value) {
return _nodeExits(rhsExpression);
}
}
return _nodeExits(lhsExpression);
}
// If the operator is &&, then only consider the RHS of the binary
// expression if the left hand side is the true literal.
if (operatorType == TokenType.AMPERSAND_AMPERSAND) {
if (lhsExpression is BooleanLiteral) {
BooleanLiteral booleanLiteral = lhsExpression;
if (booleanLiteral.value) {
return _nodeExits(rhsExpression);
}
}
return _nodeExits(lhsExpression);
}
// If the operator is ??, then don't consider the RHS of the binary
// expression.
if (operatorType == TokenType.QUESTION_QUESTION) {
return _nodeExits(lhsExpression);
}
return _nodeExits(lhsExpression) || _nodeExits(rhsExpression);
}
@override
bool visitBlock(Block node) => _visitStatements(node.statements);
@override
bool visitBlockFunctionBody(BlockFunctionBody node) => _nodeExits(node.block);
@override
bool visitBreakStatement(BreakStatement node) {
_enclosingBlockContainsBreak = true;
return false;
}
@override
bool visitCascadeExpression(CascadeExpression node) =>
_nodeExits(node.target) || _visitExpressions(node.cascadeSections);
@override
bool visitConditionalExpression(ConditionalExpression node) {
Expression conditionExpression = node.condition;
Expression thenStatement = node.thenExpression;
Expression elseStatement = node.elseExpression;
// TODO(jwren) Do we want to take constant expressions into account,
// evaluate if(false) {} differently than if(<condition>), when <condition>
// evaluates to a constant false value?
if (_nodeExits(conditionExpression)) {
return true;
}
if (thenStatement == null || elseStatement == null) {
return false;
}
return thenStatement.accept(this) && elseStatement.accept(this);
}
@override
bool visitContinueStatement(ContinueStatement node) => false;
@override
bool visitDoStatement(DoStatement node) {
bool outerBreakValue = _enclosingBlockContainsBreak;
_enclosingBlockContainsBreak = false;
try {
Expression conditionExpression = node.condition;
if (_nodeExits(conditionExpression)) {
return true;
}
// TODO(jwren) Do we want to take all constant expressions into account?
if (conditionExpression is BooleanLiteral) {
BooleanLiteral booleanLiteral = conditionExpression;
// If do {} while (true), and the body doesn't return or the body
// doesn't have a break, then return true.
bool blockReturns = _nodeExits(node.body);
if (booleanLiteral.value &&
(blockReturns || !_enclosingBlockContainsBreak)) {
return true;
}
}
return false;
} finally {
_enclosingBlockContainsBreak = outerBreakValue;
}
}
@override
bool visitEmptyStatement(EmptyStatement node) => false;
@override
bool visitExpressionStatement(ExpressionStatement node) =>
_nodeExits(node.expression);
@override
bool visitForEachStatement(ForEachStatement node) {
bool outerBreakValue = _enclosingBlockContainsBreak;
_enclosingBlockContainsBreak = false;
try {
return _nodeExits(node.iterable);
} finally {
_enclosingBlockContainsBreak = outerBreakValue;
}
}
@override
bool visitForStatement(ForStatement node) {
bool outerBreakValue = _enclosingBlockContainsBreak;
_enclosingBlockContainsBreak = false;
try {
if (node.variables != null &&
_visitVariableDeclarations(node.variables.variables)) {
return true;
}
if (node.initialization != null && _nodeExits(node.initialization)) {
return true;
}
Expression conditionExpression = node.condition;
if (conditionExpression != null && _nodeExits(conditionExpression)) {
return true;
}
if (_visitExpressions(node.updaters)) {
return true;
}
// TODO(jwren) Do we want to take all constant expressions into account?
// If for(; true; ) (or for(;;)), and the body doesn't return or the body
// doesn't have a break, then return true.
bool implicitOrExplictTrue = conditionExpression == null ||
(conditionExpression is BooleanLiteral && conditionExpression.value);
if (implicitOrExplictTrue) {
bool blockReturns = _nodeExits(node.body);
if (blockReturns || !_enclosingBlockContainsBreak) {
return true;
}
}
return false;
} finally {
_enclosingBlockContainsBreak = outerBreakValue;
}
}
@override
bool visitFunctionDeclarationStatement(FunctionDeclarationStatement node) =>
false;
@override
bool visitFunctionExpression(FunctionExpression node) => false;
@override
bool visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
if (_nodeExits(node.function)) {
return true;
}
return node.argumentList.accept(this);
}
@override
bool visitIdentifier(Identifier node) => false;
@override
bool visitIfStatement(IfStatement node) {
Expression conditionExpression = node.condition;
Statement thenStatement = node.thenStatement;
Statement elseStatement = node.elseStatement;
if (_nodeExits(conditionExpression)) {
return true;
}
// TODO(jwren) Do we want to take all constant expressions into account?
if (conditionExpression is BooleanLiteral) {
BooleanLiteral booleanLiteral = conditionExpression;
if (booleanLiteral.value) {
// if(true) ...
return _nodeExits(thenStatement);
} else if (elseStatement != null) {
// if (false) ...
return _nodeExits(elseStatement);
}
}
if (thenStatement == null || elseStatement == null) {
return false;
}
return _nodeExits(thenStatement) && _nodeExits(elseStatement);
}
@override
bool visitIndexExpression(IndexExpression node) {
Expression target = node.realTarget;
if (_nodeExits(target)) {
return true;
}
if (_nodeExits(node.index)) {
return true;
}
return false;
}
@override
bool visitInstanceCreationExpression(InstanceCreationExpression node) =>
_nodeExits(node.argumentList);
@override
bool visitIsExpression(IsExpression node) => node.expression.accept(this);
@override
bool visitLabel(Label node) => false;
@override
bool visitLabeledStatement(LabeledStatement node) =>
node.statement.accept(this);
@override
bool visitLiteral(Literal node) => false;
@override
bool visitMethodInvocation(MethodInvocation node) {
Expression target = node.realTarget;
if (target != null) {
if (target.accept(this)) {
return true;
}
if (node.operator.type == TokenType.QUESTION_PERIOD) {
return false;
}
}
return _nodeExits(node.argumentList);
}
@override
bool visitNamedExpression(NamedExpression node) =>
node.expression.accept(this);
@override
bool visitParenthesizedExpression(ParenthesizedExpression node) =>
node.expression.accept(this);
@override
bool visitPostfixExpression(PostfixExpression node) => false;
@override
bool visitPrefixExpression(PrefixExpression node) => false;
@override
bool visitPropertyAccess(PropertyAccess node) {
Expression target = node.realTarget;
if (target != null && target.accept(this)) {
return true;
}
return false;
}
@override
bool visitRethrowExpression(RethrowExpression node) => true;
@override
bool visitReturnStatement(ReturnStatement node) => true;
@override
bool visitSuperExpression(SuperExpression node) => false;
@override
bool visitSwitchCase(SwitchCase node) => _visitStatements(node.statements);
@override
bool visitSwitchDefault(SwitchDefault node) =>
_visitStatements(node.statements);
@override
bool visitSwitchStatement(SwitchStatement node) {
bool outerBreakValue = _enclosingBlockContainsBreak;
_enclosingBlockContainsBreak = false;
try {
bool hasDefault = false;
List<SwitchMember> members = node.members;
for (int i = 0; i < members.length; i++) {
SwitchMember switchMember = members[i];
if (switchMember is SwitchDefault) {
hasDefault = true;
// If this is the last member and there are no statements, return
// false
if (switchMember.statements.isEmpty && i + 1 == members.length) {
return false;
}
}
// For switch members with no statements, don't visit the children,
// otherwise, return false if no return is found in the children
// statements.
if (!switchMember.statements.isEmpty && !switchMember.accept(this)) {
return false;
}
}
// All of the members exit, determine whether there are possible cases
// that are not caught by the members.
DartType type = node.expression == null ? null : node.expression.bestType;
if (type is InterfaceType) {
ClassElement element = type.element;
if (element != null && element.isEnum) {
// If some of the enum values are not covered, then a warning will
// have already been generated, so there's no point in generating a
// hint.
return true;
}
}
return hasDefault;
} finally {
_enclosingBlockContainsBreak = outerBreakValue;
}
}
@override
bool visitThisExpression(ThisExpression node) => false;
@override
bool visitThrowExpression(ThrowExpression node) => true;
@override
bool visitTryStatement(TryStatement node) {
if (_nodeExits(node.body)) {
return true;
}
Block finallyBlock = node.finallyBlock;
if (_nodeExits(finallyBlock)) {
return true;
}
return false;
}
@override
bool visitTypeName(TypeName node) => false;
@override
bool visitVariableDeclaration(VariableDeclaration node) {
Expression initializer = node.initializer;
if (initializer != null) {
return initializer.accept(this);
}
return false;
}
@override
bool visitVariableDeclarationList(VariableDeclarationList node) =>
_visitVariableDeclarations(node.variables);
@override
bool visitVariableDeclarationStatement(VariableDeclarationStatement node) {
NodeList<VariableDeclaration> variables = node.variables.variables;
for (int i = 0; i < variables.length; i++) {
if (variables[i].accept(this)) {
return true;
}
}
return false;
}
@override
bool visitWhileStatement(WhileStatement node) {
bool outerBreakValue = _enclosingBlockContainsBreak;
_enclosingBlockContainsBreak = false;
try {
Expression conditionExpression = node.condition;
if (conditionExpression.accept(this)) {
return true;
}
// TODO(jwren) Do we want to take all constant expressions into account?
if (conditionExpression is BooleanLiteral) {
BooleanLiteral booleanLiteral = conditionExpression;
// If while(true), and the body doesn't return or the body doesn't have
// a break, then return true.
bool blockReturns = node.body.accept(this);
if (booleanLiteral.value &&
(blockReturns || !_enclosingBlockContainsBreak)) {
return true;
}
}
return false;
} finally {
_enclosingBlockContainsBreak = outerBreakValue;
}
}
/**
* Return `true` if the given node exits.
*
* @param node the node being tested
* @return `true` if the given node exits
*/
bool _nodeExits(AstNode node) {
if (node == null) {
return false;
}
return node.accept(this);
}
bool _visitExpressions(NodeList<Expression> expressions) {
for (int i = expressions.length - 1; i >= 0; i--) {
if (expressions[i].accept(this)) {
return true;
}
}
return false;
}
bool _visitStatements(NodeList<Statement> statements) {
for (int i = statements.length - 1; i >= 0; i--) {
if (statements[i].accept(this)) {
return true;
}
}
return false;
}
bool _visitVariableDeclarations(
NodeList<VariableDeclaration> variableDeclarations) {
for (int i = variableDeclarations.length - 1; i >= 0; i--) {
if (variableDeclarations[i].accept(this)) {
return true;
}
}
return false;
}
/**
* Return `true` if the given [node] exits.
*/
static bool exits(AstNode node) {
return new ExitDetector()._nodeExits(node);
}
}
/**
* The scope defined by a function.
*/
class FunctionScope extends EnclosedScope {
/**
* The element representing the function that defines this scope.
*/
final ExecutableElement _functionElement;
/**
* A flag indicating whether the parameters have already been defined, used to
* prevent the parameters from being defined multiple times.
*/
bool _parametersDefined = false;
/**
* Initialize a newly created scope enclosed within the [enclosingScope] that
* represents the given [_functionElement].
*/
FunctionScope(Scope enclosingScope, this._functionElement)
: super(new EnclosedScope(new EnclosedScope(enclosingScope))) {
if (_functionElement == null) {
throw new IllegalArgumentException("function element cannot be null");
}
_defineTypeParameters();
}
/**
* Define the parameters for the given function in the scope that encloses
* this function.
*/
void defineParameters() {
if (_parametersDefined) {
return;
}
_parametersDefined = true;
Scope parameterScope = enclosingScope;
for (ParameterElement parameter in _functionElement.parameters) {
if (!parameter.isInitializingFormal) {
parameterScope.define(parameter);
}
}
}
/**
* Define the type parameters for the function.
*/
void _defineTypeParameters() {
Scope typeParameterScope = enclosingScope.enclosingScope;
for (TypeParameterElement typeParameter
in _functionElement.typeParameters) {
typeParameterScope.define(typeParameter);
}
}
}
/**
* The scope defined by a function type alias.
*/
class FunctionTypeScope extends EnclosedScope {
final FunctionTypeAliasElement _typeElement;
bool _parametersDefined = false;
/**
* Initialize a newly created scope enclosed within the [enclosingScope] that
* represents the given [_typeElement].
*/
FunctionTypeScope(Scope enclosingScope, this._typeElement)
: super(new EnclosedScope(enclosingScope)) {
_defineTypeParameters();
}
/**
* Define the parameters for the function type alias.
*/
void defineParameters() {
if (_parametersDefined) {
return;
}
_parametersDefined = true;
for (ParameterElement parameter in _typeElement.parameters) {
define(parameter);
}
}
/**
* Define the type parameters for the function type alias.
*/
void _defineTypeParameters() {
Scope typeParameterScope = enclosingScope;
for (TypeParameterElement typeParameter in _typeElement.typeParameters) {
typeParameterScope.define(typeParameter);
}
}
}
/**
* A visitor that visits ASTs and fills [UsedImportedElements].
*/
class GatherUsedImportedElementsVisitor extends RecursiveAstVisitor {
final LibraryElement library;
final UsedImportedElements usedElements = new UsedImportedElements();
GatherUsedImportedElementsVisitor(this.library);
@override
void visitExportDirective(ExportDirective node) {
_visitMetadata(node.metadata);
}
@override
void visitImportDirective(ImportDirective node) {
_visitMetadata(node.metadata);
}
@override
void visitLibraryDirective(LibraryDirective node) {
_visitMetadata(node.metadata);
}
@override
void visitPrefixedIdentifier(PrefixedIdentifier node) {
// If the prefixed identifier references some A.B, where A is a library
// prefix, then we can lookup the associated ImportDirective in
// prefixElementMap and remove it from the unusedImports list.
SimpleIdentifier prefixIdentifier = node.prefix;
Element element = prefixIdentifier.staticElement;
if (element is PrefixElement) {
usedElements.prefixes.add(element);
return;
}
// Otherwise, pass the prefixed identifier element and name onto
// visitIdentifier.
_visitIdentifier(element, prefixIdentifier.name);
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
_visitIdentifier(node.staticElement, node.name);
}
void _visitIdentifier(Element element, String name) {
if (element == null) {
return;
}
// If the element is multiply defined then call this method recursively for
// each of the conflicting elements.
if (element is MultiplyDefinedElement) {
MultiplyDefinedElement multiplyDefinedElement = element;
for (Element elt in multiplyDefinedElement.conflictingElements) {
_visitIdentifier(elt, name);
}
return;
} else if (element is PrefixElement) {
usedElements.prefixes.add(element);
return;
} else if (element.enclosingElement is! CompilationUnitElement) {
// Identifiers that aren't a prefix element and whose enclosing element
// isn't a CompilationUnit are ignored- this covers the case the
// identifier is a relative-reference, a reference to an identifier not
// imported by this library.
return;
}
// Ignore if an unknown library.
LibraryElement containingLibrary = element.library;
if (containingLibrary == null) {
return;
}
// Ignore if a local element.
if (library == containingLibrary) {
return;
}
// Remember the element.
usedElements.elements.add(element);
}
/**
* Given some [NodeList] of [Annotation]s, ensure that the identifiers are visited by
* this visitor. Specifically, this covers the cases where AST nodes don't have their identifiers
* visited by this visitor, but still need their annotations visited.
*
* @param annotations the list of annotations to visit
*/
void _visitMetadata(NodeList<Annotation> annotations) {
int count = annotations.length;
for (int i = 0; i < count; i++) {
annotations[i].accept(this);
}
}
}
/**
* An [AstVisitor] that fills [UsedLocalElements].
*/
class GatherUsedLocalElementsVisitor extends RecursiveAstVisitor {
final UsedLocalElements usedElements = new UsedLocalElements();
final LibraryElement _enclosingLibrary;
ClassElement _enclosingClass;
ExecutableElement _enclosingExec;
GatherUsedLocalElementsVisitor(this._enclosingLibrary);
@override
visitCatchClause(CatchClause node) {
SimpleIdentifier exceptionParameter = node.exceptionParameter;
SimpleIdentifier stackTraceParameter = node.stackTraceParameter;
if (exceptionParameter != null) {
Element element = exceptionParameter.staticElement;
usedElements.addCatchException(element);
if (stackTraceParameter != null || node.onKeyword == null) {
usedElements.addElement(element);
}
}
if (stackTraceParameter != null) {
Element element = stackTraceParameter.staticElement;
usedElements.addCatchStackTrace(element);
}
super.visitCatchClause(node);
}
@override
visitClassDeclaration(ClassDeclaration node) {
ClassElement enclosingClassOld = _enclosingClass;
try {
_enclosingClass = node.element;
super.visitClassDeclaration(node);
} finally {
_enclosingClass = enclosingClassOld;
}
}
@override
visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement enclosingExecOld = _enclosingExec;
try {
_enclosingExec = node.element;
super.visitFunctionDeclaration(node);
} finally {
_enclosingExec = enclosingExecOld;
}
}
@override
visitFunctionExpression(FunctionExpression node) {
if (node.parent is! FunctionDeclaration) {
usedElements.addElement(node.element);
}
super.visitFunctionExpression(node);
}
@override
visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement enclosingExecOld = _enclosingExec;
try {
_enclosingExec = node.element;
super.visitMethodDeclaration(node);
} finally {
_enclosingExec = enclosingExecOld;
}
}
@override
visitSimpleIdentifier(SimpleIdentifier node) {
if (node.inDeclarationContext()) {
return;
}
Element element = node.staticElement;
bool isIdentifierRead = _isReadIdentifier(node);
if (element is LocalVariableElement) {
if (isIdentifierRead) {
usedElements.addElement(element);
}
} else {
_useIdentifierElement(node);
if (element == null ||
element.enclosingElement is ClassElement &&
!identical(element, _enclosingExec)) {
usedElements.members.add(node.name);
if (isIdentifierRead) {
usedElements.readMembers.add(node.name);
}
}
}
}
/**
* Marks an [Element] of [node] as used in the library.
*/
void _useIdentifierElement(Identifier node) {
Element element = node.staticElement;
if (element == null) {
return;
}
// check if a local element
if (!identical(element.library, _enclosingLibrary)) {
return;
}
// ignore references to an element from itself
if (identical(element, _enclosingClass)) {
return;
}
if (identical(element, _enclosingExec)) {
return;
}
// ignore places where the element is not actually used
if (node.parent is TypeName) {
if (element is ClassElement) {
AstNode parent2 = node.parent.parent;
if (parent2 is IsExpression) {
return;
}
if (parent2 is VariableDeclarationList) {
// If it's a field's type, it still counts as used.
if (parent2.parent is! FieldDeclaration) {
return;
}
}
}
}
// OK
usedElements.addElement(element);
}
static bool _isReadIdentifier(SimpleIdentifier node) {
// not reading at all
if (!node.inGetterContext()) {
return false;
}
// check if useless reading
AstNode parent = node.parent;
if (parent.parent is ExpressionStatement &&
(parent is PrefixExpression ||
parent is PostfixExpression ||
parent is AssignmentExpression && parent.leftHandSide == node)) {
// v++;
// ++v;
// v += 2;
return false;
}
// OK
return true;
}
}
/**
* Instances of the class `HintGenerator` traverse a library's worth of dart code at a time to
* generate hints over the set of sources.
*
* See [HintCode].
*/
class HintGenerator {
final List<CompilationUnit> _compilationUnits;
final InternalAnalysisContext _context;
final AnalysisErrorListener _errorListener;
LibraryElement _library;
GatherUsedImportedElementsVisitor _usedImportedElementsVisitor;
bool _enableDart2JSHints = false;
/**
* The inheritance manager used to find overridden methods.
*/
InheritanceManager _manager;
GatherUsedLocalElementsVisitor _usedLocalElementsVisitor;
HintGenerator(this._compilationUnits, this._context, this._errorListener) {
_library = _compilationUnits[0].element.library;
_usedImportedElementsVisitor =
new GatherUsedImportedElementsVisitor(_library);
_enableDart2JSHints = _context.analysisOptions.dart2jsHint;
_manager = new InheritanceManager(_library);
_usedLocalElementsVisitor = new GatherUsedLocalElementsVisitor(_library);
}
void generateForLibrary() {
PerformanceStatistics.hints.makeCurrentWhile(() {
for (CompilationUnit unit in _compilationUnits) {
CompilationUnitElement element = unit.element;
if (element != null) {
_generateForCompilationUnit(unit, element.source);
}
}
CompilationUnit definingUnit = _compilationUnits[0];
ErrorReporter definingUnitErrorReporter =
new ErrorReporter(_errorListener, definingUnit.element.source);
{
ImportsVerifier importsVerifier = new ImportsVerifier();
importsVerifier.addImports(definingUnit);
importsVerifier
.removeUsedElements(_usedImportedElementsVisitor.usedElements);
importsVerifier.generateDuplicateImportHints(definingUnitErrorReporter);
importsVerifier.generateUnusedImportHints(definingUnitErrorReporter);
}
_library.accept(new UnusedLocalElementsVerifier(
_errorListener, _usedLocalElementsVisitor.usedElements));
});
}
void _generateForCompilationUnit(CompilationUnit unit, Source source) {
ErrorReporter errorReporter = new ErrorReporter(_errorListener, source);
unit.accept(_usedImportedElementsVisitor);
// dead code analysis
unit.accept(
new DeadCodeVerifier(errorReporter, typeSystem: _context.typeSystem));
unit.accept(_usedLocalElementsVisitor);
// dart2js analysis
if (_enableDart2JSHints) {
unit.accept(new Dart2JSVerifier(errorReporter));
}
// Dart best practices
unit.accept(new BestPracticesVerifier(errorReporter, _context.typeProvider,
typeSystem: _context.typeSystem));
unit.accept(new OverrideVerifier(errorReporter, _manager));
// Find to-do comments
new ToDoFinder(errorReporter).findIn(unit);
// pub analysis
// TODO(danrubel/jwren) Commented out until bugs in the pub verifier are
// fixed
// unit.accept(new PubVerifier(context, errorReporter));
}
}
/**
* Instances of the class `ImplicitLabelScope` represent the scope statements
* that can be the target of unlabeled break and continue statements.
*/
class ImplicitLabelScope {
/**
* The implicit label scope associated with the top level of a function.
*/
static const ImplicitLabelScope ROOT = const ImplicitLabelScope._(null, null);
/**
* The implicit label scope enclosing this implicit label scope.
*/
final ImplicitLabelScope outerScope;
/**
* The statement that acts as a target for break and/or continue statements
* at this scoping level.
*/
final Statement statement;
/**
* Private constructor.
*/
const ImplicitLabelScope._(this.outerScope, this.statement);
/**
* Get the statement which should be the target of an unlabeled `break` or
* `continue` statement, or `null` if there is no appropriate target.
*/
Statement getTarget(bool isContinue) {
if (outerScope == null) {
// This scope represents the toplevel of a function body, so it doesn't
// match either break or continue.
return null;
}
if (isContinue && statement is SwitchStatement) {
return outerScope.getTarget(isContinue);
}
return statement;
}
/**
* Initialize a newly created scope to represent a switch statement or loop
* nested within the current scope. [statement] is the statement associated
* with the newly created scope.
*/
ImplicitLabelScope nest(Statement statement) =>
new ImplicitLabelScope._(this, statement);
}
/**
* Instances of the class `ImportsVerifier` visit all of the referenced libraries in the
* source code verifying that all of the imports are used, otherwise a
* [HintCode.UNUSED_IMPORT] is generated with
* [generateUnusedImportHints].
*
* While this class does not yet have support for an "Organize Imports" action, this logic built up
* in this class could be used for such an action in the future.
*/
class ImportsVerifier /*extends RecursiveAstVisitor<Object>*/ {
/**
* A list of [ImportDirective]s that the current library imports, as identifiers are visited
* by this visitor and an import has been identified as being used by the library, the
* [ImportDirective] is removed from this list. After all the sources in the library have
* been evaluated, this list represents the set of unused imports.
*
* See [ImportsVerifier.generateUnusedImportErrors].
*/
final List<ImportDirective> _unusedImports = <ImportDirective>[];
/**
* After the list of [unusedImports] has been computed, this list is a proper subset of the
* unused imports that are listed more than once.
*/
final List<ImportDirective> _duplicateImports = <ImportDirective>[];
/**
* This is a map between the set of [LibraryElement]s that the current library imports, and
* a list of [ImportDirective]s that imports the library. In cases where the current library
* imports a library with a single directive (such as `import lib1.dart;`), the library
* element will map to a list of one [ImportDirective], which will then be removed from the
* [unusedImports] list. In cases where the current library imports a library with multiple
* directives (such as `import lib1.dart; import lib1.dart show C;`), the
* [LibraryElement] will be mapped to a list of the import directives, and the namespace
* will need to be used to compute the correct [ImportDirective] being used, see
* [namespaceMap].
*/
final HashMap<LibraryElement, List<ImportDirective>> _libraryMap =
new HashMap<LibraryElement, List<ImportDirective>>();
/**
* In cases where there is more than one import directive per library element, this mapping is
* used to determine which of the multiple import directives are used by generating a
* [Namespace] for each of the imports to do lookups in the same way that they are done from
* the [ElementResolver].
*/
final HashMap<ImportDirective, Namespace> _namespaceMap =
new HashMap<ImportDirective, Namespace>();
/**
* This is a map between prefix elements and the import directives from which they are derived. In
* cases where a type is referenced via a prefix element, the import directive can be marked as
* used (removed from the unusedImports) by looking at the resolved `lib` in `lib.X`,
* instead of looking at which library the `lib.X` resolves.
*
* TODO (jwren) Since multiple [ImportDirective]s can share the same [PrefixElement],
* it is possible to have an unreported unused import in situations where two imports use the same
* prefix and at least one import directive is used.
*/
final HashMap<PrefixElement, List<ImportDirective>> _prefixElementMap =
new HashMap<PrefixElement, List<ImportDirective>>();
void addImports(CompilationUnit node) {
for (Directive directive in node.directives) {
if (directive is ImportDirective) {
ImportDirective importDirective = directive;
LibraryElement libraryElement = importDirective.uriElement;
if (libraryElement != null) {
_unusedImports.add(importDirective);
//
// Initialize prefixElementMap
//
if (importDirective.asKeyword != null) {
SimpleIdentifier prefixIdentifier = importDirective.prefix;
if (prefixIdentifier != null) {
Element element = prefixIdentifier.staticElement;
if (element is PrefixElement) {
PrefixElement prefixElementKey = element;
List<ImportDirective> list =
_prefixElementMap[prefixElementKey];
if (list == null) {
list = new List<ImportDirective>();
_prefixElementMap[prefixElementKey] = list;
}
list.add(importDirective);
}
// TODO (jwren) Can the element ever not be a PrefixElement?
}
}
//
// Initialize libraryMap: libraryElement -> importDirective
//
_putIntoLibraryMap(libraryElement, importDirective);
//
// For this new addition to the libraryMap, also recursively add any
// exports from the libraryElement.
//
_addAdditionalLibrariesForExports(
libraryElement, importDirective, new List<LibraryElement>());
}
}
}
if (_unusedImports.length > 1) {
// order the list of unusedImports to find duplicates in faster than
// O(n^2) time
List<ImportDirective> importDirectiveArray =
new List<ImportDirective>.from(_unusedImports);
importDirectiveArray.sort(ImportDirective.COMPARATOR);
ImportDirective currentDirective = importDirectiveArray[0];
for (int i = 1; i < importDirectiveArray.length; i++) {
ImportDirective nextDirective = importDirectiveArray[i];
if (ImportDirective.COMPARATOR(currentDirective, nextDirective) == 0) {
// Add either the currentDirective or nextDirective depending on which
// comes second, this guarantees that the first of the duplicates
// won't be highlighted.
if (currentDirective.offset < nextDirective.offset) {
_duplicateImports.add(nextDirective);
} else {
_duplicateImports.add(currentDirective);
}
}
currentDirective = nextDirective;
}
}
}
/**
* Any time after the defining compilation unit has been visited by this visitor, this method can
* be called to report an [HintCode.DUPLICATE_IMPORT] hint for each of the import directives
* in the [duplicateImports] list.
*
* @param errorReporter the error reporter to report the set of [HintCode.DUPLICATE_IMPORT]
* hints to
*/
void generateDuplicateImportHints(ErrorReporter errorReporter) {
for (ImportDirective duplicateImport in _duplicateImports) {
errorReporter.reportErrorForNode(
HintCode.DUPLICATE_IMPORT, duplicateImport.uri);
}
}
/**
* After all of the compilation units have been visited by this visitor, this method can be called
* to report an [HintCode.UNUSED_IMPORT] hint for each of the import directives in the
* [unusedImports] list.
*
* @param errorReporter the error reporter to report the set of [HintCode.UNUSED_IMPORT]
* hints to
*/
void generateUnusedImportHints(ErrorReporter errorReporter) {
for (ImportDirective unusedImport in _unusedImports) {
// Check that the import isn't dart:core
ImportElement importElement = unusedImport.element;
if (importElement != null) {
LibraryElement libraryElement = importElement.importedLibrary;
if (libraryElement != null && libraryElement.isDartCore) {
continue;
}
}
errorReporter.reportErrorForNode(
HintCode.UNUSED_IMPORT, unusedImport.uri);
}
}
/**
* Remove elements from [_unusedImports] using the given [usedElements].
*/
void removeUsedElements(UsedImportedElements usedElements) {
// Stop if all the imports are known to be used.
if (_unusedImports.isEmpty) {
return;
}
// Process import prefixes.
for (PrefixElement prefix in usedElements.prefixes) {
List<ImportDirective> importDirectives = _prefixElementMap[prefix];
if (importDirectives != null) {
for (ImportDirective importDirective in importDirectives) {
_unusedImports.remove(importDirective);
}
}
}
// Process top-level elements.
for (Element element in usedElements.elements) {
// Stop if all the imports are known to be used.
if (_unusedImports.isEmpty) {
return;
}
// Prepare import directives for this library.
LibraryElement library = element.library;
List<ImportDirective> importsLibrary = _libraryMap[library];
if (importsLibrary == null) {
continue;
}
// If there is only one import directive for this library, then it must be
// the directive that this element is imported with, remove it from the
// unusedImports list.
if (importsLibrary.length == 1) {
ImportDirective usedImportDirective = importsLibrary[0];
_unusedImports.remove(usedImportDirective);
continue;
}
// Otherwise, find import directives using namespaces.
String name = element.displayName;
for (ImportDirective importDirective in importsLibrary) {
Namespace namespace = _computeNamespace(importDirective);
if (namespace != null && namespace.get(name) != null) {
_unusedImports.remove(importDirective);
}
}
}
}
/**
* Recursively add any exported library elements into the [libraryMap].
*/
void _addAdditionalLibrariesForExports(LibraryElement library,
ImportDirective importDirective, List<LibraryElement> exportPath) {
if (exportPath.contains(library)) {
return;
}
exportPath.add(library);
for (LibraryElement exportedLibraryElt in library.exportedLibraries) {
_putIntoLibraryMap(exportedLibraryElt, importDirective);
_addAdditionalLibrariesForExports(
exportedLibraryElt, importDirective, exportPath);
}
}
/**
* Lookup and return the [Namespace] from the [namespaceMap], if the map does not
* have the computed namespace, compute it and cache it in the map. If the import directive is not
* resolved or is not resolvable, `null` is returned.
*
* @param importDirective the import directive used to compute the returned namespace
* @return the computed or looked up [Namespace]
*/
Namespace _computeNamespace(ImportDirective importDirective) {
Namespace namespace = _namespaceMap[importDirective];
if (namespace == null) {
// If the namespace isn't in the namespaceMap, then compute and put it in
// the map.
ImportElement importElement = importDirective.element;
if (importElement != null) {
NamespaceBuilder builder = new NamespaceBuilder();
namespace = builder.createImportNamespaceForDirective(importElement);
_namespaceMap[importDirective] = namespace;
}
}
return namespace;
}
/**
* The [libraryMap] is a mapping between a library elements and a list of import
* directives, but when adding these mappings into the [libraryMap], this method can be
* used to simply add the mapping between the library element an an import directive without
* needing to check to see if a list needs to be created.
*/
void _putIntoLibraryMap(
LibraryElement libraryElement, ImportDirective importDirective) {
List<ImportDirective> importList = _libraryMap[libraryElement];
if (importList == null) {
importList = new List<ImportDirective>();
_libraryMap[libraryElement] = importList;
}
importList.add(importDirective);
}
}
/**
* Maintains and manages contextual type information used for
* inferring types.
*/
class InferenceContext {
// TODO(leafp): Consider replacing these node properties with a
// hash table help in an instance of this class.
static const String _typeProperty =
'analyzer.src.generated.InferenceContext.contextType';
/**
* The error listener on which to record inference information.
*/
final AnalysisErrorListener _errorListener;
/**
* If true, emit hints when types are inferred
*/
final bool _inferenceHints;
/**
* Type provider, needed for type matching.
*/
final TypeProvider _typeProvider;
/**
* The type system in use.
*/
final TypeSystem _typeSystem;
/**
* A stack of return types for all of the enclosing
* functions and methods.
*/
// TODO(leafp) Handle the implicit union type for Futures
// https://github.com/dart-lang/sdk/issues/25322
List<DartType> _returnStack = <DartType>[];
InferenceContext._(this._errorListener, TypeProvider typeProvider,
this._typeSystem, this._inferenceHints)
: _typeProvider = typeProvider;
/**
* Get the return type of the current enclosing function, if any.
*
* The type returned for a function is the type that is expected
* to be used in a return or yield context. For ordinary functions
* this is the same as the return type of the function. For async
* functions returning Future<T> and for generator functions
* returning Stream<T> or Iterable<T>, this is T.
*/
DartType get returnContext =>
_returnStack.isNotEmpty ? _returnStack.last : null;
/**
* Match type [t1] against type [t2] as follows.
* If `t1 = I<dynamic, ..., dynamic>`, then look for a supertype
* of t1 of the form `K<S0, ..., Sm>` where `t2 = K<S0', ..., Sm'>`
* If the supertype exists, use the constraints `S0 <: S0', ... Sm <: Sm'`
* to derive a concrete instantation for I of the form `<T0, ..., Tn>`,
* such that `I<T0, .., Tn> <: t2`
*/
List<DartType> matchTypes(DartType t1, DartType t2) =>
(t1 is InterfaceType && t2 is InterfaceType) ? _matchTypes(t1, t2) : null;
/**
* Pop a return type off of the return stack.
*/
void popReturnContext() {
assert(_returnStack.isNotEmpty);
if (_returnStack.isNotEmpty) {
_returnStack.removeLast();
}
}
/**
* Push a [returnType] onto the return stack.
*/
void pushReturnContext(DartType returnType) {
_returnStack.add(returnType);
}
/**
* Place an info node into the error stream indicating that a
* [type] has been inferred as the type of [node].
*/
void recordInference(Expression node, DartType type) {
StaticInfo info = InferredType.create(_typeSystem, node, type);
if (!_inferenceHints || info == null) {
return;
}
AnalysisError error = info.toAnalysisError();
_errorListener.onError(error);
}
List<DartType> _matchTypes(InterfaceType t1, InterfaceType t2) {
if (t1 == t2) {
return t2.typeArguments;
}
List<DartType> tArgs1 = t1.typeArguments;
List<DartType> tArgs2 = t2.typeArguments;
// If t1 isn't a raw type, bail out
if (tArgs1 != null && tArgs1.any((t) => !t.isDynamic)) {
return null;
}
// This is our inferred type argument list. We start at all dynamic,
// and fill in with inferred types when we reach a match.
List<DartType> actuals =
new List<DartType>.filled(tArgs1.length, _typeProvider.dynamicType);
// When we find the supertype of t1 with the same
// classname as t2 (see below), we have the following:
// If t1 is an instantiation of a class T1<X0, ..., Xn>
// and t2 is an instantiation of a class T2<Y0, ...., Ym>
// of the form t2 = T2<S0, ..., Sm>
// then we want to choose instantiations for the Xi
// T0, ..., Tn such that T1<T0, ..., Tn> <: t2 .
// To find this, we simply instantate T1 with
// X0, ..., Xn, and then find its superclass
// T2<T0', ..., Tn'>. We then solve the constraint
// set T0' <: S0, ..., Tn' <: Sn for the Xi.
// Currently, we only handle constraints where
// the Ti' is one of the Xi'. If there are multiple
// constraints on some Xi, we choose the lower of the
// two (if it exists).
bool permute(List<DartType> permutedArgs) {
if (permutedArgs == null) {
return false;
}
List<TypeParameterElement> ps = t1.typeParameters;
List<DartType> ts = ps.map((p) => p.type).toList();
for (int i = 0; i < permutedArgs.length; i++) {
DartType tVar = permutedArgs[i];
DartType tActual = tArgs2[i];
int index = ts.indexOf(tVar);
if (index >= 0 && _typeSystem.isSubtypeOf(tActual, actuals[index])) {
actuals[index] = tActual;
}
}
return actuals.any((x) => !x.isDynamic);
}
// Look for the first supertype of t1 with the same class name as t2.
bool match(InterfaceType t1, Set<Element> visited) {
if (t1.element == t2.element) {
return permute(t1.typeArguments);
}
if (t1 == _typeProvider.objectType) {
return false;
}
Element element = t1.element;
if (visited == null) {
visited = new HashSet<Element>();
}
if (element == null || !visited.add(element)) {
return false;
}
try {
if (match(t1.superclass, visited)) {
return true;
}
for (final parent in t1.mixins) {
if (match(parent, visited)) {
return true;
}
}
for (final parent in t1.interfaces) {
if (match(parent, visited)) {
return true;
}
}
} finally {
visited.remove(element);
}
return false;
}
// We have that t1 = T1<dynamic, ..., dynamic>.
// To match t1 against t2, we use the uninstantiated version
// of t1, essentially treating it as an instantiation with
// fresh variables, and solve for the variables.
// t1.element.type will be of the form T1<X0, ..., Xn>
if (!match(t1.element.type, null)) {
return null;
}
DartType newT1 = t1.element.type.substitute4(actuals);
// If we found a solution, return it.
if (_typeSystem.isSubtypeOf(newT1, t2)) {
return actuals;
}
return null;
}
/**
* Clear the type information assocated with [node].
*/
static void clearType(AstNode node) {
node?.setProperty(_typeProperty, null);
}
/**
* Look for contextual type information attached to [node]. Returns
* the type if found, otherwise null.
*/
static DartType getType(AstNode node) => node?.getProperty(_typeProperty);
/**
* Attach contextual type information [type] to [node] for use during
* inference.
*/
static void setType(AstNode node, DartType type) {
node?.setProperty(_typeProperty, type);
}
/**
* Attach contextual type information [type] to [node] for use during
* inference.
*/
static void setTypeFromNode(AstNode innerNode, AstNode outerNode) {
setType(innerNode, getType(outerNode));
}
}
/**
* Instances of the class `InheritanceManager` manage the knowledge of where class members
* (methods, getters & setters) are inherited from.
*/
class InheritanceManager {
/**
* The [LibraryElement] that is managed by this manager.
*/
LibraryElement _library;
/**
* This is a mapping between each [ClassElement] and a map between the [String] member
* names and the associated [ExecutableElement] in the mixin and superclass chain.
*/
HashMap<ClassElement, MemberMap> _classLookup;
/**
* This is a mapping between each [ClassElement] and a map between the [String] member
* names and the associated [ExecutableElement] in the interface set.
*/
HashMap<ClassElement, MemberMap> _interfaceLookup;
/**
* A map between each visited [ClassElement] and the set of [AnalysisError]s found on
* the class element.
*/
HashMap<ClassElement, HashSet<AnalysisError>> _errorsInClassElement =
new HashMap<ClassElement, HashSet<AnalysisError>>();
/**
* Initialize a newly created inheritance manager.
*
* @param library the library element context that the inheritance mappings are being generated
*/
InheritanceManager(LibraryElement library) {
this._library = library;
_classLookup = new HashMap<ClassElement, MemberMap>();
_interfaceLookup = new HashMap<ClassElement, MemberMap>();
}
/**
* Set the new library element context.
*
* @param library the new library element
*/
void set libraryElement(LibraryElement library) {
this._library = library;
}
/**
* Return the set of [AnalysisError]s found on the passed [ClassElement], or
* `null` if there are none.
*
* @param classElt the class element to query
* @return the set of [AnalysisError]s found on the passed [ClassElement], or
* `null` if there are none
*/
HashSet<AnalysisError> getErrors(ClassElement classElt) =>
_errorsInClassElement[classElt];
/**
* Get and return a mapping between the set of all string names of the members inherited from the
* passed [ClassElement] superclass hierarchy, and the associated [ExecutableElement].
*
* @param classElt the class element to query
* @return a mapping between the set of all members inherited from the passed [ClassElement]
* superclass hierarchy, and the associated [ExecutableElement]
*/
MemberMap getMapOfMembersInheritedFromClasses(ClassElement classElt) =>
_computeClassChainLookupMap(classElt, new HashSet<ClassElement>());
/**
* Get and return a mapping between the set of all string names of the members inherited from the
* passed [ClassElement] interface hierarchy, and the associated [ExecutableElement].
*
* @param classElt the class element to query
* @return a mapping between the set of all string names of the members inherited from the passed
* [ClassElement] interface hierarchy, and the associated [ExecutableElement].
*/
MemberMap getMapOfMembersInheritedFromInterfaces(ClassElement classElt) =>
_computeInterfaceLookupMap(classElt, new HashSet<ClassElement>());
/**
* Given some [ClassElement] and some member name, this returns the
* [ExecutableElement] that the class inherits from the mixins,
* superclasses or interfaces, that has the member name, if no member is inherited `null` is
* returned.
*
* @param classElt the class element to query
* @param memberName the name of the executable element to find and return
* @return the inherited executable element with the member name, or `null` if no such
* member exists
*/
ExecutableElement lookupInheritance(
ClassElement classElt, String memberName) {
if (memberName == null || memberName.isEmpty) {
return null;
}
ExecutableElement executable =
_computeClassChainLookupMap(classElt, new HashSet<ClassElement>())
.get(memberName);
if (executable == null) {
return _computeInterfaceLookupMap(classElt, new HashSet<ClassElement>())
.get(memberName);
}
return executable;
}
/**
* Given some [ClassElement] and some member name, this returns the
* [ExecutableElement] that the class either declares itself, or
* inherits, that has the member name, if no member is inherited `null` is returned.
*
* @param classElt the class element to query
* @param memberName the name of the executable element to find and return
* @return the inherited executable element with the member name, or `null` if no such
* member exists
*/
ExecutableElement lookupMember(ClassElement classElt, String memberName) {
ExecutableElement element = _lookupMemberInClass(classElt, memberName);
if (element != null) {
return element;
}
return lookupInheritance(classElt, memberName);
}
/**
* Determine the set of methods which is overridden by the given class member. If no member is
* inherited, an empty list is returned. If one of the inherited members is a
* [MultiplyInheritedExecutableElement], then it is expanded into its constituent inherited
* elements.
*
* @param classElt the class to query
* @param memberName the name of the class member to query
* @return a list of overridden methods
*/
List<ExecutableElement> lookupOverrides(
ClassElement classElt, String memberName) {
List<ExecutableElement> result = new List<ExecutableElement>();
if (memberName == null || memberName.isEmpty) {
return result;
}
List<MemberMap> interfaceMaps =
_gatherInterfaceLookupMaps(classElt, new HashSet<ClassElement>());
if (interfaceMaps != null) {
for (MemberMap interfaceMap in interfaceMaps) {
ExecutableElement overriddenElement = interfaceMap.get(memberName);
if (overriddenElement != null) {
if (overriddenElement is MultiplyInheritedExecutableElement) {
MultiplyInheritedExecutableElement multiplyInheritedElement =
overriddenElement;
for (ExecutableElement element
in multiplyInheritedElement.inheritedElements) {
result.add(element);
}
} else {
result.add(overriddenElement);
}
}
}
}
return result;
}
/**
* This method takes some inherited [FunctionType], and resolves all the parameterized types
* in the function type, dependent on the class in which it is being overridden.
*
* @param baseFunctionType the function type that is being overridden
* @param memberName the name of the member, this is used to lookup the inheritance path of the
* override
* @param definingType the type that is overriding the member
* @return the passed function type with any parameterized types substituted
*/
// TODO(jmesserly): investigate why this is needed in ErrorVerifier's override
// checking. There seems to be some rare cases where we get partially
// substituted type arguments, and the function types don't compare equally.
FunctionType substituteTypeArgumentsInMemberFromInheritance(
FunctionType baseFunctionType,
String memberName,
InterfaceType definingType) {
// if the baseFunctionType is null, or does not have any parameters,
// return it.
if (baseFunctionType == null ||
baseFunctionType.typeArguments.length == 0) {
return baseFunctionType;
}
// First, generate the path from the defining type to the overridden member
Queue<InterfaceType> inheritancePath = new Queue<InterfaceType>();
_computeInheritancePath(inheritancePath, definingType, memberName);
if (inheritancePath == null || inheritancePath.isEmpty) {
// TODO(jwren) log analysis engine error
return baseFunctionType;
}
FunctionType functionTypeToReturn = baseFunctionType;
// loop backward through the list substituting as we go:
while (!inheritancePath.isEmpty) {
InterfaceType lastType = inheritancePath.removeLast();
List<DartType> parameterTypes = lastType.element.type.typeArguments;
List<DartType> argumentTypes = lastType.typeArguments;
functionTypeToReturn =
functionTypeToReturn.substitute2(argumentTypes, parameterTypes);
}
return functionTypeToReturn;
}
/**
* Compute and return a mapping between the set of all string names of the members inherited from
* the passed [ClassElement] superclass hierarchy, and the associated
* [ExecutableElement].
*
* @param classElt the class element to query
* @param visitedClasses a set of visited classes passed back into this method when it calls
* itself recursively
* @return a mapping between the set of all string names of the members inherited from the passed
* [ClassElement] superclass hierarchy, and the associated [ExecutableElement]
*/
MemberMap _computeClassChainLookupMap(
ClassElement classElt, HashSet<ClassElement> visitedClasses) {
MemberMap resultMap = _classLookup[classElt];
if (resultMap != null) {
return resultMap;
} else {
resultMap = new MemberMap();
}
ClassElement superclassElt = null;
InterfaceType supertype = classElt.supertype;
if (supertype != null) {
superclassElt = supertype.element;
} else {
// classElt is Object
_classLookup[classElt] = resultMap;
return resultMap;
}
if (superclassElt != null) {
if (!visitedClasses.contains(superclassElt)) {
visitedClasses.add(superclassElt);
try {
resultMap = new MemberMap.from(
_computeClassChainLookupMap(superclassElt, visitedClasses));
//
// Substitute the super types down the hierarchy.
//
_substituteTypeParametersDownHierarchy(supertype, resultMap);
//
// Include the members from the superclass in the resultMap.
//
_recordMapWithClassMembers(resultMap, supertype, false);
} finally {
visitedClasses.remove(superclassElt);
}
} else {
// This case happens only when the superclass was previously visited and
// not in the lookup, meaning this is meant to shorten the compute for
// recursive cases.
_classLookup[superclassElt] = resultMap;
return resultMap;
}
}
//
// Include the members from the mixins in the resultMap. If there are
// multiple mixins, visit them in the order listed so that methods in later
// mixins will overwrite identically-named methods in earlier mixins.
//
List<InterfaceType> mixins = classElt.mixins;
for (InterfaceType mixin in mixins) {
ClassElement mixinElement = mixin.element;
if (mixinElement != null) {
if (!visitedClasses.contains(mixinElement)) {
visitedClasses.add(mixinElement);
try {
MemberMap map = new MemberMap.from(
_computeClassChainLookupMap(mixinElement, visitedClasses));
//
// Substitute the super types down the hierarchy.
//
_substituteTypeParametersDownHierarchy(mixin, map);
//
// Include the members from the superclass in the resultMap.
//
_recordMapWithClassMembers(map, mixin, false);
//
// Add the members from map into result map.
//
for (int j = 0; j < map.size; j++) {
String key = map.getKey(j);
ExecutableElement value = map.getValue(j);
if (key != null) {
ClassElement definingClass = value
.getAncestor((Element element) => element is ClassElement);
if (!definingClass.type.isObject) {
ExecutableElement existingValue = resultMap.get(key);
if (existingValue == null ||
(existingValue != null && !_isAbstract(value))) {
resultMap.put(key, value);
}
}
}
}
} finally {
visitedClasses.remove(mixinElement);
}
} else {
// This case happens only when the superclass was previously visited
// and not in the lookup, meaning this is meant to shorten the compute
// for recursive cases.
_classLookup[mixinElement] = resultMap;
return resultMap;
}
}
}
_classLookup[classElt] = resultMap;
return resultMap;
}
/**
* Compute and return the inheritance path given the context of a type and a member that is
* overridden in the inheritance path (for which the type is in the path).
*
* @param chain the inheritance path that is built up as this method calls itself recursively,
* when this method is called an empty [LinkedList] should be provided
* @param currentType the current type in the inheritance path
* @param memberName the name of the member that is being looked up the inheritance path
*/
void _computeInheritancePath(Queue<InterfaceType> chain,
InterfaceType currentType, String memberName) {
// TODO (jwren) create a public version of this method which doesn't require
// the initial chain to be provided, then provided tests for this
// functionality in InheritanceManagerTest
chain.add(currentType);
ClassElement classElt = currentType.element;
InterfaceType supertype = classElt.supertype;
// Base case- reached Object
if (supertype == null) {
// Looked up the chain all the way to Object, return null.
// This should never happen.
return;
}
// If we are done, return the chain
// We are not done if this is the first recursive call on this method.
if (chain.length != 1) {
// We are done however if the member is in this classElt
if (_lookupMemberInClass(classElt, memberName) != null) {
return;
}
}
// Mixins- note that mixins call lookupMemberInClass, not lookupMember
List<InterfaceType> mixins = classElt.mixins;
for (int i = mixins.length - 1; i >= 0; i--) {
ClassElement mixinElement = mixins[i].element;
if (mixinElement != null) {
ExecutableElement elt = _lookupMemberInClass(mixinElement, memberName);
if (elt != null) {
// this is equivalent (but faster than) calling this method
// recursively
// (return computeInheritancePath(chain, mixins[i], memberName);)
chain.add(mixins[i]);
return;
}
}
}
// Superclass
ClassElement superclassElt = supertype.element;
if (lookupMember(superclassElt, memberName) != null) {
_computeInheritancePath(chain, supertype, memberName);
return;
}
// Interfaces
List<InterfaceType> interfaces = classElt.interfaces;
for (InterfaceType interfaceType in interfaces) {
ClassElement interfaceElement = interfaceType.element;
if (interfaceElement != null &&
lookupMember(interfaceElement, memberName) != null) {
_computeInheritancePath(chain, interfaceType, memberName);
return;
}
}
}
/**
* Compute and return a mapping between the set of all string names of the members inherited from
* the passed [ClassElement] interface hierarchy, and the associated
* [ExecutableElement].
*
* @param classElt the class element to query
* @param visitedInterfaces a set of visited classes passed back into this method when it calls
* itself recursively
* @return a mapping between the set of all string names of the members inherited from the passed
* [ClassElement] interface hierarchy, and the associated [ExecutableElement]
*/
MemberMap _computeInterfaceLookupMap(
ClassElement classElt, HashSet<ClassElement> visitedInterfaces) {
MemberMap resultMap = _interfaceLookup[classElt];
if (resultMap != null) {
return resultMap;
}
List<MemberMap> lookupMaps =
_gatherInterfaceLookupMaps(classElt, visitedInterfaces);
if (lookupMaps == null) {
resultMap = new MemberMap();
} else {
HashMap<String, List<ExecutableElement>> unionMap =
_unionInterfaceLookupMaps(lookupMaps);
resultMap = _resolveInheritanceLookup(classElt, unionMap);
}
_interfaceLookup[classElt] = resultMap;
return resultMap;
}
/**
* Collect a list of interface lookup maps whose elements correspond to all of the classes
* directly above [classElt] in the class hierarchy (the direct superclass if any, all
* mixins, and all direct superinterfaces). Each item in the list is the interface lookup map
* returned by [computeInterfaceLookupMap] for the corresponding super, except with type
* parameters appropriately substituted.
*
* @param classElt the class element to query
* @param visitedInterfaces a set of visited classes passed back into this method when it calls
* itself recursively
* @return `null` if there was a problem (such as a loop in the class hierarchy) or if there
* are no classes above this one in the class hierarchy. Otherwise, a list of interface
* lookup maps.
*/
List<MemberMap> _gatherInterfaceLookupMaps(
ClassElement classElt, HashSet<ClassElement> visitedInterfaces) {
InterfaceType supertype = classElt.supertype;
ClassElement superclassElement =
supertype != null ? supertype.element : null;
List<InterfaceType> mixins = classElt.mixins;
List<InterfaceType> interfaces = classElt.interfaces;
// Recursively collect the list of mappings from all of the interface types
List<MemberMap> lookupMaps = new List<MemberMap>();
//
// Superclass element
//
if (superclassElement != null) {
if (!visitedInterfaces.contains(superclassElement)) {
try {
visitedInterfaces.add(superclassElement);
//
// Recursively compute the map for the super type.
//
MemberMap map =
_computeInterfaceLookupMap(superclassElement, visitedInterfaces);
map = new MemberMap.from(map);
//
// Substitute the super type down the hierarchy.
//
_substituteTypeParametersDownHierarchy(supertype, map);
//
// Add any members from the super type into the map as well.
//
_recordMapWithClassMembers(map, supertype, true);
lookupMaps.add(map);
} finally {
visitedInterfaces.remove(superclassElement);
}
} else {
return null;
}
}
//
// Mixin elements
//
for (int i = mixins.length - 1; i >= 0; i--) {
InterfaceType mixinType = mixins[i];
ClassElement mixinElement = mixinType.element;
if (mixinElement != null) {
if (!visitedInterfaces.contains(mixinElement)) {
try {
visitedInterfaces.add(mixinElement);
//
// Recursively compute the map for the mixin.
//
MemberMap map =
_computeInterfaceLookupMap(mixinElement, visitedInterfaces);
map = new MemberMap.from(map);
//
// Substitute the mixin type down the hierarchy.
//
_substituteTypeParametersDownHierarchy(mixinType, map);
//
// Add any members from the mixin type into the map as well.
//
_recordMapWithClassMembers(map, mixinType, true);
lookupMaps.add(map);
} finally {
visitedInterfaces.remove(mixinElement);
}
} else {
return null;
}
}
}
//
// Interface elements
//
for (InterfaceType interfaceType in interfaces) {
ClassElement interfaceElement = interfaceType.element;
if (interfaceElement != null) {
if (!visitedInterfaces.contains(interfaceElement)) {
try {
visitedInterfaces.add(interfaceElement);
//
// Recursively compute the map for the interfaces.
//
MemberMap map =
_computeInterfaceLookupMap(interfaceElement, visitedInterfaces);
map = new MemberMap.from(map);
//
// Substitute the supertypes down the hierarchy
//
_substituteTypeParametersDownHierarchy(interfaceType, map);
//
// And add any members from the interface into the map as well.
//
_recordMapWithClassMembers(map, interfaceType, true);
lookupMaps.add(map);
} finally {
visitedInterfaces.remove(interfaceElement);
}
} else {
return null;
}
}
}
if (lookupMaps.length == 0) {
return null;
}
return lookupMaps;
}
/**
* Given some [ClassElement], this method finds and returns the [ExecutableElement] of
* the passed name in the class element. Static members, members in super types and members not
* accessible from the current library are not considered.
*
* @param classElt the class element to query
* @param memberName the name of the member to lookup in the class
* @return the found [ExecutableElement], or `null` if no such member was found
*/
ExecutableElement _lookupMemberInClass(
ClassElement classElt, String memberName) {
List<MethodElement> methods = classElt.methods;
for (MethodElement method in methods) {
if (memberName == method.name &&
method.isAccessibleIn(_library) &&
!method.isStatic) {
return method;
}
}
List<PropertyAccessorElement> accessors = classElt.accessors;
for (PropertyAccessorElement accessor in accessors) {
if (memberName == accessor.name &&
accessor.isAccessibleIn(_library) &&
!accessor.isStatic) {
return accessor;
}
}
return null;
}
/**
* Record the passed map with the set of all members (methods, getters and setters) in the type
* into the passed map.
*
* @param map some non-`null` map to put the methods and accessors from the passed
* [ClassElement] into
* @param type the type that will be recorded into the passed map
* @param doIncludeAbstract `true` if abstract members will be put into the map
*/
void _recordMapWithClassMembers(
MemberMap map, InterfaceType type, bool doIncludeAbstract) {
List<MethodElement> methods = type.methods;
for (MethodElement method in methods) {
if (method.isAccessibleIn(_library) &&
!method.isStatic &&
(doIncludeAbstract || !method.isAbstract)) {
map.put(method.name, method);
}
}
List<PropertyAccessorElement> accessors = type.accessors;
for (PropertyAccessorElement accessor in accessors) {
if (accessor.isAccessibleIn(_library) &&
!accessor.isStatic &&
(doIncludeAbstract || !accessor.isAbstract)) {
map.put(accessor.name, accessor);
}
}
}
/**
* This method is used to report errors on when they are found computing inheritance information.
* See [ErrorVerifier.checkForInconsistentMethodInheritance] to see where these generated
* error codes are reported back into the analysis engine.
*
* @param classElt the location of the source for which the exception occurred
* @param offset the offset of the location of the error
* @param length the length of the location of the error
* @param errorCode the error code to be associated with this error
* @param arguments the arguments used to build the error message
*/
void _reportError(ClassElement classElt, int offset, int length,
ErrorCode errorCode, List<Object> arguments) {
HashSet<AnalysisError> errorSet = _errorsInClassElement[classElt];
if (errorSet == null) {
errorSet = new HashSet<AnalysisError>();
_errorsInClassElement[classElt] = errorSet;
}
errorSet.add(new AnalysisError(
classElt.source, offset, length, errorCode, arguments));
}
/**
* Given the set of methods defined by classes above [classElt] in the class hierarchy,
* apply the appropriate inheritance rules to determine those methods inherited by or overridden
* by [classElt]. Also report static warnings
* [StaticTypeWarningCode.INCONSISTENT_METHOD_INHERITANCE] and
* [StaticWarningCode.INCONSISTENT_METHOD_INHERITANCE_GETTER_AND_METHOD] if appropriate.
*
* @param classElt the class element to query.
* @param unionMap a mapping from method name to the set of unique (in terms of signature) methods
* defined in superclasses of [classElt].
* @return the inheritance lookup map for [classElt].
*/
MemberMap _resolveInheritanceLookup(ClassElement classElt,
HashMap<String, List<ExecutableElement>> unionMap) {
MemberMap resultMap = new MemberMap();
unionMap.forEach((String key, List<ExecutableElement> list) {
int numOfEltsWithMatchingNames = list.length;
if (numOfEltsWithMatchingNames == 1) {
//
// Example: class A inherits only 1 method named 'm'.
// Since it is the only such method, it is inherited.
// Another example: class A inherits 2 methods named 'm' from 2
// different interfaces, but they both have the same signature, so it is
// the method inherited.
//
resultMap.put(key, list[0]);
} else {
//
// Then numOfEltsWithMatchingNames > 1, check for the warning cases.
//
bool allMethods = true;
bool allSetters = true;
bool allGetters = true;
for (ExecutableElement executableElement in list) {
if (executableElement is PropertyAccessorElement) {
allMethods = false;
if (executableElement.isSetter) {
allGetters = false;
} else {
allSetters = false;
}
} else {
allGetters = false;
allSetters = false;
}
}
//
// If there isn't a mixture of methods with getters, then continue,
// otherwise create a warning.
//
if (allMethods || allGetters || allSetters) {
//
// Compute the element whose type is the subtype of all of the other
// types.
//
List<ExecutableElement> elements = new List.from(list);
List<FunctionType> executableElementTypes =
new List<FunctionType>(numOfEltsWithMatchingNames);
for (int i = 0; i < numOfEltsWithMatchingNames; i++) {
executableElementTypes[i] = elements[i].type;
}
List<int> subtypesOfAllOtherTypesIndexes = new List<int>();
for (int i = 0; i < numOfEltsWithMatchingNames; i++) {
FunctionType subtype = executableElementTypes[i];
if (subtype == null) {
continue;
}
bool subtypeOfAllTypes = true;
TypeSystem typeSystem = _library.context.typeSystem;
for (int j = 0;
j < numOfEltsWithMatchingNames && subtypeOfAllTypes;
j++) {
if (i != j) {
if (!typeSystem.isSubtypeOf(
subtype, executableElementTypes[j])) {
subtypeOfAllTypes = false;
break;
}
}
}
if (subtypeOfAllTypes) {
subtypesOfAllOtherTypesIndexes.add(i);
}
}
//
// The following is split into three cases determined by the number of
// elements in subtypesOfAllOtherTypes
//
if (subtypesOfAllOtherTypesIndexes.length == 1) {
//
// Example: class A inherited only 2 method named 'm'.
// One has the function type '() -> dynamic' and one has the
// function type '([int]) -> dynamic'. Since the second method is a
// subtype of all the others, it is the inherited method.
// Tests: InheritanceManagerTest.
// test_getMapOfMembersInheritedFromInterfaces_union_oneSubtype_*
//
resultMap.put(key, elements[subtypesOfAllOtherTypesIndexes[0]]);
} else {
if (subtypesOfAllOtherTypesIndexes.isEmpty) {
//
// Determine if the current class has a method or accessor with
// the member name, if it does then then this class does not
// "inherit" from any of the supertypes. See issue 16134.
//
bool classHasMember = false;
if (allMethods) {
classHasMember = classElt.getMethod(key) != null;
} else {
List<PropertyAccessorElement> accessors = classElt.accessors;
for (int i = 0; i < accessors.length; i++) {
if (accessors[i].name == key) {
classHasMember = true;
}
}
}
//
// Example: class A inherited only 2 method named 'm'.
// One has the function type '() -> int' and one has the function
// type '() -> String'. Since neither is a subtype of the other,
// we create a warning, and have this class inherit nothing.
//
if (!classHasMember) {
String firstTwoFuntionTypesStr =
"${executableElementTypes[0]}, ${executableElementTypes[1]}";
_reportError(
classElt,
classElt.nameOffset,
classElt.nameLength,
StaticTypeWarningCode.INCONSISTENT_METHOD_INHERITANCE,
[key, firstTwoFuntionTypesStr]);
}
} else {
//
// Example: class A inherits 2 methods named 'm'.
// One has the function type '(int) -> dynamic' and one has the
// function type '(num) -> dynamic'. Since they are both a subtype
// of the other, a synthetic function '(dynamic) -> dynamic' is
// inherited.
// Tests: test_getMapOfMembersInheritedFromInterfaces_
// union_multipleSubtypes_*
//
List<ExecutableElement> elementArrayToMerge =
new List<ExecutableElement>(
subtypesOfAllOtherTypesIndexes.length);
for (int i = 0; i < elementArrayToMerge.length; i++) {
elementArrayToMerge[i] =
elements[subtypesOfAllOtherTypesIndexes[i]];
}
ExecutableElement mergedExecutableElement =
_computeMergedExecutableElement(elementArrayToMerge);
resultMap.put(key, mergedExecutableElement);
}
}
} else {
_reportError(
classElt,
classElt.nameOffset,
classElt.nameLength,
StaticWarningCode
.INCONSISTENT_METHOD_INHERITANCE_GETTER_AND_METHOD,
[key]);
}
}
});
return resultMap;
}
/**
* Loop through all of the members in some [MemberMap], performing type parameter
* substitutions using a passed supertype.
*
* @param superType the supertype to substitute into the members of the [MemberMap]
* @param map the MemberMap to perform the substitutions on
*/
void _substituteTypeParametersDownHierarchy(
InterfaceType superType, MemberMap map) {
for (int i = 0; i < map.size; i++) {
ExecutableElement executableElement = map.getValue(i);
if (executableElement is MethodMember) {
executableElement =
MethodMember.from(executableElement as MethodMember, superType);
map.setValue(i, executableElement);
} else if (executableElement is PropertyAccessorMember) {
executableElement = PropertyAccessorMember.from(
executableElement as PropertyAccessorMember, superType);
map.setValue(i, executableElement);
}
}
}
/**
* Union all of the [lookupMaps] together into a single map, grouping the ExecutableElements
* into a list where none of the elements are equal where equality is determined by having equal
* function types. (We also take note too of the kind of the element: ()->int and () -> int may
* not be equal if one is a getter and the other is a method.)
*
* @param lookupMaps the maps to be unioned together.
* @return the resulting union map.
*/
HashMap<String, List<ExecutableElement>> _unionInterfaceLookupMaps(
List<MemberMap> lookupMaps) {
HashMap<String, List<ExecutableElement>> unionMap =
new HashMap<String, List<ExecutableElement>>();
for (MemberMap lookupMap in lookupMaps) {
int lookupMapSize = lookupMap.size;
for (int i = 0; i < lookupMapSize; i++) {
// Get the string key, if null, break.
String key = lookupMap.getKey(i);
if (key == null) {
break;
}
// Get the list value out of the unionMap
List<ExecutableElement> list = unionMap[key];
// If we haven't created such a map for this key yet, do create it and
// put the list entry into the unionMap.
if (list == null) {
list = new List<ExecutableElement>();
unionMap[key] = list;
}
// Fetch the entry out of this lookupMap
ExecutableElement newExecutableElementEntry = lookupMap.getValue(i);
if (list.isEmpty) {
// If the list is empty, just the new value
list.add(newExecutableElementEntry);
} else {
// Otherwise, only add the newExecutableElementEntry if it isn't
// already in the list, this covers situation where a class inherits
// two methods (or two getters) that are identical.
bool alreadyInList = false;
bool isMethod1 = newExecutableElementEntry is MethodElement;
for (ExecutableElement executableElementInList in list) {
bool isMethod2 = executableElementInList is MethodElement;
if (isMethod1 == isMethod2 &&
executableElementInList.type ==
newExecutableElementEntry.type) {
alreadyInList = true;
break;
}
}
if (!alreadyInList) {
list.add(newExecutableElementEntry);
}
}
}
}
return unionMap;
}
/**
* Given some array of [ExecutableElement]s, this method creates a synthetic element as
* described in 8.1.1:
*
* Let <i>numberOfPositionals</i>(<i>f</i>) denote the number of positional parameters of a
* function <i>f</i>, and let <i>numberOfRequiredParams</i>(<i>f</i>) denote the number of
* required parameters of a function <i>f</i>. Furthermore, let <i>s</i> denote the set of all
* named parameters of the <i>m<sub>1</sub>, &hellip;, m<sub>k</sub></i>. Then let
* * <i>h = max(numberOfPositionals(m<sub>i</sub>)),</i>
* * <i>r = min(numberOfRequiredParams(m<sub>i</sub>)), for all <i>i</i>, 1 <= i <= k.</i>
* Then <i>I</i> has a method named <i>n</i>, with <i>r</i> required parameters of type
* <b>dynamic</b>, <i>h</i> positional parameters of type <b>dynamic</b>, named parameters
* <i>s</i> of type <b>dynamic</b> and return type <b>dynamic</b>.
*
*/
static ExecutableElement _computeMergedExecutableElement(
List<ExecutableElement> elementArrayToMerge) {
int h = _getNumOfPositionalParameters(elementArrayToMerge[0]);
int r = _getNumOfRequiredParameters(elementArrayToMerge[0]);
Set<String> namedParametersList = new HashSet<String>();
for (int i = 1; i < elementArrayToMerge.length; i++) {
ExecutableElement element = elementArrayToMerge[i];
int numOfPositionalParams = _getNumOfPositionalParameters(element);
if (h < numOfPositionalParams) {
h = numOfPositionalParams;
}
int numOfRequiredParams = _getNumOfRequiredParameters(element);
if (r > numOfRequiredParams) {
r = numOfRequiredParams;
}
namedParametersList.addAll(_getNamedParameterNames(element));
}
return _createSyntheticExecutableElement(
elementArrayToMerge,
elementArrayToMerge[0].displayName,
r,
h - r,
new List.from(namedParametersList));
}
/**
* Used by [computeMergedExecutableElement] to actually create the
* synthetic element.
*
* @param elementArrayToMerge the array used to create the synthetic element
* @param name the name of the method, getter or setter
* @param numOfRequiredParameters the number of required parameters
* @param numOfPositionalParameters the number of positional parameters
* @param namedParameters the list of [String]s that are the named parameters
* @return the created synthetic element
*/
static ExecutableElement _createSyntheticExecutableElement(
List<ExecutableElement> elementArrayToMerge,
String name,
int numOfRequiredParameters,
int numOfPositionalParameters,
List<String> namedParameters) {
DynamicTypeImpl dynamicType = DynamicTypeImpl.instance;
SimpleIdentifier nameIdentifier =
new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, 0));
ExecutableElementImpl executable;
if (elementArrayToMerge[0] is MethodElement) {
MultiplyInheritedMethodElementImpl unionedMethod =
new MultiplyInheritedMethodElementImpl(nameIdentifier);
unionedMethod.inheritedElements = elementArrayToMerge;
executable = unionedMethod;
} else {
MultiplyInheritedPropertyAccessorElementImpl unionedPropertyAccessor =
new MultiplyInheritedPropertyAccessorElementImpl(nameIdentifier);
unionedPropertyAccessor.getter =
(elementArrayToMerge[0] as PropertyAccessorElement).isGetter;
unionedPropertyAccessor.setter =
(elementArrayToMerge[0] as PropertyAccessorElement).isSetter;
unionedPropertyAccessor.inheritedElements = elementArrayToMerge;
executable = unionedPropertyAccessor;
}
int numOfParameters = numOfRequiredParameters +
numOfPositionalParameters +
namedParameters.length;
List<ParameterElement> parameters =
new List<ParameterElement>(numOfParameters);
int i = 0;
for (int j = 0; j < numOfRequiredParameters; j++, i++) {
ParameterElementImpl parameter = new ParameterElementImpl("", 0);
parameter.type = dynamicType;
parameter.parameterKind = ParameterKind.REQUIRED;
parameters[i] = parameter;
}
for (int k = 0; k < numOfPositionalParameters; k++, i++) {
ParameterElementImpl parameter = new ParameterElementImpl("", 0);
parameter.type = dynamicType;
parameter.parameterKind = ParameterKind.POSITIONAL;
parameters[i] = parameter;
}
for (int m = 0; m < namedParameters.length; m++, i++) {
ParameterElementImpl parameter =
new ParameterElementImpl(namedParameters[m], 0);
parameter.type = dynamicType;
parameter.parameterKind = ParameterKind.NAMED;
parameters[i] = parameter;
}
executable.returnType = dynamicType;
executable.parameters = parameters;
FunctionTypeImpl methodType = new FunctionTypeImpl(executable);
executable.type = methodType;
return executable;
}
/**
* Given some [ExecutableElement], return the list of named parameters.
*/
static List<String> _getNamedParameterNames(
ExecutableElement executableElement) {
List<String> namedParameterNames = new List<String>();
List<ParameterElement> parameters = executableElement.parameters;
for (int i = 0; i < parameters.length; i++) {
ParameterElement parameterElement = parameters[i];
if (parameterElement.parameterKind == ParameterKind.NAMED) {
namedParameterNames.add(parameterElement.name);
}
}
return namedParameterNames;
}
/**
* Given some [ExecutableElement] return the number of parameters of the specified kind.
*/
static int _getNumOfParameters(
ExecutableElement executableElement, ParameterKind parameterKind) {
int parameterCount = 0;
List<ParameterElement> parameters = executableElement.parameters;
for (int i = 0; i < parameters.length; i++) {
ParameterElement parameterElement = parameters[i];
if (parameterElement.parameterKind == parameterKind) {
parameterCount++;
}
}
return parameterCount;
}
/**
* Given some [ExecutableElement] return the number of positional parameters.
*
* Note: by positional we mean [ParameterKind.REQUIRED] or [ParameterKind.POSITIONAL].
*/
static int _getNumOfPositionalParameters(
ExecutableElement executableElement) =>
_getNumOfParameters(executableElement, ParameterKind.REQUIRED) +
_getNumOfParameters(executableElement, ParameterKind.POSITIONAL);
/**
* Given some [ExecutableElement] return the number of required parameters.
*/
static int _getNumOfRequiredParameters(ExecutableElement executableElement) =>
_getNumOfParameters(executableElement, ParameterKind.REQUIRED);
/**
* Given some [ExecutableElement] returns `true` if it is an abstract member of a
* class.
*
* @param executableElement some [ExecutableElement] to evaluate
* @return `true` if the given element is an abstract member of a class
*/
static bool _isAbstract(ExecutableElement executableElement) {
if (executableElement is MethodElement) {
return executableElement.isAbstract;
} else if (executableElement is PropertyAccessorElement) {
return executableElement.isAbstract;
}
return false;
}
}
/**
* This enum holds one of four states of a field initialization state through a constructor
* signature, not initialized, initialized in the field declaration, initialized in the field
* formal, and finally, initialized in the initializers list.
*/
class INIT_STATE extends Enum<INIT_STATE> {
static const INIT_STATE NOT_INIT = const INIT_STATE('NOT_INIT', 0);
static const INIT_STATE INIT_IN_DECLARATION =
const INIT_STATE('INIT_IN_DECLARATION', 1);
static const INIT_STATE INIT_IN_FIELD_FORMAL =
const INIT_STATE('INIT_IN_FIELD_FORMAL', 2);
static const INIT_STATE INIT_IN_INITIALIZERS =
const INIT_STATE('INIT_IN_INITIALIZERS', 3);
static const List<INIT_STATE> values = const [
NOT_INIT,
INIT_IN_DECLARATION,
INIT_IN_FIELD_FORMAL,
INIT_IN_INITIALIZERS
];
const INIT_STATE(String name, int ordinal) : super(name, ordinal);
}
/**
* Instances of the class `LabelScope` represent a scope in which a single label is defined.
*/
class LabelScope {
/**
* The label scope enclosing this label scope.
*/
final LabelScope _outerScope;
/**
* The label defined in this scope.
*/
final String _label;
/**
* The element to which the label resolves.
*/
final LabelElement element;
/**
* The AST node to which the label resolves.
*/
final AstNode node;
/**
* Initialize a newly created scope to represent the label [_label].
* [_outerScope] is the scope enclosing the new label scope. [node] is the
* AST node the label resolves to. [element] is the element the label
* resolves to.
*/
LabelScope(this._outerScope, this._label, this.node, this.element);
/**
* Return the LabelScope which defines [targetLabel], or `null` if it is not
* defined in this scope.
*/
LabelScope lookup(String targetLabel) {
if (_label == targetLabel) {
return this;
} else if (_outerScope != null) {
return _outerScope.lookup(targetLabel);
} else {
return null;
}
}
}
/**
* Instances of the class `LibraryImportScope` represent the scope containing all of the names
* available from imported libraries.
*/
class LibraryImportScope extends Scope {
/**
* The element representing the library in which this scope is enclosed.
*/
final LibraryElement _definingLibrary;
/**
* The listener that is to be informed when an error is encountered.
*/
final AnalysisErrorListener errorListener;
/**
* A list of the namespaces representing the names that are available in this scope from imported
* libraries.
*/
List<Namespace> _importedNamespaces;
/**
* Initialize a newly created scope representing the names imported into the given library.
*
* @param definingLibrary the element representing the library that imports the names defined in
* this scope
* @param errorListener the listener that is to be informed when an error is encountered
*/
LibraryImportScope(this._definingLibrary, this.errorListener) {
_createImportedNamespaces();
}
@override
void define(Element element) {
if (!Scope.isPrivateName(element.displayName)) {
super.define(element);
}
}
@override
Source getSource(AstNode node) {
Source source = super.getSource(node);
if (source == null) {
source = _definingLibrary.definingCompilationUnit.source;
}
return source;
}
@override
Element internalLookup(
Identifier identifier, String name, LibraryElement referencingLibrary) {
Element foundElement = localLookup(name, referencingLibrary);
if (foundElement != null) {
return foundElement;
}
for (int i = 0; i < _importedNamespaces.length; i++) {
Namespace nameSpace = _importedNamespaces[i];
Element element = nameSpace.get(name);
if (element != null) {
if (foundElement == null) {
foundElement = element;
} else if (!identical(foundElement, element)) {
foundElement = MultiplyDefinedElementImpl.fromElements(
_definingLibrary.context, foundElement, element);
}
}
}
if (foundElement is MultiplyDefinedElementImpl) {
foundElement = _removeSdkElements(
identifier, name, foundElement as MultiplyDefinedElementImpl);
}
if (foundElement is MultiplyDefinedElementImpl) {
String foundEltName = foundElement.displayName;
List<Element> conflictingMembers = foundElement.conflictingElements;
int count = conflictingMembers.length;
List<String> libraryNames = new List<String>(count);
for (int i = 0; i < count; i++) {
libraryNames[i] = _getLibraryName(conflictingMembers[i]);
}
libraryNames.sort();
errorListener.onError(new AnalysisError(
getSource(identifier),
identifier.offset,
identifier.length,
StaticWarningCode.AMBIGUOUS_IMPORT, [
foundEltName,
StringUtilities.printListOfQuotedNames(libraryNames)
]));
return foundElement;
}
if (foundElement != null) {
defineNameWithoutChecking(name, foundElement);
}
return foundElement;
}
/**
* Create all of the namespaces associated with the libraries imported into this library. The
* names are not added to this scope, but are stored for later reference.
*
* @param definingLibrary the element representing the library that imports the libraries for
* which namespaces will be created
*/
void _createImportedNamespaces() {
NamespaceBuilder builder = new NamespaceBuilder();
List<ImportElement> imports = _definingLibrary.imports;
int count = imports.length;
_importedNamespaces = new List<Namespace>(count);
for (int i = 0; i < count; i++) {
_importedNamespaces[i] =
builder.createImportNamespaceForDirective(imports[i]);
}
}
/**
* Returns the name of the library that defines given element.
*
* @param element the element to get library name
* @return the name of the library that defines given element
*/
String _getLibraryName(Element element) {
if (element == null) {
return StringUtilities.EMPTY;
}
LibraryElement library = element.library;
if (library == null) {
return StringUtilities.EMPTY;
}
List<ImportElement> imports = _definingLibrary.imports;
int count = imports.length;
for (int i = 0; i < count; i++) {
if (identical(imports[i].importedLibrary, library)) {
return library.definingCompilationUnit.displayName;
}
}
List<String> indirectSources = new List<String>();
for (int i = 0; i < count; i++) {
LibraryElement importedLibrary = imports[i].importedLibrary;
if (importedLibrary != null) {
for (LibraryElement exportedLibrary
in importedLibrary.exportedLibraries) {
if (identical(exportedLibrary, library)) {
indirectSources
.add(importedLibrary.definingCompilationUnit.displayName);
}
}
}
}
int indirectCount = indirectSources.length;
StringBuffer buffer = new StringBuffer();
buffer.write(library.definingCompilationUnit.displayName);
if (indirectCount > 0) {
buffer.write(" (via ");
if (indirectCount > 1) {
indirectSources.sort();
buffer.write(StringUtilities.printListOfQuotedNames(indirectSources));
} else {
buffer.write(indirectSources[0]);
}
buffer.write(")");
}
return buffer.toString();
}
/**
* Given a collection of elements (captured by the [foundElement]) that the
* [identifier] (with the given [name]) resolved to, remove from the list all
* of the names defined in the SDK and return the element(s) that remain.
*/
Element _removeSdkElements(Identifier identifier, String name,
MultiplyDefinedElementImpl foundElement) {
List<Element> conflictingElements = foundElement.conflictingElements;
List<Element> nonSdkElements = new List<Element>();
Element sdkElement = null;
for (Element member in conflictingElements) {
if (member.library.isInSdk) {
sdkElement = member;
} else {
nonSdkElements.add(member);
}
}
if (sdkElement != null && nonSdkElements.length > 0) {
String sdkLibName = _getLibraryName(sdkElement);
String otherLibName = _getLibraryName(nonSdkElements[0]);
errorListener.onError(new AnalysisError(
getSource(identifier),
identifier.offset,
identifier.length,
StaticWarningCode.CONFLICTING_DART_IMPORT,
[name, sdkLibName, otherLibName]));
}
if (nonSdkElements.length == conflictingElements.length) {
// None of the members were removed
return foundElement;
} else if (nonSdkElements.length == 1) {
// All but one member was removed
return nonSdkElements[0];
} else if (nonSdkElements.length == 0) {
// All members were removed
AnalysisEngine.instance.logger
.logInformation("Multiply defined SDK element: $foundElement");
return foundElement;
}
return new MultiplyDefinedElementImpl(
_definingLibrary.context, nonSdkElements);
}
}
/**
* Instances of the class `LibraryResolver` are used to resolve one or more mutually dependent
* libraries within a single context.
*/
/**
* Instances of the class `LibraryResolver` are used to resolve one or more mutually dependent
* libraries within a single context.
*/
/**
* Instances of the class `LibraryScope` implement a scope containing all of the names defined
* in a given library.
*/
class LibraryScope extends EnclosedScope {
/**
* Initialize a newly created scope representing the names defined in the given library.
*
* @param definingLibrary the element representing the library represented by this scope
* @param errorListener the listener that is to be informed when an error is encountered
*/
LibraryScope(
LibraryElement definingLibrary, AnalysisErrorListener errorListener)
: super(new LibraryImportScope(definingLibrary, errorListener)) {
_defineTopLevelNames(definingLibrary);
}
@override
AnalysisError getErrorForDuplicate(Element existing, Element duplicate) {
if (existing is PrefixElement) {
// TODO(scheglov) consider providing actual 'nameOffset' from the
// synthetic accessor
int offset = duplicate.nameOffset;
if (duplicate is PropertyAccessorElement) {
PropertyAccessorElement accessor = duplicate;
if (accessor.isSynthetic) {
offset = accessor.variable.nameOffset;
}
}
return new AnalysisError(
duplicate.source,
offset,
duplicate.nameLength,
CompileTimeErrorCode.PREFIX_COLLIDES_WITH_TOP_LEVEL_MEMBER,
[existing.displayName]);
}
return super.getErrorForDuplicate(existing, duplicate);
}
/**
* Add to this scope all of the public top-level names that are defined in the given compilation
* unit.
*
* @param compilationUnit the compilation unit defining the top-level names to be added to this
* scope
*/
void _defineLocalNames(CompilationUnitElement compilationUnit) {
for (PropertyAccessorElement element in compilationUnit.accessors) {
define(element);
}
for (ClassElement element in compilationUnit.enums) {
define(element);
}
for (FunctionElement element in compilationUnit.functions) {
define(element);
}
for (FunctionTypeAliasElement element
in compilationUnit.functionTypeAliases) {
define(element);
}
for (ClassElement element in compilationUnit.types) {
define(element);
}
}
/**
* Add to this scope all of the names that are explicitly defined in the given library.
*
* @param definingLibrary the element representing the library that defines the names in this
* scope
*/
void _defineTopLevelNames(LibraryElement definingLibrary) {
for (PrefixElement prefix in definingLibrary.prefixes) {
define(prefix);
}
_defineLocalNames(definingLibrary.definingCompilationUnit);
for (CompilationUnitElement compilationUnit in definingLibrary.parts) {
_defineLocalNames(compilationUnit);
}
}
}
/**
* This class is used to replace uses of `HashMap<String, ExecutableElement>`
* which are not as performant as this class.
*/
class MemberMap {
/**
* The current size of this map.
*/
int _size = 0;
/**
* The array of keys.
*/
List<String> _keys;
/**
* The array of ExecutableElement values.
*/
List<ExecutableElement> _values;
/**
* Initialize a newly created member map to have the given [initialCapacity].
* The map will grow if needed.
*/
MemberMap([int initialCapacity = 10]) {
_initArrays(initialCapacity);
}
/**
* Initialize a newly created member map to contain the same members as the
* given [memberMap].
*/
MemberMap.from(MemberMap memberMap) {
_initArrays(memberMap._size + 5);
for (int i = 0; i < memberMap._size; i++) {
_keys[i] = memberMap._keys[i];
_values[i] = memberMap._values[i];
}
_size = memberMap._size;
}
/**
* The size of the map.
*
* @return the size of the map.
*/
int get size => _size;
/**
* Given some key, return the ExecutableElement value from the map, if the key does not exist in
* the map, `null` is returned.
*
* @param key some key to look up in the map
* @return the associated ExecutableElement value from the map, if the key does not exist in the
* map, `null` is returned
*/
ExecutableElement get(String key) {
for (int i = 0; i < _size; i++) {
if (_keys[i] != null && _keys[i] == key) {
return _values[i];
}
}
return null;
}
/**
* Get and return the key at the specified location. If the key/value pair has been removed from
* the set, then `null` is returned.
*
* @param i some non-zero value less than size
* @return the key at the passed index
* @throw ArrayIndexOutOfBoundsException this exception is thrown if the passed index is less than
* zero or greater than or equal to the capacity of the arrays
*/
String getKey(int i) => _keys[i];
/**
* Get and return the ExecutableElement at the specified location. If the key/value pair has been
* removed from the set, then then `null` is returned.
*
* @param i some non-zero value less than size
* @return the key at the passed index
* @throw ArrayIndexOutOfBoundsException this exception is thrown if the passed index is less than
* zero or greater than or equal to the capacity of the arrays
*/
ExecutableElement getValue(int i) => _values[i];
/**
* Given some key/value pair, store the pair in the map. If the key exists already, then the new
* value overrides the old value.
*
* @param key the key to store in the map
* @param value the ExecutableElement value to store in the map
*/
void put(String key, ExecutableElement value) {
// If we already have a value with this key, override the value
for (int i = 0; i < _size; i++) {
if (_keys[i] != null && _keys[i] == key) {
_values[i] = value;
return;
}
}
// If needed, double the size of our arrays and copy values over in both
// arrays
if (_size == _keys.length) {
int newArrayLength = _size * 2;
List<String> keys_new_array = new List<String>(newArrayLength);
List<ExecutableElement> values_new_array =
new List<ExecutableElement>(newArrayLength);
for (int i = 0; i < _size; i++) {
keys_new_array[i] = _keys[i];
}
for (int i = 0; i < _size; i++) {
values_new_array[i] = _values[i];
}
_keys = keys_new_array;
_values = values_new_array;
}
// Put new value at end of array
_keys[_size] = key;
_values[_size] = value;
_size++;
}
/**
* Given some [String] key, this method replaces the associated key and value pair with
* `null`. The size is not decremented with this call, instead it is expected that the users
* check for `null`.
*
* @param key the key of the key/value pair to remove from the map
*/
void remove(String key) {
for (int i = 0; i < _size; i++) {
if (_keys[i] == key) {
_keys[i] = null;
_values[i] = null;
return;
}
}
}
/**
* Sets the ExecutableElement at the specified location.
*
* @param i some non-zero value less than size
* @param value the ExecutableElement value to store in the map
*/
void setValue(int i, ExecutableElement value) {
_values[i] = value;
}
/**
* Initializes [keys] and [values].
*/
void _initArrays(int initialCapacity) {
_keys = new List<String>(initialCapacity);
_values = new List<ExecutableElement>(initialCapacity);
}
}
/**
* Instances of the class `Namespace` implement a mapping of identifiers to the elements
* represented by those identifiers. Namespaces are the building blocks for scopes.
*/
class Namespace {
/**
* An empty namespace.
*/
static Namespace EMPTY = new Namespace(new HashMap<String, Element>());
/**
* A table mapping names that are defined in this namespace to the element representing the thing
* declared with that name.
*/
final HashMap<String, Element> _definedNames;
/**
* Initialize a newly created namespace to have the given defined names.
*
* @param definedNames the mapping from names that are defined in this namespace to the
* corresponding elements
*/
Namespace(this._definedNames);
/**
* Return a table containing the same mappings as those defined by this namespace.
*
* @return a table containing the same mappings as those defined by this namespace
*/
Map<String, Element> get definedNames => _definedNames;
/**
* Return the element in this namespace that is available to the containing scope using the given
* name.
*
* @param name the name used to reference the
* @return the element represented by the given identifier
*/
Element get(String name) => _definedNames[name];
}
/**
* Instances of the class `NamespaceBuilder` are used to build a `Namespace`. Namespace
* builders are thread-safe and re-usable.
*/
class NamespaceBuilder {
/**
* Create a namespace representing the export namespace of the given [ExportElement].
*
* @param element the export element whose export namespace is to be created
* @return the export namespace that was created
*/
Namespace createExportNamespaceForDirective(ExportElement element) {
LibraryElement exportedLibrary = element.exportedLibrary;
if (exportedLibrary == null) {
//
// The exported library will be null if the URI does not reference a valid
// library.
//
return Namespace.EMPTY;
}
HashMap<String, Element> definedNames =
_createExportMapping(exportedLibrary, new HashSet<LibraryElement>());
definedNames = _applyCombinators(definedNames, element.combinators);
return new Namespace(definedNames);
}
/**
* Create a namespace representing the export namespace of the given library.
*
* @param library the library whose export namespace is to be created
* @return the export namespace that was created
*/
Namespace createExportNamespaceForLibrary(LibraryElement library) =>
new Namespace(
_createExportMapping(library, new HashSet<LibraryElement>()));
/**
* Create a namespace representing the import namespace of the given library.
*
* @param library the library whose import namespace is to be created
* @return the import namespace that was created
*/
Namespace createImportNamespaceForDirective(ImportElement element) {
LibraryElement importedLibrary = element.importedLibrary;
if (importedLibrary == null) {
//
// The imported library will be null if the URI does not reference a valid
// library.
//
return Namespace.EMPTY;
}
HashMap<String, Element> definedNames =
_createExportMapping(importedLibrary, new HashSet<LibraryElement>());
definedNames = _applyCombinators(definedNames, element.combinators);
definedNames = _applyPrefix(definedNames, element.prefix);
return new Namespace(definedNames);
}
/**
* Create a namespace representing the public namespace of the given library.
*
* @param library the library whose public namespace is to be created
* @return the public namespace that was created
*/
Namespace createPublicNamespaceForLibrary(LibraryElement library) {
HashMap<String, Element> definedNames = new HashMap<String, Element>();
_addPublicNames(definedNames, library.definingCompilationUnit);
for (CompilationUnitElement compilationUnit in library.parts) {
_addPublicNames(definedNames, compilationUnit);
}
return new Namespace(definedNames);
}
/**
* Add all of the names in the given namespace to the given mapping table.
*
* @param definedNames the mapping table to which the names in the given namespace are to be added
* @param namespace the namespace containing the names to be added to this namespace
*/
void _addAllFromNamespace(
Map<String, Element> definedNames, Namespace namespace) {
if (namespace != null) {
definedNames.addAll(namespace.definedNames);
}
}
/**
* Add the given element to the given mapping table if it has a publicly visible name.
*
* @param definedNames the mapping table to which the public name is to be added
* @param element the element to be added
*/
void _addIfPublic(Map<String, Element> definedNames, Element element) {
String name = element.name;
if (name != null && !Scope.isPrivateName(name)) {
definedNames[name] = element;
}
}
/**
* Add to the given mapping table all of the public top-level names that are defined in the given
* compilation unit.
*
* @param definedNames the mapping table to which the public names are to be added
* @param compilationUnit the compilation unit defining the top-level names to be added to this
* namespace
*/
void _addPublicNames(Map<String, Element> definedNames,
CompilationUnitElement compilationUnit) {
for (PropertyAccessorElement element in compilationUnit.accessors) {
_addIfPublic(definedNames, element);
}
for (ClassElement element in compilationUnit.enums) {
_addIfPublic(definedNames, element);
}
for (FunctionElement element in compilationUnit.functions) {
_addIfPublic(definedNames, element);
}
for (FunctionTypeAliasElement element
in compilationUnit.functionTypeAliases) {
_addIfPublic(definedNames, element);
}
for (ClassElement element in compilationUnit.types) {
_addIfPublic(definedNames, element);
}
}
/**
* Apply the given combinators to all of the names in the given mapping table.
*
* @param definedNames the mapping table to which the namespace operations are to be applied
* @param combinators the combinators to be applied
*/
HashMap<String, Element> _applyCombinators(
HashMap<String, Element> definedNames,
List<NamespaceCombinator> combinators) {
for (NamespaceCombinator combinator in combinators) {
if (combinator is HideElementCombinator) {
definedNames = _hide(definedNames, combinator.hiddenNames);
} else if (combinator is ShowElementCombinator) {
definedNames = _show(definedNames, combinator.shownNames);
} else {
// Internal error.
AnalysisEngine.instance.logger
.logError("Unknown type of combinator: ${combinator.runtimeType}");
}
}
return definedNames;
}
/**
* Apply the given prefix to all of the names in the table of defined names.
*
* @param definedNames the names that were defined before this operation
* @param prefixElement the element defining the prefix to be added to the names
*/
HashMap<String, Element> _applyPrefix(
HashMap<String, Element> definedNames, PrefixElement prefixElement) {
if (prefixElement != null) {
String prefix = prefixElement.name;
HashMap<String, Element> newNames = new HashMap<String, Element>();
definedNames.forEach((String name, Element element) {
newNames["$prefix.$name"] = element;
});
return newNames;
} else {
return definedNames;
}
}
/**
* Create a mapping table representing the export namespace of the given library.
*
* @param library the library whose public namespace is to be created
* @param visitedElements a set of libraries that do not need to be visited when processing the
* export directives of the given library because all of the names defined by them will
* be added by another library
* @return the mapping table that was created
*/
HashMap<String, Element> _createExportMapping(
LibraryElement library, HashSet<LibraryElement> visitedElements) {
// Check if the export namespace has been already computed.
{
Namespace exportNamespace = library.exportNamespace;
if (exportNamespace != null) {
return exportNamespace.definedNames;
}
}
// TODO(scheglov) Remove this after switching to the new task model.
visitedElements.add(library);
try {
HashMap<String, Element> definedNames = new HashMap<String, Element>();
for (ExportElement element in library.exports) {
LibraryElement exportedLibrary = element.exportedLibrary;
if (exportedLibrary != null &&
!visitedElements.contains(exportedLibrary)) {
//
// The exported library will be null if the URI does not reference a
// valid library.
//
HashMap<String, Element> exportedNames =
_createExportMapping(exportedLibrary, visitedElements);
exportedNames = _applyCombinators(exportedNames, element.combinators);
definedNames.addAll(exportedNames);
}
}
_addAllFromNamespace(
definedNames,
(library.context as InternalAnalysisContext)
.getPublicNamespace(library));
return definedNames;
} finally {
visitedElements.remove(library);
}
}
/**
* Return a new map of names which has all the names from [definedNames]
* with exception of [hiddenNames].
*/
Map<String, Element> _hide(
HashMap<String, Element> definedNames, List<String> hiddenNames) {
HashMap<String, Element> newNames =
new HashMap<String, Element>.from(definedNames);
for (String name in hiddenNames) {
newNames.remove(name);
newNames.remove("$name=");
}
return newNames;
}
/**
* Return a new map of names which has only [shownNames] from [definedNames].
*/
HashMap<String, Element> _show(
HashMap<String, Element> definedNames, List<String> shownNames) {
HashMap<String, Element> newNames = new HashMap<String, Element>();
for (String name in shownNames) {
Element element = definedNames[name];
if (element != null) {
newNames[name] = element;
}
String setterName = "$name=";
element = definedNames[setterName];
if (element != null) {
newNames[setterName] = element;
}
}
return newNames;
}
}
/**
* Instances of the class `OverrideVerifier` visit all of the declarations in a compilation
* unit to verify that if they have an override annotation it is being used correctly.
*/
class OverrideVerifier extends RecursiveAstVisitor<Object> {
/**
* The error reporter used to report errors.
*/
final ErrorReporter _errorReporter;
/**
* The inheritance manager used to find overridden methods.
*/
final InheritanceManager _manager;
/**
* Initialize a newly created verifier to look for inappropriate uses of the override annotation.
*
* @param errorReporter the error reporter used to report errors
* @param manager the inheritance manager used to find overridden methods
*/
OverrideVerifier(this._errorReporter, this._manager);
@override
Object visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement element = node.element;
if (_isOverride(element)) {
if (_getOverriddenMember(element) == null) {
if (element is MethodElement) {
_errorReporter.reportErrorForNode(
HintCode.OVERRIDE_ON_NON_OVERRIDING_METHOD, node.name);
} else if (element is PropertyAccessorElement) {
if (element.isGetter) {
_errorReporter.reportErrorForNode(
HintCode.OVERRIDE_ON_NON_OVERRIDING_GETTER, node.name);
} else {
_errorReporter.reportErrorForNode(
HintCode.OVERRIDE_ON_NON_OVERRIDING_SETTER, node.name);
}
}
}
}
return super.visitMethodDeclaration(node);
}
/**
* Return the member that overrides the given member.
*
* @param member the member that overrides the returned member
* @return the member that overrides the given member
*/
ExecutableElement _getOverriddenMember(ExecutableElement member) {
LibraryElement library = member.library;
if (library == null) {
return null;
}
ClassElement classElement =
member.getAncestor((element) => element is ClassElement);
if (classElement == null) {
return null;
}
return _manager.lookupInheritance(classElement, member.name);
}
/**
* Return `true` if the given element has an override annotation associated with it.
*
* @param element the element being tested
* @return `true` if the element has an override annotation associated with it
*/
bool _isOverride(Element element) => element != null && element.isOverride;
}
/**
* An AST visitor that is used to resolve the some of the nodes within a single
* compilation unit. The nodes that are skipped are those that are within
* function bodies.
*/
class PartialResolverVisitor extends ResolverVisitor {
/**
* The static variables and fields that have an initializer. These are the
* variables that need to be re-resolved after static variables have their
* types inferred. A subset of these variables are those whose types should
* be inferred.
*/
final List<VariableElement> staticVariables = <VariableElement>[];
/**
* The static and instance variables and fields that have an initializer.
* These are the variables whose types might be propagated.
*/
final List<VariableElement> propagableVariables = <VariableElement>[];
/**
* Initialize a newly created visitor to resolve the nodes in an AST node.
*
* The [definingLibrary] is the element for the library containing the node
* being visited. The [source] is the source representing the compilation unit
* containing the node being visited. The [typeProvider] is the object used to
* access the types from the core library. The [errorListener] is the error
* listener that will be informed of any errors that are found during
* resolution. The [nameScope] is the scope used to resolve identifiers in the
* node that will first be visited. If `null` or unspecified, a new
* [LibraryScope] will be created based on [definingLibrary] and
* [typeProvider]. The [inheritanceManager] is used to perform inheritance
* lookups. If `null` or unspecified, a new [InheritanceManager] will be
* created based on [definingLibrary]. The [typeAnalyzerFactory] is used to
* create the type analyzer. If `null` or unspecified, a type analyzer of
* type [StaticTypeAnalyzer] will be created.
*/
PartialResolverVisitor(LibraryElement definingLibrary, Source source,
TypeProvider typeProvider, AnalysisErrorListener errorListener,
{Scope nameScope})
: super(definingLibrary, source, typeProvider, errorListener,
nameScope: nameScope);
@override
Object visitBlockFunctionBody(BlockFunctionBody node) {
if (_shouldBeSkipped(node)) {
return null;
}
return super.visitBlockFunctionBody(node);
}
@override
Object visitExpressionFunctionBody(ExpressionFunctionBody node) {
if (_shouldBeSkipped(node)) {
return null;
}
return super.visitExpressionFunctionBody(node);
}
@override
Object visitFieldDeclaration(FieldDeclaration node) {
_addPropagableVariables(node.fields.variables);
if (node.isStatic) {
_addStaticVariables(node.fields.variables);
}
return super.visitFieldDeclaration(node);
}
@override
Object visitNode(AstNode node) {
return super.visitNode(node);
}
@override
Object visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
_addPropagableVariables(node.variables.variables);
_addStaticVariables(node.variables.variables);
return super.visitTopLevelVariableDeclaration(node);
}
/**
* Add all of the [variables] with initializers to [propagableVariables].
*/
void _addPropagableVariables(List<VariableDeclaration> variables) {
for (VariableDeclaration variable in variables) {
if (variable.initializer != null) {
VariableElement element = variable.element;
if (element.isConst || element.isFinal) {
propagableVariables.add(element);
}
}
}
}
/**
* Add all of the [variables] with initializers to the list of variables whose
* type can be inferred. Technically, we only infer the types of variables
* that do not have a static type, but all variables with initializers
* potentially need to be re-resolved after inference because they might
* refer to a field whose type was inferred.
*/
void _addStaticVariables(List<VariableDeclaration> variables) {
for (VariableDeclaration variable in variables) {
if (variable.initializer != null) {
staticVariables.add(variable.element);
}
}
}
/**
* Return `true` if the given function body should be skipped because it is
* the body of a top-level function, method or constructor.
*/
bool _shouldBeSkipped(FunctionBody body) {
AstNode parent = body.parent;
if (parent is MethodDeclaration) {
return parent.body == body;
}
if (parent is ConstructorDeclaration) {
return parent.body == body;
}
if (parent is FunctionExpression) {
AstNode parent2 = parent.parent;
if (parent2 is FunctionDeclaration &&
parent2.parent is! FunctionDeclarationStatement) {
return parent.body == body;
}
}
return false;
}
}
/**
* Instances of the class `PubVerifier` traverse an AST structure looking for deviations from
* pub best practices.
*/
class PubVerifier extends RecursiveAstVisitor<Object> {
// static String _PUBSPEC_YAML = "pubspec.yaml";
/**
* The analysis context containing the sources to be analyzed
*/
final AnalysisContext _context;
/**
* The error reporter by which errors will be reported.
*/
final ErrorReporter _errorReporter;
PubVerifier(this._context, this._errorReporter);
@override
Object visitImportDirective(ImportDirective directive) {
return null;
}
// /**
// * This verifies that the passed file import directive is not contained in a source inside a
// * package "lib" directory hierarchy referencing a source outside that package "lib" directory
// * hierarchy.
// *
// * @param uriLiteral the import URL (not `null`)
// * @param path the file path being verified (not `null`)
// * @return `true` if and only if an error code is generated on the passed node
// * See [PubSuggestionCode.FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE].
// */
// bool
// _checkForFileImportInsideLibReferencesFileOutside(StringLiteral uriLiteral,
// String path) {
// Source source = _getSource(uriLiteral);
// String fullName = _getSourceFullName(source);
// if (fullName != null) {
// int pathIndex = 0;
// int fullNameIndex = fullName.length;
// while (pathIndex < path.length &&
// StringUtilities.startsWith3(path, pathIndex, 0x2E, 0x2E, 0x2F)) {
// fullNameIndex = JavaString.lastIndexOf(fullName, '/', fullNameIndex);
// if (fullNameIndex < 4) {
// return false;
// }
// // Check for "/lib" at a specified place in the fullName
// if (StringUtilities.startsWith4(
// fullName,
// fullNameIndex - 4,
// 0x2F,
// 0x6C,
// 0x69,
// 0x62)) {
// String relativePubspecPath =
// path.substring(0, pathIndex + 3) +
// _PUBSPEC_YAML;
// Source pubspecSource =
// _context.sourceFactory.resolveUri(source, relativePubspecPath);
// if (_context.exists(pubspecSource)) {
// // Files inside the lib directory hierarchy should not reference
// // files outside
// _errorReporter.reportErrorForNode(
// HintCode.FILE_IMPORT_INSIDE_LIB_REFERENCES_FILE_OUTSIDE,
// uriLiteral);
// }
// return true;
// }
// pathIndex += 3;
// }
// }
// return false;
// }
// /**
// * This verifies that the passed file import directive is not contained in a source outside a
// * package "lib" directory hierarchy referencing a source inside that package "lib" directory
// * hierarchy.
// *
// * @param uriLiteral the import URL (not `null`)
// * @param path the file path being verified (not `null`)
// * @return `true` if and only if an error code is generated on the passed node
// * See [PubSuggestionCode.FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE].
// */
// bool
// _checkForFileImportOutsideLibReferencesFileInside(StringLiteral uriLiteral,
// String path) {
// if (StringUtilities.startsWith4(path, 0, 0x6C, 0x69, 0x62, 0x2F)) {
// if (_checkForFileImportOutsideLibReferencesFileInsideAtIndex(
// uriLiteral,
// path,
// 0)) {
// return true;
// }
// }
// int pathIndex =
// StringUtilities.indexOf5(path, 0, 0x2F, 0x6C, 0x69, 0x62, 0x2F);
// while (pathIndex != -1) {
// if (_checkForFileImportOutsideLibReferencesFileInsideAtIndex(
// uriLiteral,
// path,
// pathIndex + 1)) {
// return true;
// }
// pathIndex =
// StringUtilities.indexOf5(path, pathIndex + 4, 0x2F, 0x6C, 0x69, 0x62, 0x2F);
// }
// return false;
// }
// bool
// _checkForFileImportOutsideLibReferencesFileInsideAtIndex(StringLiteral uriLiteral,
// String path, int pathIndex) {
// Source source = _getSource(uriLiteral);
// String relativePubspecPath = path.substring(0, pathIndex) + _PUBSPEC_YAML;
// Source pubspecSource =
// _context.sourceFactory.resolveUri(source, relativePubspecPath);
// if (!_context.exists(pubspecSource)) {
// return false;
// }
// String fullName = _getSourceFullName(source);
// if (fullName != null) {
// if (StringUtilities.indexOf5(fullName, 0, 0x2F, 0x6C, 0x69, 0x62, 0x2F) <
// 0) {
// // Files outside the lib directory hierarchy should not reference files
// // inside ... use package: url instead
// _errorReporter.reportErrorForNode(
// HintCode.FILE_IMPORT_OUTSIDE_LIB_REFERENCES_FILE_INSIDE,
// uriLiteral);
// return true;
// }
// }
// return false;
// }
// /**
// * This verifies that the passed package import directive does not contain ".."
// *
// * @param uriLiteral the import URL (not `null`)
// * @param path the path to be validated (not `null`)
// * @return `true` if and only if an error code is generated on the passed node
// * See [PubSuggestionCode.PACKAGE_IMPORT_CONTAINS_DOT_DOT].
// */
// bool _checkForPackageImportContainsDotDot(StringLiteral uriLiteral,
// String path) {
// if (StringUtilities.startsWith3(path, 0, 0x2E, 0x2E, 0x2F) ||
// StringUtilities.indexOf4(path, 0, 0x2F, 0x2E, 0x2E, 0x2F) >= 0) {
// // Package import should not to contain ".."
// _errorReporter.reportErrorForNode(
// HintCode.PACKAGE_IMPORT_CONTAINS_DOT_DOT,
// uriLiteral);
// return true;
// }
// return false;
// }
// /**
// * Answer the source associated with the compilation unit containing the given AST node.
// *
// * @param node the node (not `null`)
// * @return the source or `null` if it could not be determined
// */
// Source _getSource(AstNode node) {
// Source source = null;
// CompilationUnit unit = node.getAncestor((node) => node is CompilationUnit);
// if (unit != null) {
// CompilationUnitElement element = unit.element;
// if (element != null) {
// source = element.source;
// }
// }
// return source;
// }
// /**
// * Answer the full name of the given source. The returned value will have all
// * [File.separatorChar] replace by '/'.
// *
// * @param source the source
// * @return the full name or `null` if it could not be determined
// */
// String _getSourceFullName(Source source) {
// if (source != null) {
// String fullName = source.fullName;
// if (fullName != null) {
// return fullName.replaceAll(r'\', '/');
// }
// }
// return null;
// }
}
/**
* Kind of the redirecting constructor.
*/
class RedirectingConstructorKind extends Enum<RedirectingConstructorKind> {
static const RedirectingConstructorKind CONST =
const RedirectingConstructorKind('CONST', 0);
static const RedirectingConstructorKind NORMAL =
const RedirectingConstructorKind('NORMAL', 1);
static const List<RedirectingConstructorKind> values = const [CONST, NORMAL];
const RedirectingConstructorKind(String name, int ordinal)
: super(name, ordinal);
}
/**
* The enumeration `ResolverErrorCode` defines the error codes used for errors
* detected by the resolver. The convention for this class is for the name of
* the error code to indicate the problem that caused the error to be generated
* and for the error message to explain what is wrong and, when appropriate, how
* the problem can be corrected.
*/
class ResolverErrorCode extends ErrorCode {
static const ResolverErrorCode BREAK_LABEL_ON_SWITCH_MEMBER =
const ResolverErrorCode('BREAK_LABEL_ON_SWITCH_MEMBER',
"Break label resolves to case or default statement");
static const ResolverErrorCode CONTINUE_LABEL_ON_SWITCH =
const ResolverErrorCode('CONTINUE_LABEL_ON_SWITCH',
"A continue label resolves to switch, must be loop or switch member");
static const ResolverErrorCode MISSING_LIBRARY_DIRECTIVE_WITH_PART =
const ResolverErrorCode('MISSING_LIBRARY_DIRECTIVE_WITH_PART',
"Libraries that have parts must have a library directive");
/**
* Initialize a newly created error code to have the given [name]. The message
* associated with the error will be created from the given [message]
* template. The correction associated with the error will be created from the
* given [correction] template.
*/
const ResolverErrorCode(String name, String message, [String correction])
: super(name, message, correction);
@override
ErrorSeverity get errorSeverity => type.severity;
@override
ErrorType get type => ErrorType.COMPILE_TIME_ERROR;
}
/**
* Instances of the class `ResolverVisitor` are used to resolve the nodes within a single
* compilation unit.
*/
class ResolverVisitor extends ScopedVisitor {
/**
* The object used to resolve the element associated with the current node.
*/
ElementResolver elementResolver;
/**
* The object used to compute the type associated with the current node.
*/
StaticTypeAnalyzer typeAnalyzer;
/*
* The type system in use during resolution.
*/
TypeSystem typeSystem;
/**
* The class element representing the class containing the current node,
* or `null` if the current node is not contained in a class.
*/
ClassElement enclosingClass = null;
/**
* The class declaration representing the class containing the current node, or `null` if
* the current node is not contained in a class.
*/
ClassDeclaration _enclosingClassDeclaration = null;
/**
* The function type alias representing the function type containing the current node, or
* `null` if the current node is not contained in a function type alias.
*/
FunctionTypeAlias _enclosingFunctionTypeAlias = null;
/**
* The element representing the function containing the current node, or `null` if the
* current node is not contained in a function.
*/
ExecutableElement _enclosingFunction = null;
InferenceContext inferenceContext = null;
/**
* The object keeping track of which elements have had their types overridden.
*/
TypeOverrideManager _overrideManager = new TypeOverrideManager();
/**
* The object keeping track of which elements have had their types promoted.
*/
TypePromotionManager _promoteManager = new TypePromotionManager();
/**
* A comment before a function should be resolved in the context of the
* function. But when we incrementally resolve a comment, we don't want to
* resolve the whole function.
*
* So, this flag is set to `true`, when just context of the function should
* be built and the comment resolved.
*/
bool resolveOnlyCommentInFunctionBody = false;
/**
* Initialize a newly created visitor to resolve the nodes in an AST node.
*
* The [definingLibrary] is the element for the library containing the node
* being visited. The [source] is the source representing the compilation unit
* containing the node being visited. The [typeProvider] is the object used to
* access the types from the core library. The [errorListener] is the error
* listener that will be informed of any errors that are found during
* resolution. The [nameScope] is the scope used to resolve identifiers in the
* node that will first be visited. If `null` or unspecified, a new
* [LibraryScope] will be created based on [definingLibrary] and
* [typeProvider]. The [inheritanceManager] is used to perform inheritance
* lookups. If `null` or unspecified, a new [InheritanceManager] will be
* created based on [definingLibrary]. The [typeAnalyzerFactory] is used to
* create the type analyzer. If `null` or unspecified, a type analyzer of
* type [StaticTypeAnalyzer] will be created.
*/
ResolverVisitor(LibraryElement definingLibrary, Source source,
TypeProvider typeProvider, AnalysisErrorListener errorListener,
{Scope nameScope})
: super(definingLibrary, source, typeProvider, errorListener,
nameScope: nameScope) {
this.elementResolver = new ElementResolver(this);
this.typeSystem = definingLibrary.context.typeSystem;
bool strongModeHints = false;
AnalysisOptions options = definingLibrary.context.analysisOptions;
if (options is AnalysisOptionsImpl) {
strongModeHints = options.strongModeHints;
}
this.inferenceContext = new InferenceContext._(
errorListener, typeProvider, typeSystem, strongModeHints);
this.typeAnalyzer = new StaticTypeAnalyzer(this);
}
/**
* Return the element representing the function containing the current node, or `null` if
* the current node is not contained in a function.
*
* @return the element representing the function containing the current node
*/
ExecutableElement get enclosingFunction => _enclosingFunction;
/**
* Return the object keeping track of which elements have had their types overridden.
*
* @return the object keeping track of which elements have had their types overridden
*/
TypeOverrideManager get overrideManager => _overrideManager;
/**
* Return the object keeping track of which elements have had their types promoted.
*
* @return the object keeping track of which elements have had their types promoted
*/
TypePromotionManager get promoteManager => _promoteManager;
/**
* Return the propagated element associated with the given expression whose type can be
* overridden, or `null` if there is no element whose type can be overridden.
*
* @param expression the expression with which the element is associated
* @return the element associated with the given expression
*/
VariableElement getOverridablePropagatedElement(Expression expression) {
Element element = null;
if (expression is SimpleIdentifier) {
element = expression.propagatedElement;
} else if (expression is PrefixedIdentifier) {
element = expression.propagatedElement;
} else if (expression is PropertyAccess) {
element = expression.propertyName.propagatedElement;
}
if (element is VariableElement) {
return element;
}
return null;
}
/**
* Return the static element associated with the given expression whose type can be overridden, or
* `null` if there is no element whose type can be overridden.
*
* @param expression the expression with which the element is associated
* @return the element associated with the given expression
*/
VariableElement getOverridableStaticElement(Expression expression) {
Element element = null;
if (expression is SimpleIdentifier) {
element = expression.staticElement;
} else if (expression is PrefixedIdentifier) {
element = expression.staticElement;
} else if (expression is PropertyAccess) {
element = expression.propertyName.staticElement;
}
if (element is VariableElement) {
return element;
}
return null;
}
/**
* Return the static element associated with the given expression whose type can be promoted, or
* `null` if there is no element whose type can be promoted.
*
* @param expression the expression with which the element is associated
* @return the element associated with the given expression
*/
VariableElement getPromotionStaticElement(Expression expression) {
while (expression is ParenthesizedExpression) {
expression = (expression as ParenthesizedExpression).expression;
}
if (expression is! SimpleIdentifier) {
return null;
}
SimpleIdentifier identifier = expression as SimpleIdentifier;
Element element = identifier.staticElement;
if (element is! VariableElement) {
return null;
}
ElementKind kind = element.kind;
if (kind == ElementKind.LOCAL_VARIABLE) {
return element as VariableElement;
}
if (kind == ElementKind.PARAMETER) {
return element as VariableElement;
}
return null;
}
/**
* Prepares this [ResolverVisitor] to using it for incremental resolution.
*/
void initForIncrementalResolution([Declaration declaration = null]) {
if (declaration != null) {
Element element = declaration.element;
if (element is ExecutableElement) {
_enclosingFunction = element;
}
}
_overrideManager.enterScope();
}
/**
* If it is appropriate to do so, override the current type of the static and propagated elements
* associated with the given expression with the given type. Generally speaking, it is appropriate
* if the given type is more specific than the current type.
*
* @param expression the expression used to access the static and propagated elements whose types
* might be overridden
* @param potentialType the potential type of the elements
* @param allowPrecisionLoss see @{code overrideVariable} docs
*/
void overrideExpression(Expression expression, DartType potentialType,
bool allowPrecisionLoss, bool setExpressionType) {
VariableElement element = getOverridableStaticElement(expression);
if (element != null) {
DartType newBestType =
overrideVariable(element, potentialType, allowPrecisionLoss);
if (setExpressionType) {
recordPropagatedTypeIfBetter(expression, newBestType);
}
}
element = getOverridablePropagatedElement(expression);
if (element != null) {
overrideVariable(element, potentialType, allowPrecisionLoss);
}
}
/**
* If it is appropriate to do so, override the current type of the given element with the given
* type.
*
* @param element the element whose type might be overridden
* @param potentialType the potential type of the element
* @param allowPrecisionLoss true if `potentialType` is allowed to be less precise than the
* current best type
*
* Return a new better [DartType], or `null` if [potentialType] is not better
* than the current [element] type.
*/
DartType overrideVariable(VariableElement element, DartType potentialType,
bool allowPrecisionLoss) {
// TODO(scheglov) type propagation for instance/top-level fields
// was disabled because it depends on the order or visiting.
// If both field and its client are in the same unit, and we visit
// the client before the field, then propagated type is not set yet.
if (element is PropertyInducingElement) {
return null;
}
if (potentialType == null || potentialType.isBottom) {
return null;
}
DartType currentType = _overrideManager.getBestType(element);
if (potentialType == currentType) {
return null;
}
// If we aren't allowing precision loss then the third and fourth conditions
// check that we aren't losing precision.
//
// Let [C] be the current type and [P] be the potential type. When we
// aren't allowing precision loss -- which is the case for is-checks -- we
// check that [! (C << P)] or [P << C]. The second check, that [P << C], is
// analogous to part of the Dart Language Spec rule for type promotion under
// is-checks (in the analogy [T] is [P] and [S] is [C]):
//
// An is-expression of the form [v is T] shows that [v] has type [T] iff
// [T] is more specific than the type [S] of the expression [v] and both
// [T != dynamic] and [S != dynamic].
//
// It also covers an important case that is not applicable in the spec:
// for union types, we want an is-check to promote from an union type to
// (a subtype of) any of its members.
//
// The first check, that [! (C << P)], covers the case where [P] and [C] are
// unrelated types; This case is not addressed in the spec for static types.
if (currentType == null ||
allowPrecisionLoss ||
!currentType.isMoreSpecificThan(potentialType) ||
potentialType.isMoreSpecificThan(currentType)) {
_overrideManager.setType(element, potentialType);
return potentialType;
}
return null;
}
/**
* A client is about to resolve a member in the given class declaration.
*/
void prepareToResolveMembersInClass(ClassDeclaration node) {
_enclosingClassDeclaration = node;
enclosingClass = node.element;
typeAnalyzer.thisType = enclosingClass == null ? null : enclosingClass.type;
}
/**
* If the given [type] is valid, strongly more specific than the
* existing static type of the given [expression], record it as a propagated
* type of the given [expression]. Otherwise, reset it to `null`.
*
* If [hasOldPropagatedType] is `true` then the existing propagated type
* should also is checked.
*/
void recordPropagatedTypeIfBetter(Expression expression, DartType type,
[bool hasOldPropagatedType = false]) {
// Ensure that propagated type invalid.
if (type == null || type.isDynamic || type.isBottom) {
if (!hasOldPropagatedType) {
expression.propagatedType = null;
}
return;
}
// Ensure that propagated type is more specific than the static type.
DartType staticType = expression.staticType;
if (type == staticType || !type.isMoreSpecificThan(staticType)) {
expression.propagatedType = null;
return;
}
// Ensure that the new propagated type is more specific than the old one.
if (hasOldPropagatedType) {
DartType oldPropagatedType = expression.propagatedType;
if (oldPropagatedType != null &&
!type.isMoreSpecificThan(oldPropagatedType)) {
return;
}
}
// OK
expression.propagatedType = type;
}
/**
* Visit the given [comment] if it is not `null`.
*/
void safelyVisitComment(Comment comment) {
if (comment != null) {
super.visitComment(comment);
}
}
@override
Object visitAnnotation(Annotation node) {
AstNode parent = node.parent;
if (identical(parent, _enclosingClassDeclaration) ||
identical(parent, _enclosingFunctionTypeAlias)) {
return null;
}
safelyVisit(node.name);
safelyVisit(node.constructorName);
Element element = node.element;
if (element is ExecutableElement) {
InferenceContext.setType(node.arguments, element.type);
}
safelyVisit(node.arguments);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitArgumentList(ArgumentList node) {
DartType callerType = InferenceContext.getType(node);
if (callerType is FunctionType) {
Map<String, DartType> namedParameterTypes =
callerType.namedParameterTypes;
List<DartType> normalParameterTypes = callerType.normalParameterTypes;
List<DartType> optionalParameterTypes = callerType.optionalParameterTypes;
int normalCount = normalParameterTypes.length;
int optionalCount = optionalParameterTypes.length;
NodeList<Expression> arguments = node.arguments;
Iterable<Expression> positional =
arguments.takeWhile((l) => l is! NamedExpression);
Iterable<Expression> required = positional.take(normalCount);
Iterable<Expression> optional =
positional.skip(normalCount).take(optionalCount);
Iterable<Expression> named =
arguments.skipWhile((l) => l is! NamedExpression);
//TODO(leafp): Consider using the parameter elements here instead.
//TODO(leafp): Make sure that the parameter elements are getting
// setup correctly with inference.
int index = 0;
for (Expression argument in required) {
InferenceContext.setType(argument, normalParameterTypes[index++]);
}
index = 0;
for (Expression argument in optional) {
InferenceContext.setType(argument, optionalParameterTypes[index++]);
}
for (Expression argument in named) {
if (argument is NamedExpression) {
DartType type = namedParameterTypes[argument.name.label.name];
if (type != null) {
InferenceContext.setType(argument, type);
}
}
}
}
return super.visitArgumentList(node);
}
@override
Object visitAsExpression(AsExpression node) {
InferenceContext.setType(node.expression, node.type.type);
super.visitAsExpression(node);
// Since an as-statement doesn't actually change the type, we don't
// let it affect the propagated type when it would result in a loss
// of precision.
overrideExpression(node.expression, node.type.type, false, false);
return null;
}
@override
Object visitAssertStatement(AssertStatement node) {
super.visitAssertStatement(node);
_propagateTrueState(node.condition);
return null;
}
@override
Object visitAssignmentExpression(AssignmentExpression node) {
safelyVisit(node.leftHandSide);
TokenType operator = node.operator.type;
if (operator == TokenType.EQ ||
operator == TokenType.QUESTION_QUESTION_EQ) {
InferenceContext.setType(
node.rightHandSide, node.leftHandSide.staticType);
}
safelyVisit(node.rightHandSide);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitAwaitExpression(AwaitExpression node) {
// TODO(leafp): Handle the implicit union type here
// https://github.com/dart-lang/sdk/issues/25322
DartType contextType = StaticTypeAnalyzer.flattenFutures(
typeProvider, InferenceContext.getType(node));
if (contextType != null) {
InterfaceType futureT =
typeProvider.futureType.substitute4([contextType]);
InferenceContext.setType(node.expression, futureT);
}
return super.visitAwaitExpression(node);
}
@override
Object visitBinaryExpression(BinaryExpression node) {
TokenType operatorType = node.operator.type;
Expression leftOperand = node.leftOperand;
Expression rightOperand = node.rightOperand;
if (operatorType == TokenType.AMPERSAND_AMPERSAND) {
safelyVisit(leftOperand);
if (rightOperand != null) {
_overrideManager.enterScope();
try {
_promoteManager.enterScope();
try {
_propagateTrueState(leftOperand);
// Type promotion.
_promoteTypes(leftOperand);
_clearTypePromotionsIfPotentiallyMutatedIn(leftOperand);
_clearTypePromotionsIfPotentiallyMutatedIn(rightOperand);
_clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(
rightOperand);
// Visit right operand.
rightOperand.accept(this);
} finally {
_promoteManager.exitScope();
}
} finally {
_overrideManager.exitScope();
}
}
} else if (operatorType == TokenType.BAR_BAR) {
safelyVisit(leftOperand);
if (rightOperand != null) {
_overrideManager.enterScope();
try {
_propagateFalseState(leftOperand);
rightOperand.accept(this);
} finally {
_overrideManager.exitScope();
}
}
} else {
// TODO(leafp): Do downwards inference using the declared type
// of the binary operator for other cases.
if (operatorType == TokenType.QUESTION_QUESTION) {
InferenceContext.setTypeFromNode(leftOperand, node);
InferenceContext.setTypeFromNode(rightOperand, node);
}
safelyVisit(leftOperand);
safelyVisit(rightOperand);
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitBlockFunctionBody(BlockFunctionBody node) {
_overrideManager.enterScope();
try {
inferenceContext.pushReturnContext(InferenceContext.getType(node));
super.visitBlockFunctionBody(node);
} finally {
_overrideManager.exitScope();
inferenceContext.popReturnContext();
}
return null;
}
@override
Object visitBreakStatement(BreakStatement node) {
//
// We do not visit the label because it needs to be visited in the context
// of the statement.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitCascadeExpression(CascadeExpression node) {
InferenceContext.setTypeFromNode(node.target, node);
return super.visitCascadeExpression(node);
}
@override
Object visitClassDeclaration(ClassDeclaration node) {
//
// Resolve the metadata in the library scope.
//
if (node.metadata != null) {
node.metadata.accept(this);
}
_enclosingClassDeclaration = node;
//
// Continue the class resolution.
//
ClassElement outerType = enclosingClass;
try {
enclosingClass = node.element;
typeAnalyzer.thisType =
enclosingClass == null ? null : enclosingClass.type;
super.visitClassDeclaration(node);
node.accept(elementResolver);
node.accept(typeAnalyzer);
} finally {
typeAnalyzer.thisType = outerType == null ? null : outerType.type;
enclosingClass = outerType;
_enclosingClassDeclaration = null;
}
return null;
}
/**
* Implementation of this method should be synchronized with
* [visitClassDeclaration].
*/
visitClassDeclarationIncrementally(ClassDeclaration node) {
//
// Resolve the metadata in the library scope.
//
if (node.metadata != null) {
node.metadata.accept(this);
}
_enclosingClassDeclaration = node;
//
// Continue the class resolution.
//
enclosingClass = node.element;
typeAnalyzer.thisType = enclosingClass == null ? null : enclosingClass.type;
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
Object visitComment(Comment node) {
AstNode parent = node.parent;
if (parent is FunctionDeclaration ||
parent is FunctionTypeAlias ||
parent is ConstructorDeclaration ||
parent is MethodDeclaration) {
return null;
}
super.visitComment(node);
return null;
}
@override
Object visitCommentReference(CommentReference node) {
//
// We do not visit the identifier because it needs to be visited in the
// context of the reference.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitCompilationUnit(CompilationUnit node) {
//
// TODO(brianwilkerson) The goal of the code below is to visit the
// declarations in such an order that we can infer type information for
// top-level variables before we visit references to them. This is better
// than making no effort, but still doesn't completely satisfy that goal
// (consider for example "final var a = b; final var b = 0;"; we'll infer a
// type of 'int' for 'b', but not for 'a' because of the order of the
// visits). Ideally we would create a dependency graph, but that would
// require references to be resolved, which they are not.
//
_overrideManager.enterScope();
try {
NodeList<Directive> directives = node.directives;
int directiveCount = directives.length;
for (int i = 0; i < directiveCount; i++) {
directives[i].accept(this);
}
NodeList<CompilationUnitMember> declarations = node.declarations;
int declarationCount = declarations.length;
for (int i = 0; i < declarationCount; i++) {
CompilationUnitMember declaration = declarations[i];
if (declaration is! ClassDeclaration) {
declaration.accept(this);
}
}
for (int i = 0; i < declarationCount; i++) {
CompilationUnitMember declaration = declarations[i];
if (declaration is ClassDeclaration) {
declaration.accept(this);
}
}
} finally {
_overrideManager.exitScope();
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitConditionalExpression(ConditionalExpression node) {
Expression condition = node.condition;
safelyVisit(condition);
Expression thenExpression = node.thenExpression;
if (thenExpression != null) {
_overrideManager.enterScope();
try {
_promoteManager.enterScope();
try {
_propagateTrueState(condition);
// Type promotion.
_promoteTypes(condition);
_clearTypePromotionsIfPotentiallyMutatedIn(thenExpression);
_clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(
thenExpression);
// Visit "then" expression.
InferenceContext.setTypeFromNode(thenExpression, node);
thenExpression.accept(this);
} finally {
_promoteManager.exitScope();
}
} finally {
_overrideManager.exitScope();
}
}
Expression elseExpression = node.elseExpression;
if (elseExpression != null) {
_overrideManager.enterScope();
try {
_propagateFalseState(condition);
InferenceContext.setTypeFromNode(elseExpression, node);
elseExpression.accept(this);
} finally {
_overrideManager.exitScope();
}
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
bool thenIsAbrupt = _isAbruptTerminationExpression(thenExpression);
bool elseIsAbrupt = _isAbruptTerminationExpression(elseExpression);
if (elseIsAbrupt && !thenIsAbrupt) {
_propagateTrueState(condition);
_propagateState(thenExpression);
} else if (thenIsAbrupt && !elseIsAbrupt) {
_propagateFalseState(condition);
_propagateState(elseExpression);
}
return null;
}
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
ExecutableElement outerFunction = _enclosingFunction;
try {
_enclosingFunction = node.element;
FunctionType type = _enclosingFunction.type;
InferenceContext.setType(node.body, type.returnType);
super.visitConstructorDeclaration(node);
} finally {
_enclosingFunction = outerFunction;
}
ConstructorElementImpl constructor = node.element;
constructor.constantInitializers =
new ConstantAstCloner().cloneNodeList(node.initializers);
return null;
}
@override
void visitConstructorDeclarationInScope(ConstructorDeclaration node) {
super.visitConstructorDeclarationInScope(node);
safelyVisitComment(node.documentationComment);
}
@override
Object visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
//
// We visit the expression, but do not visit the field name because it needs
// to be visited in the context of the constructor field initializer node.
//
FieldElement fieldElement = enclosingClass.getField(node.fieldName.name);
InferenceContext.setType(node.expression, fieldElement?.type);
safelyVisit(node.expression);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitConstructorName(ConstructorName node) {
//
// We do not visit either the type name, because it won't be visited anyway,
// or the name, because it needs to be visited in the context of the
// constructor name.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitContinueStatement(ContinueStatement node) {
//
// We do not visit the label because it needs to be visited in the context
// of the statement.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitDefaultFormalParameter(DefaultFormalParameter node) {
InferenceContext.setType(node.defaultValue, node.parameter.element?.type);
super.visitDefaultFormalParameter(node);
ParameterElement element = node.element;
if (element.initializer != null && node.defaultValue != null) {
(element.initializer as FunctionElementImpl).returnType =
node.defaultValue.staticType;
}
// Clone the ASTs for default formal parameters, so that we can use them
// during constant evaluation.
(element as ConstVariableElement).constantInitializer =
new ConstantAstCloner().cloneNode(node.defaultValue);
return null;
}
@override
Object visitDoStatement(DoStatement node) {
_overrideManager.enterScope();
try {
super.visitDoStatement(node);
} finally {
_overrideManager.exitScope();
}
// TODO(brianwilkerson) If the loop can only be exited because the condition
// is false, then propagateFalseState(node.getCondition());
return null;
}
@override
Object visitEmptyFunctionBody(EmptyFunctionBody node) {
if (resolveOnlyCommentInFunctionBody) {
return null;
}
return super.visitEmptyFunctionBody(node);
}
@override
Object visitEnumDeclaration(EnumDeclaration node) {
//
// Resolve the metadata in the library scope
// and associate the annotations with the element.
//
if (node.metadata != null) {
node.metadata.accept(this);
ElementResolver.setMetadata(node.element, node);
}
//
// Continue the enum resolution.
//
ClassElement outerType = enclosingClass;
try {
enclosingClass = node.element;
typeAnalyzer.thisType =
enclosingClass == null ? null : enclosingClass.type;
super.visitEnumDeclaration(node);
node.accept(elementResolver);
node.accept(typeAnalyzer);
} finally {
typeAnalyzer.thisType = outerType == null ? null : outerType.type;
enclosingClass = outerType;
_enclosingClassDeclaration = null;
}
return null;
}
@override
Object visitExpressionFunctionBody(ExpressionFunctionBody node) {
if (resolveOnlyCommentInFunctionBody) {
return null;
}
_overrideManager.enterScope();
try {
InferenceContext.setTypeFromNode(node.expression, node);
super.visitExpressionFunctionBody(node);
} finally {
_overrideManager.exitScope();
}
return null;
}
@override
Object visitFieldDeclaration(FieldDeclaration node) {
_overrideManager.enterScope();
try {
super.visitFieldDeclaration(node);
} finally {
Map<VariableElement, DartType> overrides =
_overrideManager.captureOverrides(node.fields);
_overrideManager.exitScope();
_overrideManager.applyOverrides(overrides);
}
return null;
}
@override
Object visitForEachStatement(ForEachStatement node) {
_overrideManager.enterScope();
try {
super.visitForEachStatement(node);
} finally {
_overrideManager.exitScope();
}
return null;
}
@override
void visitForEachStatementInScope(ForEachStatement node) {
//
// We visit the iterator before the loop variable because the loop variable
// cannot be in scope while visiting the iterator.
//
Expression iterable = node.iterable;
DeclaredIdentifier loopVariable = node.loopVariable;
SimpleIdentifier identifier = node.identifier;
if (loopVariable?.type?.type != null) {
InterfaceType targetType = (node.awaitKeyword == null)
? typeProvider.iterableType
: typeProvider.streamType;
InferenceContext.setType(
iterable, targetType.substitute4([loopVariable.type.type]));
}
safelyVisit(iterable);
safelyVisit(loopVariable);
safelyVisit(identifier);
Statement body = node.body;
if (body != null) {
_overrideManager.enterScope();
try {
if (loopVariable != null && iterable != null) {
LocalVariableElement loopElement = loopVariable.element;
if (loopElement != null) {
DartType propagatedType = null;
if (node.awaitKeyword == null) {
propagatedType = _getIteratorElementType(iterable);
} else {
propagatedType = _getStreamElementType(iterable);
}
if (propagatedType != null) {
overrideVariable(loopElement, propagatedType, true);
recordPropagatedTypeIfBetter(
loopVariable.identifier, propagatedType);
}
}
} else if (identifier != null && iterable != null) {
Element identifierElement = identifier.staticElement;
if (identifierElement is VariableElement) {
DartType iteratorElementType = _getIteratorElementType(iterable);
overrideVariable(identifierElement, iteratorElementType, true);
recordPropagatedTypeIfBetter(identifier, iteratorElementType);
}
}
visitStatementInScope(body);
} finally {
_overrideManager.exitScope();
}
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
Object visitForStatement(ForStatement node) {
_overrideManager.enterScope();
try {
super.visitForStatement(node);
} finally {
_overrideManager.exitScope();
}
return null;
}
@override
void visitForStatementInScope(ForStatement node) {
safelyVisit(node.variables);
safelyVisit(node.initialization);
safelyVisit(node.condition);
_overrideManager.enterScope();
try {
_propagateTrueState(node.condition);
visitStatementInScope(node.body);
node.updaters.accept(this);
} finally {
_overrideManager.exitScope();
}
// TODO(brianwilkerson) If the loop can only be exited because the condition
// is false, then propagateFalseState(condition);
}
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement outerFunction = _enclosingFunction;
try {
SimpleIdentifier functionName = node.name;
_enclosingFunction = functionName.staticElement as ExecutableElement;
InferenceContext.setType(
node.functionExpression, _enclosingFunction.type);
super.visitFunctionDeclaration(node);
} finally {
_enclosingFunction = outerFunction;
}
return null;
}
@override
void visitFunctionDeclarationInScope(FunctionDeclaration node) {
super.visitFunctionDeclarationInScope(node);
safelyVisitComment(node.documentationComment);
}
@override
Object visitFunctionExpression(FunctionExpression node) {
ExecutableElement outerFunction = _enclosingFunction;
try {
_enclosingFunction = node.element;
_overrideManager.enterScope();
try {
DartType functionType = InferenceContext.getType(node);
if (functionType is FunctionType) {
_inferFormalParameterList(node.parameters, functionType);
DartType returnType = _computeReturnOrYieldType(
functionType.returnType,
_enclosingFunction.isGenerator,
_enclosingFunction.isAsynchronous);
InferenceContext.setType(node.body, returnType);
}
super.visitFunctionExpression(node);
} finally {
_overrideManager.exitScope();
}
} finally {
_enclosingFunction = outerFunction;
}
return null;
}
@override
Object visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
safelyVisit(node.function);
node.accept(elementResolver);
_inferFunctionExpressionsParametersTypes(node.argumentList);
InferenceContext.setType(node.argumentList, node.function.staticType);
safelyVisit(node.argumentList);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitFunctionTypeAlias(FunctionTypeAlias node) {
// Resolve the metadata in the library scope.
if (node.metadata != null) {
node.metadata.accept(this);
}
FunctionTypeAlias outerAlias = _enclosingFunctionTypeAlias;
_enclosingFunctionTypeAlias = node;
try {
super.visitFunctionTypeAlias(node);
} finally {
_enclosingFunctionTypeAlias = outerAlias;
}
return null;
}
@override
void visitFunctionTypeAliasInScope(FunctionTypeAlias node) {
super.visitFunctionTypeAliasInScope(node);
safelyVisitComment(node.documentationComment);
}
@override
Object visitHideCombinator(HideCombinator node) => null;
@override
Object visitIfStatement(IfStatement node) {
Expression condition = node.condition;
safelyVisit(condition);
Map<VariableElement, DartType> thenOverrides =
new HashMap<VariableElement, DartType>();
Statement thenStatement = node.thenStatement;
if (thenStatement != null) {
_overrideManager.enterScope();
try {
_promoteManager.enterScope();
try {
_propagateTrueState(condition);
// Type promotion.
_promoteTypes(condition);
_clearTypePromotionsIfPotentiallyMutatedIn(thenStatement);
_clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(
thenStatement);
// Visit "then".
visitStatementInScope(thenStatement);
} finally {
_promoteManager.exitScope();
}
} finally {
thenOverrides = _overrideManager.captureLocalOverrides();
_overrideManager.exitScope();
}
}
Map<VariableElement, DartType> elseOverrides =
new HashMap<VariableElement, DartType>();
Statement elseStatement = node.elseStatement;
if (elseStatement != null) {
_overrideManager.enterScope();
try {
_propagateFalseState(condition);
visitStatementInScope(elseStatement);
} finally {
elseOverrides = _overrideManager.captureLocalOverrides();
_overrideManager.exitScope();
}
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
// Join overrides.
bool thenIsAbrupt = _isAbruptTerminationStatement(thenStatement);
bool elseIsAbrupt = _isAbruptTerminationStatement(elseStatement);
if (elseIsAbrupt && !thenIsAbrupt) {
_propagateTrueState(condition);
_overrideManager.applyOverrides(thenOverrides);
} else if (thenIsAbrupt && !elseIsAbrupt) {
_propagateFalseState(condition);
_overrideManager.applyOverrides(elseOverrides);
} else if (!thenIsAbrupt && !elseIsAbrupt) {
List<Map<VariableElement, DartType>> perBranchOverrides =
new List<Map<VariableElement, DartType>>();
perBranchOverrides.add(thenOverrides);
perBranchOverrides.add(elseOverrides);
_overrideManager.mergeOverrides(perBranchOverrides);
}
return null;
}
@override
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
TypeName classTypeName = node.constructorName.type;
if (classTypeName.typeArguments == null) {
DartType contextType = InferenceContext.getType(node);
if (contextType is InterfaceType &&
contextType.typeArguments != null &&
contextType.typeArguments.length > 0) {
List<DartType> targs =
inferenceContext.matchTypes(classTypeName.type, contextType);
if (targs != null && targs.any((t) => !t.isDynamic)) {
ClassElement classElement = classTypeName.type.element;
InterfaceType rawType = classElement.type;
InterfaceType fullType =
rawType.substitute2(targs, rawType.typeArguments);
// The element resolver uses the type on the constructor name, so
// infer it first
typeAnalyzer.inferConstructorName(node.constructorName, fullType);
}
}
}
safelyVisit(node.constructorName);
FunctionType constructorType = node.constructorName.staticElement?.type;
if (constructorType != null) {
InferenceContext.setType(node.argumentList, constructorType);
}
safelyVisit(node.argumentList);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitLabel(Label node) => null;
@override
Object visitLibraryIdentifier(LibraryIdentifier node) => null;
@override
Object visitListLiteral(ListLiteral node) {
DartType contextType = InferenceContext.getType(node);
List<DartType> targs = null;
if (node.typeArguments != null) {
targs = node.typeArguments.arguments.map((t) => t.type).toList();
} else if (contextType is InterfaceType) {
InterfaceType listD =
typeProvider.listType.substitute4([typeProvider.dynamicType]);
targs = inferenceContext.matchTypes(listD, contextType);
}
if (targs != null && targs.length == 1 && !targs[0].isDynamic) {
DartType eType = targs[0];
InterfaceType listT = typeProvider.listType.substitute4([eType]);
for (Expression child in node.elements) {
InferenceContext.setType(child, eType);
}
InferenceContext.setType(node, listT);
} else {
InferenceContext.clearType(node);
}
super.visitListLiteral(node);
return null;
}
@override
Object visitMapLiteral(MapLiteral node) {
DartType contextType = InferenceContext.getType(node);
List<DartType> targs = null;
if (node.typeArguments != null) {
targs = node.typeArguments.arguments.map((t) => t.type).toList();
} else if (contextType is InterfaceType) {
InterfaceType mapD = typeProvider.mapType
.substitute4([typeProvider.dynamicType, typeProvider.dynamicType]);
targs = inferenceContext.matchTypes(mapD, contextType);
}
if (targs != null && targs.length == 2 && targs.any((t) => !t.isDynamic)) {
DartType kType = targs[0];
DartType vType = targs[1];
InterfaceType mapT = typeProvider.mapType.substitute4([kType, vType]);
for (MapLiteralEntry entry in node.entries) {
InferenceContext.setType(entry.key, kType);
InferenceContext.setType(entry.value, vType);
}
InferenceContext.setType(node, mapT);
} else {
InferenceContext.clearType(node);
}
super.visitMapLiteral(node);
return null;
}
@override
Object visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement outerFunction = _enclosingFunction;
try {
_enclosingFunction = node.element;
DartType returnType = _computeReturnOrYieldType(
_enclosingFunction.type?.returnType,
_enclosingFunction.isGenerator,
_enclosingFunction.isAsynchronous);
InferenceContext.setType(node.body, returnType);
super.visitMethodDeclaration(node);
} finally {
_enclosingFunction = outerFunction;
}
return null;
}
@override
void visitMethodDeclarationInScope(MethodDeclaration node) {
super.visitMethodDeclarationInScope(node);
safelyVisitComment(node.documentationComment);
}
@override
Object visitMethodInvocation(MethodInvocation node) {
//
// We visit the target and argument list, but do not visit the method name
// because it needs to be visited in the context of the invocation.
//
safelyVisit(node.target);
safelyVisit(node.typeArguments);
node.accept(elementResolver);
_inferFunctionExpressionsParametersTypes(node.argumentList);
DartType contextType = node.staticInvokeType;
if (contextType is FunctionType) {
InferenceContext.setType(node.argumentList, contextType);
}
safelyVisit(node.argumentList);
node.accept(typeAnalyzer);
return null;
}
/**
* Given an [argumentList] and the [parameters] related to the element that
* will be invoked using those arguments, compute the list of parameters that
* correspond to the list of arguments.
*
* An error will be reported to [onError] if any of the arguments cannot be
* matched to a parameter. onError can be null to ignore the error.
*
* The flag [reportAsError] should be `true` if a compile-time error should be
* reported; or `false` if a compile-time warning should be reported
*
* Returns the parameters that correspond to the arguments.
*/
static List<ParameterElement> resolveArgumentsToParameters(
ArgumentList argumentList,
List<ParameterElement> parameters,
void onError(ErrorCode errorCode, AstNode node, [List<Object> arguments]),
{bool reportAsError: false}) {
List<ParameterElement> requiredParameters = new List<ParameterElement>();
List<ParameterElement> positionalParameters = new List<ParameterElement>();
HashMap<String, ParameterElement> namedParameters =
new HashMap<String, ParameterElement>();
for (ParameterElement parameter in parameters) {
ParameterKind kind = parameter.parameterKind;
if (kind == ParameterKind.REQUIRED) {
requiredParameters.add(parameter);
} else if (kind == ParameterKind.POSITIONAL) {
positionalParameters.add(parameter);
} else {
namedParameters[parameter.name] = parameter;
}
}
List<ParameterElement> unnamedParameters =
new List<ParameterElement>.from(requiredParameters);
unnamedParameters.addAll(positionalParameters);
int unnamedParameterCount = unnamedParameters.length;
int unnamedIndex = 0;
NodeList<Expression> arguments = argumentList.arguments;
int argumentCount = arguments.length;
List<ParameterElement> resolvedParameters =
new List<ParameterElement>(argumentCount);
int positionalArgumentCount = 0;
HashSet<String> usedNames = new HashSet<String>();
bool noBlankArguments = true;
for (int i = 0; i < argumentCount; i++) {
Expression argument = arguments[i];
if (argument is NamedExpression) {
SimpleIdentifier nameNode = argument.name.label;
String name = nameNode.name;
ParameterElement element = namedParameters[name];
if (element == null) {
ErrorCode errorCode = (reportAsError
? CompileTimeErrorCode.UNDEFINED_NAMED_PARAMETER
: StaticWarningCode.UNDEFINED_NAMED_PARAMETER);
if (onError != null) {
onError(errorCode, nameNode, [name]);
}
} else {
resolvedParameters[i] = element;
nameNode.staticElement = element;
}
if (!usedNames.add(name)) {
if (onError != null) {
onError(CompileTimeErrorCode.DUPLICATE_NAMED_ARGUMENT, nameNode,
[name]);
}
}
} else {
if (argument is SimpleIdentifier && argument.name.isEmpty) {
noBlankArguments = false;
}
positionalArgumentCount++;
if (unnamedIndex < unnamedParameterCount) {
resolvedParameters[i] = unnamedParameters[unnamedIndex++];
}
}
}
if (positionalArgumentCount < requiredParameters.length &&
noBlankArguments) {
ErrorCode errorCode = (reportAsError
? CompileTimeErrorCode.NOT_ENOUGH_REQUIRED_ARGUMENTS
: StaticWarningCode.NOT_ENOUGH_REQUIRED_ARGUMENTS);
if (onError != null) {
onError(errorCode, argumentList,
[requiredParameters.length, positionalArgumentCount]);
}
} else if (positionalArgumentCount > unnamedParameterCount &&
noBlankArguments) {
ErrorCode errorCode = (reportAsError
? CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS
: StaticWarningCode.EXTRA_POSITIONAL_ARGUMENTS);
if (onError != null) {
onError(errorCode, argumentList,
[unnamedParameterCount, positionalArgumentCount]);
}
}
return resolvedParameters;
}
@override
Object visitNamedExpression(NamedExpression node) {
InferenceContext.setType(node.expression, InferenceContext.getType(node));
return super.visitNamedExpression(node);
}
@override
Object visitNode(AstNode node) {
node.visitChildren(this);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitParenthesizedExpression(ParenthesizedExpression node) {
InferenceContext.setType(node.expression, InferenceContext.getType(node));
return super.visitParenthesizedExpression(node);
}
@override
Object visitPrefixedIdentifier(PrefixedIdentifier node) {
//
// We visit the prefix, but do not visit the identifier because it needs to
// be visited in the context of the prefix.
//
safelyVisit(node.prefix);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitPropertyAccess(PropertyAccess node) {
//
// We visit the target, but do not visit the property name because it needs
// to be visited in the context of the property access node.
//
safelyVisit(node.target);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node) {
//
// We visit the argument list, but do not visit the optional identifier
// because it needs to be visited in the context of the constructor
// invocation.
//
InferenceContext.setType(node.argumentList, node.staticElement?.type);
safelyVisit(node.argumentList);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitReturnStatement(ReturnStatement node) {
InferenceContext.setType(node.expression, inferenceContext.returnContext);
return super.visitReturnStatement(node);
}
@override
Object visitShowCombinator(ShowCombinator node) => null;
@override
Object visitSuperConstructorInvocation(SuperConstructorInvocation node) {
//
// We visit the argument list, but do not visit the optional identifier
// because it needs to be visited in the context of the constructor
// invocation.
//
InferenceContext.setType(node.argumentList, node.staticElement?.type);
safelyVisit(node.argumentList);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitSwitchCase(SwitchCase node) {
_overrideManager.enterScope();
try {
super.visitSwitchCase(node);
} finally {
_overrideManager.exitScope();
}
return null;
}
@override
Object visitSwitchDefault(SwitchDefault node) {
_overrideManager.enterScope();
try {
super.visitSwitchDefault(node);
} finally {
_overrideManager.exitScope();
}
return null;
}
@override
Object visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
_overrideManager.enterScope();
try {
super.visitTopLevelVariableDeclaration(node);
} finally {
Map<VariableElement, DartType> overrides =
_overrideManager.captureOverrides(node.variables);
_overrideManager.exitScope();
_overrideManager.applyOverrides(overrides);
}
return null;
}
@override
Object visitTypeName(TypeName node) => null;
@override
Object visitVariableDeclaration(VariableDeclaration node) {
InferenceContext.setType(node.initializer, InferenceContext.getType(node));
super.visitVariableDeclaration(node);
VariableElement element = node.element;
if (element.initializer != null && node.initializer != null) {
(element.initializer as FunctionElementImpl).returnType =
node.initializer.staticType;
}
// Note: in addition to cloning the initializers for const variables, we
// have to clone the initializers for non-static final fields (because if
// they occur in a class with a const constructor, they will be needed to
// evaluate the const constructor).
if ((element.isConst ||
(element is FieldElement &&
element.isFinal &&
!element.isStatic)) &&
node.initializer != null) {
(element as ConstVariableElement).constantInitializer =
new ConstantAstCloner().cloneNode(node.initializer);
}
return null;
}
@override visitVariableDeclarationList(VariableDeclarationList node) {
for (VariableDeclaration decl in node.variables) {
InferenceContext.setType(decl, node.type?.type);
}
super.visitVariableDeclarationList(node);
}
@override
Object visitWhileStatement(WhileStatement node) {
// Note: since we don't call the base class, we have to maintain
// _implicitLabelScope ourselves.
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
Expression condition = node.condition;
safelyVisit(condition);
Statement body = node.body;
if (body != null) {
_overrideManager.enterScope();
try {
_propagateTrueState(condition);
visitStatementInScope(body);
} finally {
_overrideManager.exitScope();
}
}
} finally {
_implicitLabelScope = outerImplicitScope;
}
// TODO(brianwilkerson) If the loop can only be exited because the condition
// is false, then propagateFalseState(condition);
node.accept(elementResolver);
node.accept(typeAnalyzer);
return null;
}
@override
Object visitYieldStatement(YieldStatement node) {
DartType returnType = inferenceContext.returnContext;
if (returnType != null && _enclosingFunction != null) {
// If we're not in a generator ([a]sync*, then we shouldn't have a yield.
// so don't infer
if (_enclosingFunction.isGenerator) {
// If this just a yield, then we just pass on the element type
DartType type = returnType;
if (node.star != null) {
// If this is a yield*, then we wrap the element return type
// If it's synchronous, we expect Iterable<T>, otherwise Stream<T>
InterfaceType wrapperType = _enclosingFunction.isSynchronous
? typeProvider.iterableType
: typeProvider.streamType;
type = wrapperType.substitute4(<DartType>[type]);
}
InferenceContext.setType(node.expression, type);
}
}
return super.visitYieldStatement(node);
}
/**
* Checks each promoted variable in the current scope for compliance with the following
* specification statement:
*
* If the variable <i>v</i> is accessed by a closure in <i>s<sub>1</sub></i> then the variable
* <i>v</i> is not potentially mutated anywhere in the scope of <i>v</i>.
*/
void _clearTypePromotionsIfAccessedInClosureAndProtentiallyMutated(
AstNode target) {
for (Element element in _promoteManager.promotedElements) {
if ((element as VariableElementImpl).isPotentiallyMutatedInScope) {
if (_isVariableAccessedInClosure(element, target)) {
_promoteManager.setType(element, null);
}
}
}
}
/**
* Checks each promoted variable in the current scope for compliance with the following
* specification statement:
*
* <i>v</i> is not potentially mutated in <i>s<sub>1</sub></i> or within a closure.
*/
void _clearTypePromotionsIfPotentiallyMutatedIn(AstNode target) {
for (Element element in _promoteManager.promotedElements) {
if (_isVariablePotentiallyMutatedIn(element, target)) {
_promoteManager.setType(element, null);
}
}
}
/**
* Given the declared return type of a function, compute the type of the
* values which should be returned or yielded as appropriate. If a type
* cannot be computed from the declared return type, return null.
*/
DartType _computeReturnOrYieldType(
DartType declaredType, bool isGenerator, bool isAsynchronous) {
// Ordinary functions just return their declared types.
if (!isGenerator && !isAsynchronous) {
return declaredType;
}
if (isGenerator) {
if (declaredType is! InterfaceType) {
return null;
}
// If it's synchronous, we expect Iterable<T>, otherwise Stream<T>
InterfaceType rawType = isAsynchronous
? typeProvider.streamDynamicType
: typeProvider.iterableDynamicType;
// Match the types to instantiate the type arguments if possible
List<DartType> typeArgs =
inferenceContext.matchTypes(rawType, declaredType);
return (typeArgs?.length == 1) ? typeArgs[0] : null;
}
// Must be asynchronous to reach here, so strip off any layers of Future
return StaticTypeAnalyzer.flattenFutures(typeProvider, declaredType);
}
/**
* The given expression is the expression used to compute the iterator for a
* for-each statement. Attempt to compute the type of objects that will be
* assigned to the loop variable and return that type. Return `null` if the
* type could not be determined. The [iteratorExpression] is the expression
* that will return the Iterable being iterated over.
*/
DartType _getIteratorElementType(Expression iteratorExpression) {
DartType expressionType = iteratorExpression.bestType;
if (expressionType is InterfaceType) {
InterfaceType interfaceType = expressionType;
PropertyAccessorElement iteratorFunction =
interfaceType.lookUpInheritedGetter("iterator");
if (iteratorFunction == null) {
// TODO(brianwilkerson) Should we report this error?
return null;
}
DartType iteratorType = iteratorFunction.returnType;
if (iteratorType is InterfaceType) {
InterfaceType iteratorInterfaceType = iteratorType;
PropertyAccessorElement currentFunction =
iteratorInterfaceType.lookUpInheritedGetter("current");
if (currentFunction == null) {
// TODO(brianwilkerson) Should we report this error?
return null;
}
return currentFunction.returnType;
}
}
return null;
}
/**
* The given expression is the expression used to compute the stream for an
* asynchronous for-each statement. Attempt to compute the type of objects
* that will be assigned to the loop variable and return that type.
* Return `null` if the type could not be determined. The [streamExpression]
* is the expression that will return the stream being iterated over.
*/
DartType _getStreamElementType(Expression streamExpression) {
DartType streamType = streamExpression.bestType;
if (streamType is InterfaceType) {
MethodElement listenFunction = streamType.lookUpInheritedMethod("listen");
if (listenFunction == null) {
return null;
}
List<ParameterElement> listenParameters = listenFunction.parameters;
if (listenParameters == null || listenParameters.length < 1) {
return null;
}
DartType onDataType = listenParameters[0].type;
if (onDataType is FunctionType) {
List<ParameterElement> onDataParameters = onDataType.parameters;
if (onDataParameters == null || onDataParameters.isEmpty) {
return null;
}
return onDataParameters[0].type;
}
}
return null;
}
void _inferFormalParameterList(FormalParameterList node, DartType type) {
if (typeAnalyzer.inferFormalParameterList(node, type)) {
// TODO(leafp): This gets dropped on the floor if we're in the field
// inference task. We should probably keep these infos.
inferenceContext.recordInference(node.parent, type);
}
}
/**
* If given "mayBeClosure" is [FunctionExpression] without explicit parameters types and its
* required type is [FunctionType], then infer parameters types from [FunctionType].
*/
void _inferFunctionExpressionParametersTypes(
Expression mayBeClosure, DartType mayByFunctionType) {
// prepare closure
if (mayBeClosure is! FunctionExpression) {
return;
}
FunctionExpression closure = mayBeClosure as FunctionExpression;
// prepare expected closure type
if (mayByFunctionType is! FunctionType) {
return;
}
FunctionType expectedClosureType = mayByFunctionType as FunctionType;
// If the expectedClosureType is not more specific than the static type,
// return.
DartType staticClosureType =
closure.element != null ? closure.element.type : null;
if (staticClosureType != null &&
!expectedClosureType.isMoreSpecificThan(staticClosureType)) {
return;
}
// set propagated type for the closure
closure.propagatedType = expectedClosureType;
// set inferred types for parameters
NodeList<FormalParameter> parameters = closure.parameters.parameters;
List<ParameterElement> expectedParameters = expectedClosureType.parameters;
for (int i = 0;
i < parameters.length && i < expectedParameters.length;
i++) {
FormalParameter parameter = parameters[i];
ParameterElement element = parameter.element;
DartType currentType = _overrideManager.getBestType(element);
// may be override the type
DartType expectedType = expectedParameters[i].type;
if (currentType == null || expectedType.isMoreSpecificThan(currentType)) {
_overrideManager.setType(element, expectedType);
}
}
}
/**
* Try to infer types of parameters of the [FunctionExpression] arguments.
*/
void _inferFunctionExpressionsParametersTypes(ArgumentList argumentList) {
for (Expression argument in argumentList.arguments) {
ParameterElement parameter = argument.propagatedParameterElement;
if (parameter == null) {
parameter = argument.staticParameterElement;
}
if (parameter != null) {
_inferFunctionExpressionParametersTypes(argument, parameter.type);
}
}
}
/**
* Return `true` if the given expression terminates abruptly (that is, if any expression
* following the given expression will not be reached).
*
* @param expression the expression being tested
* @return `true` if the given expression terminates abruptly
*/
bool _isAbruptTerminationExpression(Expression expression) {
// TODO(brianwilkerson) This needs to be significantly improved. Ideally we
// would eventually turn this into a method on Expression that returns a
// termination indication (normal, abrupt with no exception, abrupt with an
// exception).
while (expression is ParenthesizedExpression) {
expression = (expression as ParenthesizedExpression).expression;
}
return expression is ThrowExpression || expression is RethrowExpression;
}
/**
* Return `true` if the given statement terminates abruptly (that is, if any statement
* following the given statement will not be reached).
*
* @param statement the statement being tested
* @return `true` if the given statement terminates abruptly
*/
bool _isAbruptTerminationStatement(Statement statement) {
// TODO(brianwilkerson) This needs to be significantly improved. Ideally we
// would eventually turn this into a method on Statement that returns a
// termination indication (normal, abrupt with no exception, abrupt with an
// exception).
//
// collinsn: it is unsound to assume that [break] and [continue] are
// "abrupt". See: https://code.google.com/p/dart/issues/detail?id=19929#c4
// (tests are included in TypePropagationTest.java).
// In general, the difficulty is loopy control flow.
//
// In the presence of exceptions things become much more complicated, but
// while we only use this to propagate at [if]-statement join points,
// checking for [return] may work well enough in the common case.
if (statement is ReturnStatement) {
return true;
} else if (statement is ExpressionStatement) {
return _isAbruptTerminationExpression(statement.expression);
} else if (statement is Block) {
NodeList<Statement> statements = statement.statements;
int size = statements.length;
if (size == 0) {
return false;
}
// This last-statement-is-return heuristic is unsound for adversarial
// code, but probably works well in the common case:
//
// var x = 123;
// var c = true;
// L: if (c) {
// x = "hello";
// c = false;
// break L;
// return;
// }
// print(x);
//
// Unsound to assume that [x = "hello";] never executed after the
// if-statement. Of course, a dead-code analysis could point out that
// [return] here is dead.
return _isAbruptTerminationStatement(statements[size - 1]);
}
return false;
}
/**
* Return `true` if the given variable is accessed within a closure in the given
* [AstNode] and also mutated somewhere in variable scope. This information is only
* available for local variables (including parameters).
*
* @param variable the variable to check
* @param target the [AstNode] to check within
* @return `true` if this variable is potentially mutated somewhere in the given ASTNode
*/
bool _isVariableAccessedInClosure(Element variable, AstNode target) {
_ResolverVisitor_isVariableAccessedInClosure visitor =
new _ResolverVisitor_isVariableAccessedInClosure(variable);
target.accept(visitor);
return visitor.result;
}
/**
* Return `true` if the given variable is potentially mutated somewhere in the given
* [AstNode]. This information is only available for local variables (including parameters).
*
* @param variable the variable to check
* @param target the [AstNode] to check within
* @return `true` if this variable is potentially mutated somewhere in the given ASTNode
*/
bool _isVariablePotentiallyMutatedIn(Element variable, AstNode target) {
_ResolverVisitor_isVariablePotentiallyMutatedIn visitor =
new _ResolverVisitor_isVariablePotentiallyMutatedIn(variable);
target.accept(visitor);
return visitor.result;
}
/**
* If it is appropriate to do so, promotes the current type of the static element associated with
* the given expression with the given type. Generally speaking, it is appropriate if the given
* type is more specific than the current type.
*
* @param expression the expression used to access the static element whose types might be
* promoted
* @param potentialType the potential type of the elements
*/
void _promote(Expression expression, DartType potentialType) {
VariableElement element = getPromotionStaticElement(expression);
if (element != null) {
// may be mutated somewhere in closure
if (element.isPotentiallyMutatedInClosure) {
return;
}
// prepare current variable type
DartType type = _promoteManager.getType(element);
if (type == null) {
type = expression.staticType;
}
// Declared type should not be "dynamic".
if (type == null || type.isDynamic) {
return;
}
// Promoted type should not be "dynamic".
if (potentialType == null || potentialType.isDynamic) {
return;
}
// Promoted type should be more specific than declared.
if (!potentialType.isMoreSpecificThan(type)) {
return;
}
// Do promote type of variable.
_promoteManager.setType(element, potentialType);
}
}
/**
* Promotes type information using given condition.
*/
void _promoteTypes(Expression condition) {
if (condition is BinaryExpression) {
BinaryExpression binary = condition;
if (binary.operator.type == TokenType.AMPERSAND_AMPERSAND) {
Expression left = binary.leftOperand;
Expression right = binary.rightOperand;
_promoteTypes(left);
_promoteTypes(right);
_clearTypePromotionsIfPotentiallyMutatedIn(right);
}
} else if (condition is IsExpression) {
IsExpression is2 = condition;
if (is2.notOperator == null) {
_promote(is2.expression, is2.type.type);
}
} else if (condition is ParenthesizedExpression) {
_promoteTypes(condition.expression);
}
}
/**
* Propagate any type information that results from knowing that the given condition will have
* been evaluated to 'false'.
*
* @param condition the condition that will have evaluated to 'false'
*/
void _propagateFalseState(Expression condition) {
if (condition is BinaryExpression) {
BinaryExpression binary = condition;
if (binary.operator.type == TokenType.BAR_BAR) {
_propagateFalseState(binary.leftOperand);
_propagateFalseState(binary.rightOperand);
}
} else if (condition is IsExpression) {
IsExpression is2 = condition;
if (is2.notOperator != null) {
// Since an is-statement doesn't actually change the type, we don't
// let it affect the propagated type when it would result in a loss
// of precision.
overrideExpression(is2.expression, is2.type.type, false, false);
}
} else if (condition is PrefixExpression) {
PrefixExpression prefix = condition;
if (prefix.operator.type == TokenType.BANG) {
_propagateTrueState(prefix.operand);
}
} else if (condition is ParenthesizedExpression) {
_propagateFalseState(condition.expression);
}
}
/**
* Propagate any type information that results from knowing that the given expression will have
* been evaluated without altering the flow of execution.
*
* @param expression the expression that will have been evaluated
*/
void _propagateState(Expression expression) {
// TODO(brianwilkerson) Implement this.
}
/**
* Propagate any type information that results from knowing that the given condition will have
* been evaluated to 'true'.
*
* @param condition the condition that will have evaluated to 'true'
*/
void _propagateTrueState(Expression condition) {
if (condition is BinaryExpression) {
BinaryExpression binary = condition;
if (binary.operator.type == TokenType.AMPERSAND_AMPERSAND) {
_propagateTrueState(binary.leftOperand);
_propagateTrueState(binary.rightOperand);
}
} else if (condition is IsExpression) {
IsExpression is2 = condition;
if (is2.notOperator == null) {
// Since an is-statement doesn't actually change the type, we don't
// let it affect the propagated type when it would result in a loss
// of precision.
overrideExpression(is2.expression, is2.type.type, false, false);
}
} else if (condition is PrefixExpression) {
PrefixExpression prefix = condition;
if (prefix.operator.type == TokenType.BANG) {
_propagateFalseState(prefix.operand);
}
} else if (condition is ParenthesizedExpression) {
_propagateTrueState(condition.expression);
}
}
}
/**
* The abstract class `Scope` defines the behavior common to name scopes used by the resolver
* to determine which names are visible at any given point in the code.
*/
abstract class Scope {
/**
* The prefix used to mark an identifier as being private to its library.
*/
static int PRIVATE_NAME_PREFIX = 0x5F;
/**
* The suffix added to the declared name of a setter when looking up the setter. Used to
* disambiguate between a getter and a setter that have the same name.
*/
static String SETTER_SUFFIX = "=";
/**
* The name used to look up the method used to implement the unary minus operator. Used to
* disambiguate between the unary and binary operators.
*/
static String UNARY_MINUS = "unary-";
/**
* A table mapping names that are defined in this scope to the element representing the thing
* declared with that name.
*/
HashMap<String, Element> _definedNames = new HashMap<String, Element>();
/**
* A flag indicating whether there are any names defined in this scope.
*/
bool _hasName = false;
/**
* Return the scope in which this scope is lexically enclosed.
*
* @return the scope in which this scope is lexically enclosed
*/
Scope get enclosingScope => null;
/**
* Return the listener that is to be informed when an error is encountered.
*
* @return the listener that is to be informed when an error is encountered
*/
AnalysisErrorListener get errorListener;
/**
* Add the given element to this scope. If there is already an element with the given name defined
* in this scope, then an error will be generated and the original element will continue to be
* mapped to the name. If there is an element with the given name in an enclosing scope, then a
* warning will be generated but the given element will hide the inherited element.
*
* @param element the element to be added to this scope
*/
void define(Element element) {
String name = _getName(element);
if (name != null && !name.isEmpty) {
if (_definedNames.containsKey(name)) {
errorListener
.onError(getErrorForDuplicate(_definedNames[name], element));
} else {
_definedNames[name] = element;
_hasName = true;
}
}
}
/**
* Add the given element to this scope without checking for duplication or hiding.
*
* @param name the name of the element to be added
* @param element the element to be added to this scope
*/
void defineNameWithoutChecking(String name, Element element) {
_definedNames[name] = element;
_hasName = true;
}
/**
* Add the given element to this scope without checking for duplication or hiding.
*
* @param element the element to be added to this scope
*/
void defineWithoutChecking(Element element) {
_definedNames[_getName(element)] = element;
_hasName = true;
}
/**
* Return the error code to be used when reporting that a name being defined locally conflicts
* with another element of the same name in the local scope.
*
* @param existing the first element to be declared with the conflicting name
* @param duplicate another element declared with the conflicting name
* @return the error code used to report duplicate names within a scope
*/
AnalysisError getErrorForDuplicate(Element existing, Element duplicate) {
// TODO(brianwilkerson) Customize the error message based on the types of
// elements that share the same name.
// TODO(jwren) There are 4 error codes for duplicate, but only 1 is being
// generated.
Source source = duplicate.source;
return new AnalysisError(source, duplicate.nameOffset, duplicate.nameLength,
CompileTimeErrorCode.DUPLICATE_DEFINITION, [existing.displayName]);
}
/**
* Return the source that contains the given identifier, or the source associated with this scope
* if the source containing the identifier could not be determined.
*
* @param identifier the identifier whose source is to be returned
* @return the source that contains the given identifier
*/
Source getSource(AstNode node) {
CompilationUnit unit = node.getAncestor((node) => node is CompilationUnit);
if (unit != null) {
CompilationUnitElement unitElement = unit.element;
if (unitElement != null) {
return unitElement.source;
}
}
return null;
}
/**
* Return the element with which the given name is associated, or `null` if the name is not
* defined within this scope.
*
* @param identifier the identifier node to lookup element for, used to report correct kind of a
* problem and associate problem with
* @param name the name associated with the element to be returned
* @param referencingLibrary the library that contains the reference to the name, used to
* implement library-level privacy
* @return the element with which the given name is associated
*/
Element internalLookup(
Identifier identifier, String name, LibraryElement referencingLibrary);
/**
* Return the element with which the given name is associated, or `null` if the name is not
* defined within this scope. This method only returns elements that are directly defined within
* this scope, not elements that are defined in an enclosing scope.
*
* @param name the name associated with the element to be returned
* @param referencingLibrary the library that contains the reference to the name, used to
* implement library-level privacy
* @return the element with which the given name is associated
*/
Element localLookup(String name, LibraryElement referencingLibrary) {
if (_hasName) {
return _definedNames[name];
}
return null;
}
/**
* Return the element with which the given identifier is associated, or `null` if the name
* is not defined within this scope.
*
* @param identifier the identifier associated with the element to be returned
* @param referencingLibrary the library that contains the reference to the name, used to
* implement library-level privacy
* @return the element with which the given identifier is associated
*/
Element lookup(Identifier identifier, LibraryElement referencingLibrary) =>
internalLookup(identifier, identifier.name, referencingLibrary);
/**
* Return the name that will be used to look up the given element.
*
* @param element the element whose look-up name is to be returned
* @return the name that will be used to look up the given element
*/
String _getName(Element element) {
if (element is MethodElement) {
MethodElement method = element;
if (method.name == "-" && method.parameters.length == 0) {
return UNARY_MINUS;
}
}
return element.name;
}
/**
* Return `true` if the given name is a library-private name.
*
* @param name the name being tested
* @return `true` if the given name is a library-private name
*/
static bool isPrivateName(String name) =>
name != null && StringUtilities.startsWithChar(name, PRIVATE_NAME_PREFIX);
}
/**
* The abstract class `ScopedVisitor` maintains name and label scopes as an AST structure is
* being visited.
*/
abstract class ScopedVisitor extends UnifyingAstVisitor<Object> {
/**
* The element for the library containing the compilation unit being visited.
*/
final LibraryElement definingLibrary;
/**
* The source representing the compilation unit being visited.
*/
final Source source;
/**
* The error listener that will be informed of any errors that are found during resolution.
*/
final AnalysisErrorListener errorListener;
/**
* The scope used to resolve identifiers.
*/
Scope nameScope;
/**
* The object used to access the types from the core library.
*/
final TypeProvider typeProvider;
/**
* The scope used to resolve unlabeled `break` and `continue` statements.
*/
ImplicitLabelScope _implicitLabelScope = ImplicitLabelScope.ROOT;
/**
* The scope used to resolve labels for `break` and `continue` statements, or
* `null` if no labels have been defined in the current context.
*/
LabelScope labelScope;
/**
* The class containing the AST nodes being visited,
* or `null` if we are not in the scope of a class.
*/
ClassElement enclosingClass;
/**
* Initialize a newly created visitor to resolve the nodes in a compilation
* unit.
*
* [definingLibrary] is the element for the library containing the
* compilation unit being visited.
* [source] is the source representing the compilation unit being visited.
* [typeProvider] is the object used to access the types from the core
* library.
* [errorListener] is the error listener that will be informed of any errors
* that are found during resolution.
* [nameScope] is the scope used to resolve identifiers in the node that will
* first be visited. If `null` or unspecified, a new [LibraryScope] will be
* created based on [definingLibrary] and [typeProvider].
*/
ScopedVisitor(
this.definingLibrary, this.source, this.typeProvider, this.errorListener,
{Scope nameScope}) {
if (nameScope == null) {
this.nameScope = new LibraryScope(definingLibrary, errorListener);
} else {
this.nameScope = nameScope;
}
}
/**
* Return the implicit label scope in which the current node is being
* resolved.
*/
ImplicitLabelScope get implicitLabelScope => _implicitLabelScope;
/**
* Replaces the current [Scope] with the enclosing [Scope].
*
* @return the enclosing [Scope].
*/
Scope popNameScope() {
nameScope = nameScope.enclosingScope;
return nameScope;
}
/**
* Pushes a new [Scope] into the visitor.
*
* @return the new [Scope].
*/
Scope pushNameScope() {
Scope newScope = new EnclosedScope(nameScope);
nameScope = newScope;
return nameScope;
}
/**
* Report an error with the given error code and arguments.
*
* @param errorCode the error code of the error to be reported
* @param node the node specifying the location of the error
* @param arguments the arguments to the error, used to compose the error message
*/
void reportErrorForNode(ErrorCode errorCode, AstNode node,
[List<Object> arguments]) {
errorListener.onError(new AnalysisError(
source, node.offset, node.length, errorCode, arguments));
}
/**
* Report an error with the given error code and arguments.
*
* @param errorCode the error code of the error to be reported
* @param offset the offset of the location of the error
* @param length the length of the location of the error
* @param arguments the arguments to the error, used to compose the error message
*/
void reportErrorForOffset(ErrorCode errorCode, int offset, int length,
[List<Object> arguments]) {
errorListener.onError(
new AnalysisError(source, offset, length, errorCode, arguments));
}
/**
* Report an error with the given error code and arguments.
*
* @param errorCode the error code of the error to be reported
* @param token the token specifying the location of the error
* @param arguments the arguments to the error, used to compose the error message
*/
void reportErrorForToken(ErrorCode errorCode, Token token,
[List<Object> arguments]) {
errorListener.onError(new AnalysisError(
source, token.offset, token.length, errorCode, arguments));
}
/**
* Visit the given AST node if it is not null.
*
* @param node the node to be visited
*/
void safelyVisit(AstNode node) {
if (node != null) {
node.accept(this);
}
}
@override
Object visitBlock(Block node) {
Scope outerScope = nameScope;
try {
EnclosedScope enclosedScope = new EnclosedScope(nameScope);
_hideNamesDefinedInBlock(enclosedScope, node);
nameScope = enclosedScope;
super.visitBlock(node);
} finally {
nameScope = outerScope;
}
return null;
}
@override
Object visitBlockFunctionBody(BlockFunctionBody node) {
ImplicitLabelScope implicitOuterScope = _implicitLabelScope;
try {
_implicitLabelScope = ImplicitLabelScope.ROOT;
super.visitBlockFunctionBody(node);
} finally {
_implicitLabelScope = implicitOuterScope;
}
return null;
}
@override
Object visitCatchClause(CatchClause node) {
SimpleIdentifier exception = node.exceptionParameter;
if (exception != null) {
Scope outerScope = nameScope;
try {
nameScope = new EnclosedScope(nameScope);
nameScope.define(exception.staticElement);
SimpleIdentifier stackTrace = node.stackTraceParameter;
if (stackTrace != null) {
nameScope.define(stackTrace.staticElement);
}
super.visitCatchClause(node);
} finally {
nameScope = outerScope;
}
} else {
super.visitCatchClause(node);
}
return null;
}
@override
Object visitClassDeclaration(ClassDeclaration node) {
ClassElement classElement = node.element;
Scope outerScope = nameScope;
try {
if (classElement == null) {
AnalysisEngine.instance.logger.logInformation(
"Missing element for class declaration ${node.name.name} in ${definingLibrary.source.fullName}",
new CaughtException(new AnalysisException(), null));
super.visitClassDeclaration(node);
} else {
ClassElement outerClass = enclosingClass;
try {
enclosingClass = node.element;
nameScope = new TypeParameterScope(nameScope, classElement);
visitClassDeclarationInScope(node);
nameScope = new ClassScope(nameScope, classElement);
visitClassMembersInScope(node);
} finally {
enclosingClass = outerClass;
}
}
} finally {
nameScope = outerScope;
}
return null;
}
void visitClassDeclarationInScope(ClassDeclaration node) {
safelyVisit(node.name);
safelyVisit(node.typeParameters);
safelyVisit(node.extendsClause);
safelyVisit(node.withClause);
safelyVisit(node.implementsClause);
safelyVisit(node.nativeClause);
}
void visitClassMembersInScope(ClassDeclaration node) {
safelyVisit(node.documentationComment);
node.metadata.accept(this);
node.members.accept(this);
}
@override
Object visitClassTypeAlias(ClassTypeAlias node) {
Scope outerScope = nameScope;
try {
ClassElement element = node.element;
nameScope =
new ClassScope(new TypeParameterScope(nameScope, element), element);
super.visitClassTypeAlias(node);
} finally {
nameScope = outerScope;
}
return null;
}
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
ConstructorElement constructorElement = node.element;
Scope outerScope = nameScope;
try {
if (constructorElement == null) {
StringBuffer buffer = new StringBuffer();
buffer.write("Missing element for constructor ");
buffer.write(node.returnType.name);
if (node.name != null) {
buffer.write(".");
buffer.write(node.name.name);
}
buffer.write(" in ");
buffer.write(definingLibrary.source.fullName);
AnalysisEngine.instance.logger.logInformation(buffer.toString(),
new CaughtException(new AnalysisException(), null));
} else {
nameScope = new FunctionScope(nameScope, constructorElement);
}
visitConstructorDeclarationInScope(node);
} finally {
nameScope = outerScope;
}
return null;
}
void visitConstructorDeclarationInScope(ConstructorDeclaration node) {
super.visitConstructorDeclaration(node);
}
@override
Object visitDeclaredIdentifier(DeclaredIdentifier node) {
VariableElement element = node.element;
if (element != null) {
nameScope.define(element);
}
super.visitDeclaredIdentifier(node);
return null;
}
@override
Object visitDoStatement(DoStatement node) {
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
visitStatementInScope(node.body);
safelyVisit(node.condition);
} finally {
_implicitLabelScope = outerImplicitScope;
}
return null;
}
@override
Object visitEnumDeclaration(EnumDeclaration node) {
ClassElement classElement = node.element;
Scope outerScope = nameScope;
try {
if (classElement == null) {
AnalysisEngine.instance.logger.logInformation(
"Missing element for enum declaration ${node.name.name} in ${definingLibrary.source.fullName}",
new CaughtException(new AnalysisException(), null));
super.visitEnumDeclaration(node);
} else {
ClassElement outerClass = enclosingClass;
try {
enclosingClass = node.element;
nameScope = new ClassScope(nameScope, classElement);
visitEnumMembersInScope(node);
} finally {
enclosingClass = outerClass;
}
}
} finally {
nameScope = outerScope;
}
return null;
}
void visitEnumMembersInScope(EnumDeclaration node) {
safelyVisit(node.documentationComment);
node.metadata.accept(this);
node.constants.accept(this);
}
@override
Object visitForEachStatement(ForEachStatement node) {
Scope outerNameScope = nameScope;
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
nameScope = new EnclosedScope(nameScope);
_implicitLabelScope = _implicitLabelScope.nest(node);
visitForEachStatementInScope(node);
} finally {
nameScope = outerNameScope;
_implicitLabelScope = outerImplicitScope;
}
return null;
}
/**
* Visit the given statement after it's scope has been created. This replaces the normal call to
* the inherited visit method so that ResolverVisitor can intervene when type propagation is
* enabled.
*
* @param node the statement to be visited
*/
void visitForEachStatementInScope(ForEachStatement node) {
//
// We visit the iterator before the loop variable because the loop variable
// cannot be in scope while visiting the iterator.
//
safelyVisit(node.identifier);
safelyVisit(node.iterable);
safelyVisit(node.loopVariable);
visitStatementInScope(node.body);
}
@override
Object visitFormalParameterList(FormalParameterList node) {
super.visitFormalParameterList(node);
// We finished resolving function signature, now include formal parameters
// scope. Note: we must not do this if the parent is a
// FunctionTypedFormalParameter, because in that case we aren't finished
// resolving the full function signature, just a part of it.
if (nameScope is FunctionScope &&
node.parent is! FunctionTypedFormalParameter) {
(nameScope as FunctionScope).defineParameters();
}
if (nameScope is FunctionTypeScope) {
(nameScope as FunctionTypeScope).defineParameters();
}
return null;
}
@override
Object visitForStatement(ForStatement node) {
Scope outerNameScope = nameScope;
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
nameScope = new EnclosedScope(nameScope);
_implicitLabelScope = _implicitLabelScope.nest(node);
visitForStatementInScope(node);
} finally {
nameScope = outerNameScope;
_implicitLabelScope = outerImplicitScope;
}
return null;
}
/**
* Visit the given statement after it's scope has been created. This replaces the normal call to
* the inherited visit method so that ResolverVisitor can intervene when type propagation is
* enabled.
*
* @param node the statement to be visited
*/
void visitForStatementInScope(ForStatement node) {
safelyVisit(node.variables);
safelyVisit(node.initialization);
safelyVisit(node.condition);
node.updaters.accept(this);
visitStatementInScope(node.body);
}
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement functionElement = node.element;
if (functionElement != null &&
functionElement.enclosingElement is! CompilationUnitElement) {
nameScope.define(functionElement);
}
Scope outerScope = nameScope;
try {
if (functionElement == null) {
AnalysisEngine.instance.logger.logInformation(
"Missing element for top-level function ${node.name.name} in ${definingLibrary.source.fullName}",
new CaughtException(new AnalysisException(), null));
} else {
nameScope = new FunctionScope(nameScope, functionElement);
}
visitFunctionDeclarationInScope(node);
} finally {
nameScope = outerScope;
}
return null;
}
void visitFunctionDeclarationInScope(FunctionDeclaration node) {
super.visitFunctionDeclaration(node);
}
@override
Object visitFunctionExpression(FunctionExpression node) {
if (node.parent is FunctionDeclaration) {
// We have already created a function scope and don't need to do so again.
super.visitFunctionExpression(node);
} else {
Scope outerScope = nameScope;
try {
ExecutableElement functionElement = node.element;
if (functionElement == null) {
StringBuffer buffer = new StringBuffer();
buffer.write("Missing element for function ");
AstNode parent = node.parent;
while (parent != null) {
if (parent is Declaration) {
Element parentElement = parent.element;
buffer.write(parentElement == null
? "<unknown> "
: "${parentElement.name} ");
}
parent = parent.parent;
}
buffer.write("in ");
buffer.write(definingLibrary.source.fullName);
AnalysisEngine.instance.logger.logInformation(buffer.toString(),
new CaughtException(new AnalysisException(), null));
} else {
nameScope = new FunctionScope(nameScope, functionElement);
}
super.visitFunctionExpression(node);
} finally {
nameScope = outerScope;
}
}
return null;
}
@override
Object visitFunctionTypeAlias(FunctionTypeAlias node) {
Scope outerScope = nameScope;
try {
nameScope = new FunctionTypeScope(nameScope, node.element);
visitFunctionTypeAliasInScope(node);
} finally {
nameScope = outerScope;
}
return null;
}
void visitFunctionTypeAliasInScope(FunctionTypeAlias node) {
super.visitFunctionTypeAlias(node);
}
@override
Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
Scope outerScope = nameScope;
try {
ParameterElement parameterElement = node.element;
if (parameterElement == null) {
AnalysisEngine.instance.logger.logInformation(
"Missing element for function typed formal parameter ${node.identifier.name} in ${definingLibrary.source.fullName}",
new CaughtException(new AnalysisException(), null));
} else {
nameScope = new EnclosedScope(nameScope);
for (TypeParameterElement typeParameter
in parameterElement.typeParameters) {
nameScope.define(typeParameter);
}
}
super.visitFunctionTypedFormalParameter(node);
} finally {
nameScope = outerScope;
}
return null;
}
@override
Object visitIfStatement(IfStatement node) {
safelyVisit(node.condition);
visitStatementInScope(node.thenStatement);
visitStatementInScope(node.elseStatement);
return null;
}
@override
Object visitLabeledStatement(LabeledStatement node) {
LabelScope outerScope = _addScopesFor(node.labels, node.unlabeled);
try {
super.visitLabeledStatement(node);
} finally {
labelScope = outerScope;
}
return null;
}
@override
Object visitMethodDeclaration(MethodDeclaration node) {
Scope outerScope = nameScope;
try {
ExecutableElement methodElement = node.element;
if (methodElement == null) {
AnalysisEngine.instance.logger.logInformation(
"Missing element for method ${node.name.name} in ${definingLibrary.source.fullName}",
new CaughtException(new AnalysisException(), null));
} else {
nameScope = new FunctionScope(nameScope, methodElement);
}
visitMethodDeclarationInScope(node);
} finally {
nameScope = outerScope;
}
return null;
}
void visitMethodDeclarationInScope(MethodDeclaration node) {
super.visitMethodDeclaration(node);
}
/**
* Visit the given statement after it's scope has been created. This is used by ResolverVisitor to
* correctly visit the 'then' and 'else' statements of an 'if' statement.
*
* @param node the statement to be visited
*/
void visitStatementInScope(Statement node) {
if (node is Block) {
// Don't create a scope around a block because the block will create it's
// own scope.
visitBlock(node);
} else if (node != null) {
Scope outerNameScope = nameScope;
try {
nameScope = new EnclosedScope(nameScope);
node.accept(this);
} finally {
nameScope = outerNameScope;
}
}
}
@override
Object visitSwitchCase(SwitchCase node) {
node.expression.accept(this);
Scope outerNameScope = nameScope;
try {
nameScope = new EnclosedScope(nameScope);
node.statements.accept(this);
} finally {
nameScope = outerNameScope;
}
return null;
}
@override
Object visitSwitchDefault(SwitchDefault node) {
Scope outerNameScope = nameScope;
try {
nameScope = new EnclosedScope(nameScope);
node.statements.accept(this);
} finally {
nameScope = outerNameScope;
}
return null;
}
@override
Object visitSwitchStatement(SwitchStatement node) {
LabelScope outerScope = labelScope;
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
for (SwitchMember member in node.members) {
for (Label label in member.labels) {
SimpleIdentifier labelName = label.label;
LabelElement labelElement = labelName.staticElement as LabelElement;
labelScope =
new LabelScope(labelScope, labelName.name, member, labelElement);
}
}
super.visitSwitchStatement(node);
} finally {
labelScope = outerScope;
_implicitLabelScope = outerImplicitScope;
}
return null;
}
@override
Object visitVariableDeclaration(VariableDeclaration node) {
super.visitVariableDeclaration(node);
if (node.parent.parent is! TopLevelVariableDeclaration &&
node.parent.parent is! FieldDeclaration) {
VariableElement element = node.element;
if (element != null) {
nameScope.define(element);
}
}
return null;
}
@override
Object visitWhileStatement(WhileStatement node) {
safelyVisit(node.condition);
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
visitStatementInScope(node.body);
} finally {
_implicitLabelScope = outerImplicitScope;
}
return null;
}
/**
* Add scopes for each of the given labels.
*
* @param labels the labels for which new scopes are to be added
* @return the scope that was in effect before the new scopes were added
*/
LabelScope _addScopesFor(NodeList<Label> labels, AstNode node) {
LabelScope outerScope = labelScope;
for (Label label in labels) {
SimpleIdentifier labelNameNode = label.label;
String labelName = labelNameNode.name;
LabelElement labelElement = labelNameNode.staticElement as LabelElement;
labelScope = new LabelScope(labelScope, labelName, node, labelElement);
}
return outerScope;
}
/**
* Marks the local declarations of the given [Block] hidden in the enclosing scope.
* According to the scoping rules name is hidden if block defines it, but name is defined after
* its declaration statement.
*/
void _hideNamesDefinedInBlock(EnclosedScope scope, Block block) {
NodeList<Statement> statements = block.statements;
int statementCount = statements.length;
for (int i = 0; i < statementCount; i++) {
Statement statement = statements[i];
if (statement is VariableDeclarationStatement) {
VariableDeclarationStatement vds = statement;
NodeList<VariableDeclaration> variables = vds.variables.variables;
int variableCount = variables.length;
for (int j = 0; j < variableCount; j++) {
scope.hide(variables[j].element);
}
} else if (statement is FunctionDeclarationStatement) {
FunctionDeclarationStatement fds = statement;
scope.hide(fds.functionDeclaration.element);
}
}
}
}
/**
* Instances of this class manage the knowledge of what the set of subtypes are for a given type.
*/
class SubtypeManager {
/**
* A map between [ClassElement]s and a set of [ClassElement]s that are subtypes of the
* key.
*/
HashMap<ClassElement, HashSet<ClassElement>> _subtypeMap =
new HashMap<ClassElement, HashSet<ClassElement>>();
/**
* The set of all [LibraryElement]s that have been visited by the manager. This is used both
* to prevent infinite loops in the recursive methods, and also as a marker for the scope of the
* libraries visited by this manager.
*/
HashSet<LibraryElement> _visitedLibraries = new HashSet<LibraryElement>();
/**
* Given some [ClassElement], return the set of all subtypes, and subtypes of subtypes.
*
* @param classElement the class to recursively return the set of subtypes of
*/
HashSet<ClassElement> computeAllSubtypes(ClassElement classElement) {
// Ensure that we have generated the subtype map for the library
_computeSubtypesInLibrary(classElement.library);
// use the subtypeMap to compute the set of all subtypes and subtype's
// subtypes
HashSet<ClassElement> allSubtypes = new HashSet<ClassElement>();
_safelyComputeAllSubtypes(
classElement, new HashSet<ClassElement>(), allSubtypes);
return allSubtypes;
}
/**
* Given some [LibraryElement], visit all of the types in the library, the passed library,
* and any imported libraries, will be in the [visitedLibraries] set.
*
* @param libraryElement the library to visit, it it hasn't been visited already
*/
void ensureLibraryVisited(LibraryElement libraryElement) {
_computeSubtypesInLibrary(libraryElement);
}
/**
* Given some [ClassElement], this method adds all of the pairs combinations of itself and
* all of its supertypes to the [subtypeMap] map.
*
* @param classElement the class element
*/
void _computeSubtypesInClass(ClassElement classElement) {
InterfaceType supertypeType = classElement.supertype;
if (supertypeType != null) {
ClassElement supertypeElement = supertypeType.element;
if (supertypeElement != null) {
_putInSubtypeMap(supertypeElement, classElement);
}
}
List<InterfaceType> interfaceTypes = classElement.interfaces;
for (InterfaceType interfaceType in interfaceTypes) {
ClassElement interfaceElement = interfaceType.element;
if (interfaceElement != null) {
_putInSubtypeMap(interfaceElement, classElement);
}
}
List<InterfaceType> mixinTypes = classElement.mixins;
for (InterfaceType mixinType in mixinTypes) {
ClassElement mixinElement = mixinType.element;
if (mixinElement != null) {
_putInSubtypeMap(mixinElement, classElement);
}
}
}
/**
* Given some [CompilationUnitElement], this method calls
* [computeAllSubtypes] on all of the [ClassElement]s in the
* compilation unit.
*
* @param unitElement the compilation unit element
*/
void _computeSubtypesInCompilationUnit(CompilationUnitElement unitElement) {
List<ClassElement> classElements = unitElement.types;
for (ClassElement classElement in classElements) {
_computeSubtypesInClass(classElement);
}
}
/**
* Given some [LibraryElement], this method calls
* [computeAllSubtypes] on all of the [ClassElement]s in the
* compilation unit, and itself for all imported and exported libraries. All visited libraries are
* added to the [visitedLibraries] set.
*
* @param libraryElement the library element
*/
void _computeSubtypesInLibrary(LibraryElement libraryElement) {
if (libraryElement == null || _visitedLibraries.contains(libraryElement)) {
return;
}
_visitedLibraries.add(libraryElement);
_computeSubtypesInCompilationUnit(libraryElement.definingCompilationUnit);
List<CompilationUnitElement> parts = libraryElement.parts;
for (CompilationUnitElement part in parts) {
_computeSubtypesInCompilationUnit(part);
}
List<LibraryElement> imports = libraryElement.importedLibraries;
for (LibraryElement importElt in imports) {
_computeSubtypesInLibrary(importElt.library);
}
List<LibraryElement> exports = libraryElement.exportedLibraries;
for (LibraryElement exportElt in exports) {
_computeSubtypesInLibrary(exportElt.library);
}
}
/**
* Add some key/ value pair into the [subtypeMap] map.
*
* @param supertypeElement the key for the [subtypeMap] map
* @param subtypeElement the value for the [subtypeMap] map
*/
void _putInSubtypeMap(
ClassElement supertypeElement, ClassElement subtypeElement) {
HashSet<ClassElement> subtypes = _subtypeMap[supertypeElement];
if (subtypes == null) {
subtypes = new HashSet<ClassElement>();
_subtypeMap[supertypeElement] = subtypes;
}
subtypes.add(subtypeElement);
}
/**
* Given some [ClassElement] and a [HashSet<ClassElement>], this method recursively
* adds all of the subtypes of the [ClassElement] to the passed array.
*
* @param classElement the type to compute the set of subtypes of
* @param visitedClasses the set of class elements that this method has already recursively seen
* @param allSubtypes the computed set of subtypes of the passed class element
*/
void _safelyComputeAllSubtypes(ClassElement classElement,
HashSet<ClassElement> visitedClasses, HashSet<ClassElement> allSubtypes) {
if (!visitedClasses.add(classElement)) {
// if this class has already been called on this class element
return;
}
HashSet<ClassElement> subtypes = _subtypeMap[classElement];
if (subtypes == null) {
return;
}
for (ClassElement subtype in subtypes) {
_safelyComputeAllSubtypes(subtype, visitedClasses, allSubtypes);
}
allSubtypes.addAll(subtypes);
}
}
/**
* Instances of the class `ToDoFinder` find to-do comments in Dart code.
*/
class ToDoFinder {
/**
* The error reporter by which to-do comments will be reported.
*/
final ErrorReporter _errorReporter;
/**
* Initialize a newly created to-do finder to report to-do comments to the given reporter.
*
* @param errorReporter the error reporter by which to-do comments will be reported
*/
ToDoFinder(this._errorReporter);
/**
* Search the comments in the given compilation unit for to-do comments and report an error for
* each.
*
* @param unit the compilation unit containing the to-do comments
*/
void findIn(CompilationUnit unit) {
_gatherTodoComments(unit.beginToken);
}
/**
* Search the comment tokens reachable from the given token and create errors for each to-do
* comment.
*
* @param token the head of the list of tokens being searched
*/
void _gatherTodoComments(Token token) {
while (token != null && token.type != TokenType.EOF) {
Token commentToken = token.precedingComments;
while (commentToken != null) {
if (commentToken.type == TokenType.SINGLE_LINE_COMMENT ||
commentToken.type == TokenType.MULTI_LINE_COMMENT) {
_scrapeTodoComment(commentToken);
}
commentToken = commentToken.next;
}
token = token.next;
}
}
/**
* Look for user defined tasks in comments and convert them into info level analysis issues.
*
* @param commentToken the comment token to analyze
*/
void _scrapeTodoComment(Token commentToken) {
JavaPatternMatcher matcher =
new JavaPatternMatcher(TodoCode.TODO_REGEX, commentToken.lexeme);
if (matcher.find()) {
int offset =
commentToken.offset + matcher.start() + matcher.group(1).length;
int length = matcher.group(2).length;
_errorReporter.reportErrorForOffset(
TodoCode.TODO, offset, length, [matcher.group(2)]);
}
}
}
/**
* Instances of the class `TypeOverrideManager` manage the ability to override the type of an
* element within a given context.
*/
class TypeOverrideManager {
/**
* The current override scope, or `null` if no scope has been entered.
*/
TypeOverrideManager_TypeOverrideScope currentScope;
/**
* Apply a set of overrides that were previously captured.
*
* @param overrides the overrides to be applied
*/
void applyOverrides(Map<VariableElement, DartType> overrides) {
if (currentScope == null) {
throw new IllegalStateException("Cannot apply overrides without a scope");
}
currentScope.applyOverrides(overrides);
}
/**
* Return a table mapping the elements whose type is overridden in the current scope to the
* overriding type.
*
* @return the overrides in the current scope
*/
Map<VariableElement, DartType> captureLocalOverrides() {
if (currentScope == null) {
throw new IllegalStateException(
"Cannot capture local overrides without a scope");
}
return currentScope.captureLocalOverrides();
}
/**
* Return a map from the elements for the variables in the given list that have their types
* overridden to the overriding type.
*
* @param variableList the list of variables whose overriding types are to be captured
* @return a table mapping elements to their overriding types
*/
Map<VariableElement, DartType> captureOverrides(
VariableDeclarationList variableList) {
if (currentScope == null) {
throw new IllegalStateException(
"Cannot capture overrides without a scope");
}
return currentScope.captureOverrides(variableList);
}
/**
* Enter a new override scope.
*/
void enterScope() {
currentScope = new TypeOverrideManager_TypeOverrideScope(currentScope);
}
/**
* Exit the current override scope.
*/
void exitScope() {
if (currentScope == null) {
throw new IllegalStateException("No scope to exit");
}
currentScope = currentScope._outerScope;
}
/**
* Return the best type information available for the given element. If the type of the element
* has been overridden, then return the overriding type. Otherwise, return the static type.
*
* @param element the element for which type information is to be returned
* @return the best type information available for the given element
*/
DartType getBestType(VariableElement element) {
DartType bestType = getType(element);
return bestType == null ? element.type : bestType;
}
/**
* Return the overridden type of the given element, or `null` if the type of the element has
* not been overridden.
*
* @param element the element whose type might have been overridden
* @return the overridden type of the given element
*/
DartType getType(Element element) {
if (currentScope == null) {
return null;
}
return currentScope.getType(element);
}
/**
* Update overrides assuming [perBranchOverrides] is the collection of
* per-branch overrides for *all* branches flowing into a join point.
*
* If a variable type in any of branches is not the same as its type before
* the branching, then its propagated type is reset to `null`.
*/
void mergeOverrides(List<Map<VariableElement, DartType>> perBranchOverrides) {
for (Map<VariableElement, DartType> branch in perBranchOverrides) {
branch.forEach((VariableElement variable, DartType branchType) {
DartType currentType = currentScope.getType(variable);
if (currentType != branchType) {
currentScope.resetType(variable);
}
});
}
}
/**
* Set the overridden type of the given element to the given type
*
* @param element the element whose type might have been overridden
* @param type the overridden type of the given element
*/
void setType(VariableElement element, DartType type) {
if (currentScope == null) {
throw new IllegalStateException("Cannot override without a scope");
}
currentScope.setType(element, type);
}
}
/**
* Instances of the class `TypeOverrideScope` represent a scope in which the types of
* elements can be overridden.
*/
class TypeOverrideManager_TypeOverrideScope {
/**
* The outer scope in which types might be overridden.
*/
final TypeOverrideManager_TypeOverrideScope _outerScope;
/**
* A table mapping elements to the overridden type of that element.
*/
Map<VariableElement, DartType> _overridenTypes =
new HashMap<VariableElement, DartType>();
/**
* Initialize a newly created scope to be an empty child of the given scope.
*
* @param outerScope the outer scope in which types might be overridden
*/
TypeOverrideManager_TypeOverrideScope(this._outerScope);
/**
* Apply a set of overrides that were previously captured.
*
* @param overrides the overrides to be applied
*/
void applyOverrides(Map<VariableElement, DartType> overrides) {
_overridenTypes.addAll(overrides);
}
/**
* Return a table mapping the elements whose type is overridden in the current scope to the
* overriding type.
*
* @return the overrides in the current scope
*/
Map<VariableElement, DartType> captureLocalOverrides() => _overridenTypes;
/**
* Return a map from the elements for the variables in the given list that have their types
* overridden to the overriding type.
*
* @param variableList the list of variables whose overriding types are to be captured
* @return a table mapping elements to their overriding types
*/
Map<VariableElement, DartType> captureOverrides(
VariableDeclarationList variableList) {
Map<VariableElement, DartType> overrides =
new HashMap<VariableElement, DartType>();
if (variableList.isConst || variableList.isFinal) {
for (VariableDeclaration variable in variableList.variables) {
VariableElement element = variable.element;
if (element != null) {
DartType type = _overridenTypes[element];
if (type != null) {
overrides[element] = type;
}
}
}
}
return overrides;
}
/**
* Return the overridden type of the given element, or `null` if the type of the element
* has not been overridden.
*
* @param element the element whose type might have been overridden
* @return the overridden type of the given element
*/
DartType getType(Element element) {
if (element is PropertyAccessorElement) {
element = (element as PropertyAccessorElement).variable;
}
DartType type = _overridenTypes[element];
if (_overridenTypes.containsKey(element)) {
return type;
}
if (type != null) {
return type;
} else if (_outerScope != null) {
return _outerScope.getType(element);
}
return null;
}
/**
* Clears the overridden type of the given [element].
*/
void resetType(VariableElement element) {
_overridenTypes[element] = null;
}
/**
* Set the overridden type of the given element to the given type
*
* @param element the element whose type might have been overridden
* @param type the overridden type of the given element
*/
void setType(VariableElement element, DartType type) {
_overridenTypes[element] = type;
}
}
/**
* Instances of the class `TypeParameterScope` implement the scope defined by the type
* parameters in a class.
*/
class TypeParameterScope 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
*/
TypeParameterScope(Scope enclosingScope, ClassElement typeElement)
: super(enclosingScope) {
if (typeElement == null) {
throw new IllegalArgumentException("class element cannot be null");
}
_defineTypeParameters(typeElement);
}
/**
* Define the type parameters for the class.
*
* @param typeElement the element representing the type represented by this scope
*/
void _defineTypeParameters(ClassElement typeElement) {
for (TypeParameterElement typeParameter in typeElement.typeParameters) {
define(typeParameter);
}
}
}
/**
* Instances of the class `TypePromotionManager` manage the ability to promote types of local
* variables and formal parameters from their declared types based on control flow.
*/
class TypePromotionManager {
/**
* The current promotion scope, or `null` if no scope has been entered.
*/
TypePromotionManager_TypePromoteScope currentScope;
/**
* Returns the elements with promoted types.
*/
Iterable<Element> get promotedElements => currentScope.promotedElements;
/**
* Enter a new promotions scope.
*/
void enterScope() {
currentScope = new TypePromotionManager_TypePromoteScope(currentScope);
}
/**
* Exit the current promotion scope.
*/
void exitScope() {
if (currentScope == null) {
throw new IllegalStateException("No scope to exit");
}
currentScope = currentScope._outerScope;
}
/**
* Returns static type of the given variable - declared or promoted.
*
* @return the static type of the given variable - declared or promoted
*/
DartType getStaticType(VariableElement variable) {
DartType staticType = getType(variable);
if (staticType == null) {
staticType = variable.type;
}
return staticType;
}
/**
* Return the promoted type of the given element, or `null` if the type of the element has
* not been promoted.
*
* @param element the element whose type might have been promoted
* @return the promoted type of the given element
*/
DartType getType(Element element) {
if (currentScope == null) {
return null;
}
return currentScope.getType(element);
}
/**
* Set the promoted type of the given element to the given type.
*
* @param element the element whose type might have been promoted
* @param type the promoted type of the given element
*/
void setType(Element element, DartType type) {
if (currentScope == null) {
throw new IllegalStateException("Cannot promote without a scope");
}
currentScope.setType(element, type);
}
}
/**
* Instances of the class `TypePromoteScope` represent a scope in which the types of
* elements can be promoted.
*/
class TypePromotionManager_TypePromoteScope {
/**
* The outer scope in which types might be promoter.
*/
final TypePromotionManager_TypePromoteScope _outerScope;
/**
* A table mapping elements to the promoted type of that element.
*/
HashMap<Element, DartType> _promotedTypes = new HashMap<Element, DartType>();
/**
* Initialize a newly created scope to be an empty child of the given scope.
*
* @param outerScope the outer scope in which types might be promoted
*/
TypePromotionManager_TypePromoteScope(this._outerScope);
/**
* Returns the elements with promoted types.
*/
Iterable<Element> get promotedElements => _promotedTypes.keys.toSet();
/**
* Return the promoted type of the given element, or `null` if the type of the element has
* not been promoted.
*
* @param element the element whose type might have been promoted
* @return the promoted type of the given element
*/
DartType getType(Element element) {
DartType type = _promotedTypes[element];
if (type == null && element is PropertyAccessorElement) {
type = _promotedTypes[element.variable];
}
if (type != null) {
return type;
} else if (_outerScope != null) {
return _outerScope.getType(element);
}
return null;
}
/**
* Set the promoted type of the given element to the given type.
*
* @param element the element whose type might have been promoted
* @param type the promoted type of the given element
*/
void setType(Element element, DartType type) {
_promotedTypes[element] = type;
}
}
/**
* The interface `TypeProvider` defines the behavior of objects that provide access to types
* defined by the language.
*/
abstract class TypeProvider {
/**
* Return the type representing the built-in type 'bool'.
*/
InterfaceType get boolType;
/**
* Return the type representing the type 'bottom'.
*/
DartType get bottomType;
/**
* Return the type representing the built-in type 'Deprecated'.
*/
InterfaceType get deprecatedType;
/**
* Return the type representing the built-in type 'double'.
*/
InterfaceType get doubleType;
/**
* Return the type representing the built-in type 'dynamic'.
*/
DartType get dynamicType;
/**
* Return the type representing the built-in type 'Function'.
*/
InterfaceType get functionType;
/**
* Return the type representing 'Future<dynamic>'.
*/
InterfaceType get futureDynamicType;
/**
* Return the type representing 'Future<Null>'.
*/
InterfaceType get futureNullType;
/**
* Return the type representing the built-in type 'Future'.
*/
InterfaceType get futureType;
/**
* Return the type representing the built-in type 'int'.
*/
InterfaceType get intType;
/**
* Return the type representing the type 'Iterable<dynamic>'.
*/
InterfaceType get iterableDynamicType;
/**
* Return the type representing the built-in type 'Iterable'.
*/
InterfaceType get iterableType;
/**
* Return the type representing the built-in type 'List'.
*/
InterfaceType get listType;
/**
* Return the type representing the built-in type 'Map'.
*/
InterfaceType get mapType;
/**
* Return a list containing all of the types that cannot be either extended or
* implemented.
*/
List<InterfaceType> get nonSubtypableTypes;
/**
* Return a [DartObjectImpl] representing the `null` object.
*/
DartObjectImpl get nullObject;
/**
* Return the type representing the built-in type 'Null'.
*/
InterfaceType get nullType;
/**
* Return the type representing the built-in type 'num'.
*/
InterfaceType get numType;
/**
* Return the type representing the built-in type 'Object'.
*/
InterfaceType get objectType;
/**
* Return the type representing the built-in type 'StackTrace'.
*/
InterfaceType get stackTraceType;
/**
* Return the type representing 'Stream<dynamic>'.
*/
InterfaceType get streamDynamicType;
/**
* Return the type representing the built-in type 'Stream'.
*/
InterfaceType get streamType;
/**
* Return the type representing the built-in type 'String'.
*/
InterfaceType get stringType;
/**
* Return the type representing the built-in type 'Symbol'.
*/
InterfaceType get symbolType;
/**
* Return the type representing the built-in type 'Type'.
*/
InterfaceType get typeType;
/**
* Return the type representing typenames that can't be resolved.
*/
DartType get undefinedType;
}
/**
* Instances of the class `TypeProviderImpl` provide access to types defined by the language
* by looking for those types in the element model for the core library.
*/
class TypeProviderImpl implements TypeProvider {
/**
* The type representing the built-in type 'bool'.
*/
InterfaceType _boolType;
/**
* The type representing the type 'bottom'.
*/
DartType _bottomType;
/**
* The type representing the built-in type 'double'.
*/
InterfaceType _doubleType;
/**
* The type representing the built-in type 'Deprecated'.
*/
InterfaceType _deprecatedType;
/**
* The type representing the built-in type 'dynamic'.
*/
DartType _dynamicType;
/**
* The type representing the built-in type 'Function'.
*/
InterfaceType _functionType;
/**
* The type representing 'Future<dynamic>'.
*/
InterfaceType _futureDynamicType;
/**
* The type representing 'Future<Null>'.
*/
InterfaceType _futureNullType;
/**
* The type representing the built-in type 'Future'.
*/
InterfaceType _futureType;
/**
* The type representing the built-in type 'int'.
*/
InterfaceType _intType;
/**
* The type representing 'Iterable<dynamic>'.
*/
InterfaceType _iterableDynamicType;
/**
* The type representing the built-in type 'Iterable'.
*/
InterfaceType _iterableType;
/**
* The type representing the built-in type 'List'.
*/
InterfaceType _listType;
/**
* The type representing the built-in type 'Map'.
*/
InterfaceType _mapType;
/**
* An shared object representing the value 'null'.
*/
DartObjectImpl _nullObject;
/**
* The type representing the type 'Null'.
*/
InterfaceType _nullType;
/**
* The type representing the built-in type 'num'.
*/
InterfaceType _numType;
/**
* The type representing the built-in type 'Object'.
*/
InterfaceType _objectType;
/**
* The type representing the built-in type 'StackTrace'.
*/
InterfaceType _stackTraceType;
/**
* The type representing 'Stream<dynamic>'.
*/
InterfaceType _streamDynamicType;
/**
* The type representing the built-in type 'Stream'.
*/
InterfaceType _streamType;
/**
* The type representing the built-in type 'String'.
*/
InterfaceType _stringType;
/**
* The type representing the built-in type 'Symbol'.
*/
InterfaceType _symbolType;
/**
* The type representing the built-in type 'Type'.
*/
InterfaceType _typeType;
/**
* The type representing typenames that can't be resolved.
*/
DartType _undefinedType;
/**
* Initialize a newly created type provider to provide the types defined in
* the given [coreLibrary] and [asyncLibrary].
*/
TypeProviderImpl(LibraryElement coreLibrary, LibraryElement asyncLibrary) {
Namespace coreNamespace =
new NamespaceBuilder().createPublicNamespaceForLibrary(coreLibrary);
Namespace asyncNamespace =
new NamespaceBuilder().createPublicNamespaceForLibrary(asyncLibrary);
_initializeFrom(coreNamespace, asyncNamespace);
}
/**
* Initialize a newly created type provider to provide the types defined in
* the given [Namespace]s.
*/
TypeProviderImpl.forNamespaces(
Namespace coreNamespace, Namespace asyncNamespace) {
_initializeFrom(coreNamespace, asyncNamespace);
}
@override
InterfaceType get boolType => _boolType;
@override
DartType get bottomType => _bottomType;
@override
InterfaceType get deprecatedType => _deprecatedType;
@override
InterfaceType get doubleType => _doubleType;
@override
DartType get dynamicType => _dynamicType;
@override
InterfaceType get functionType => _functionType;
@override
InterfaceType get futureDynamicType => _futureDynamicType;
@override
InterfaceType get futureNullType => _futureNullType;
@override
InterfaceType get futureType => _futureType;
@override
InterfaceType get intType => _intType;
@override
InterfaceType get iterableDynamicType => _iterableDynamicType;
@override
InterfaceType get iterableType => _iterableType;
@override
InterfaceType get listType => _listType;
@override
InterfaceType get mapType => _mapType;
@override
List<InterfaceType> get nonSubtypableTypes => <InterfaceType>[
nullType,
numType,
intType,
doubleType,
boolType,
stringType
];
@override
DartObjectImpl get nullObject {
if (_nullObject == null) {
_nullObject = new DartObjectImpl(nullType, NullState.NULL_STATE);
}
return _nullObject;
}
@override
InterfaceType get nullType => _nullType;
@override
InterfaceType get numType => _numType;
@override
InterfaceType get objectType => _objectType;
@override
InterfaceType get stackTraceType => _stackTraceType;
@override
InterfaceType get streamDynamicType => _streamDynamicType;
@override
InterfaceType get streamType => _streamType;
@override
InterfaceType get stringType => _stringType;
@override
InterfaceType get symbolType => _symbolType;
@override
InterfaceType get typeType => _typeType;
@override
DartType get undefinedType => _undefinedType;
/**
* Return the type with the given name from the given namespace, or `null` if there is no
* class with the given name.
*
* @param namespace the namespace in which to search for the given name
* @param typeName the name of the type being searched for
* @return the type that was found
*/
InterfaceType _getType(Namespace namespace, String typeName) {
Element element = namespace.get(typeName);
if (element == null) {
AnalysisEngine.instance.logger
.logInformation("No definition of type $typeName");
return null;
}
return (element as ClassElement).type;
}
/**
* Initialize the types provided by this type provider from the given
* [Namespace]s.
*/
void _initializeFrom(Namespace coreNamespace, Namespace asyncNamespace) {
_boolType = _getType(coreNamespace, "bool");
_bottomType = BottomTypeImpl.instance;
_deprecatedType = _getType(coreNamespace, "Deprecated");
_doubleType = _getType(coreNamespace, "double");
_dynamicType = DynamicTypeImpl.instance;
_functionType = _getType(coreNamespace, "Function");
_futureType = _getType(asyncNamespace, "Future");
_intType = _getType(coreNamespace, "int");
_iterableType = _getType(coreNamespace, "Iterable");
_listType = _getType(coreNamespace, "List");
_mapType = _getType(coreNamespace, "Map");
_nullType = _getType(coreNamespace, "Null");
_numType = _getType(coreNamespace, "num");
_objectType = _getType(coreNamespace, "Object");
_stackTraceType = _getType(coreNamespace, "StackTrace");
_streamType = _getType(asyncNamespace, "Stream");
_stringType = _getType(coreNamespace, "String");
_symbolType = _getType(coreNamespace, "Symbol");
_typeType = _getType(coreNamespace, "Type");
_undefinedType = UndefinedTypeImpl.instance;
_futureDynamicType = _futureType.substitute4(<DartType>[_dynamicType]);
_futureNullType = _futureType.substitute4(<DartType>[_nullType]);
_iterableDynamicType = _iterableType.substitute4(<DartType>[_dynamicType]);
_streamDynamicType = _streamType.substitute4(<DartType>[_dynamicType]);
}
}
/**
* Instances of the class `TypeResolverVisitor` are used to resolve the types associated with
* the elements in the element model. This includes the types of superclasses, mixins, interfaces,
* fields, methods, parameters, and local variables. As a side-effect, this also finishes building
* the type hierarchy.
*/
class TypeResolverVisitor extends ScopedVisitor {
/**
* The type representing the type 'dynamic'.
*/
DartType _dynamicType;
/**
* The type representing typenames that can't be resolved.
*/
DartType _undefinedType;
/**
* The flag specifying if currently visited class references 'super' expression.
*/
bool _hasReferenceToSuper = false;
/**
* True if we're analyzing in strong mode.
*/
bool _strongMode;
/**
* Initialize a newly created visitor to resolve the nodes in an AST node.
*
* [definingLibrary] is the element for the library containing the node being
* visited.
* [source] is the source representing the compilation unit containing the
* node being visited.
* [typeProvider] is the object used to access the types from the core
* library.
* [errorListener] is the error listener that will be informed of any errors
* that are found during resolution.
* [nameScope] is the scope used to resolve identifiers in the node that will
* first be visited. If `null` or unspecified, a new [LibraryScope] will be
* created based on [definingLibrary] and [typeProvider].
*/
TypeResolverVisitor(LibraryElement definingLibrary, Source source,
TypeProvider typeProvider, AnalysisErrorListener errorListener,
{Scope nameScope})
: super(definingLibrary, source, typeProvider, errorListener,
nameScope: nameScope) {
_dynamicType = typeProvider.dynamicType;
_undefinedType = typeProvider.undefinedType;
_strongMode = definingLibrary.context.analysisOptions.strongMode;
}
@override
Object visitAnnotation(Annotation node) {
//
// Visit annotations, if the annotation is @proxy, on a class, and "proxy"
// resolves to the proxy annotation in dart.core, then create create the
// ElementAnnotationImpl and set it as the metadata on the enclosing class.
//
// Element resolution is done in the ElementResolver, and this work will be
// done in the general case for all annotations in the ElementResolver.
// The reason we resolve this particular element early is so that
// ClassElement.isProxy() returns the correct information during all
// phases of the ElementResolver.
//
super.visitAnnotation(node);
Identifier identifier = node.name;
if (identifier.name.endsWith(ElementAnnotationImpl.PROXY_VARIABLE_NAME) &&
node.parent is ClassDeclaration) {
Element element = nameScope.lookup(identifier, definingLibrary);
if (element != null &&
element.library.isDartCore &&
element is PropertyAccessorElement) {
// This is the @proxy from dart.core
ClassDeclaration classDeclaration = node.parent as ClassDeclaration;
ElementAnnotationImpl elementAnnotation =
new ElementAnnotationImpl(element);
node.elementAnnotation = elementAnnotation;
(classDeclaration.element as ClassElementImpl).metadata =
<ElementAnnotationImpl>[elementAnnotation];
}
}
return null;
}
@override
Object visitCatchClause(CatchClause node) {
super.visitCatchClause(node);
SimpleIdentifier exception = node.exceptionParameter;
if (exception != null) {
// If an 'on' clause is provided the type of the exception parameter is
// the type in the 'on' clause. Otherwise, the type of the exception
// parameter is 'Object'.
TypeName exceptionTypeName = node.exceptionType;
DartType exceptionType;
if (exceptionTypeName == null) {
exceptionType = typeProvider.dynamicType;
} else {
exceptionType = _getType(exceptionTypeName);
}
_recordType(exception, exceptionType);
Element element = exception.staticElement;
if (element is VariableElementImpl) {
element.type = exceptionType;
} else {
// TODO(brianwilkerson) Report the internal error
}
}
SimpleIdentifier stackTrace = node.stackTraceParameter;
if (stackTrace != null) {
_recordType(stackTrace, typeProvider.stackTraceType);
Element element = stackTrace.staticElement;
if (element is VariableElementImpl) {
element.type = typeProvider.stackTraceType;
} else {
// TODO(brianwilkerson) Report the internal error
}
}
return null;
}
@override
Object visitClassDeclaration(ClassDeclaration node) {
_hasReferenceToSuper = false;
super.visitClassDeclaration(node);
ClassElementImpl classElement = _getClassElement(node.name);
if (classElement != null) {
// Clear this flag, as we just invalidated any inferred member types.
classElement.hasBeenInferred = false;
classElement.hasReferenceToSuper = _hasReferenceToSuper;
}
return null;
}
@override
void visitClassDeclarationInScope(ClassDeclaration node) {
super.visitClassDeclarationInScope(node);
ExtendsClause extendsClause = node.extendsClause;
WithClause withClause = node.withClause;
ImplementsClause implementsClause = node.implementsClause;
ClassElementImpl classElement = _getClassElement(node.name);
InterfaceType superclassType = null;
if (extendsClause != null) {
ErrorCode errorCode = (withClause == null
? CompileTimeErrorCode.EXTENDS_NON_CLASS
: CompileTimeErrorCode.MIXIN_WITH_NON_CLASS_SUPERCLASS);
superclassType = _resolveType(extendsClause.superclass, errorCode,
CompileTimeErrorCode.EXTENDS_ENUM, errorCode);
if (!identical(superclassType, typeProvider.objectType)) {
classElement.validMixin = false;
}
}
if (classElement != null) {
if (superclassType == null) {
InterfaceType objectType = typeProvider.objectType;
if (!identical(classElement.type, objectType)) {
superclassType = objectType;
}
}
classElement.supertype = superclassType;
}
_resolve(classElement, withClause, implementsClause);
return null;
}
@override
void visitClassMembersInScope(ClassDeclaration node) {
//
// Process field declarations before constructors and methods so that the
// types of field formal parameters can be correctly resolved.
//
List<ClassMember> nonFields = new List<ClassMember>();
node.visitChildren(
new _TypeResolverVisitor_visitClassMembersInScope(this, nonFields));
int count = nonFields.length;
for (int i = 0; i < count; i++) {
nonFields[i].accept(this);
}
}
@override
Object visitClassTypeAlias(ClassTypeAlias node) {
super.visitClassTypeAlias(node);
ErrorCode errorCode = CompileTimeErrorCode.MIXIN_WITH_NON_CLASS_SUPERCLASS;
InterfaceType superclassType = _resolveType(node.superclass, errorCode,
CompileTimeErrorCode.EXTENDS_ENUM, errorCode);
if (superclassType == null) {
superclassType = typeProvider.objectType;
}
ClassElementImpl classElement = _getClassElement(node.name);
if (classElement != null) {
classElement.supertype = superclassType;
}
_resolve(classElement, node.withClause, node.implementsClause);
return null;
}
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
super.visitConstructorDeclaration(node);
ExecutableElementImpl element = node.element as ExecutableElementImpl;
if (element == null) {
ClassDeclaration classNode =
node.getAncestor((node) => node is ClassDeclaration);
StringBuffer buffer = new StringBuffer();
buffer.write("The element for the constructor ");
buffer.write(node.name == null ? "<unnamed>" : node.name.name);
buffer.write(" in ");
if (classNode == null) {
buffer.write("<unknown class>");
} else {
buffer.write(classNode.name.name);
}
buffer.write(" in ");
buffer.write(source.fullName);
buffer.write(" was not set while trying to resolve types.");
AnalysisEngine.instance.logger.logError(buffer.toString(),
new CaughtException(new AnalysisException(), null));
} else {
ClassElement definingClass = element.enclosingElement as ClassElement;
element.returnType = definingClass.type;
element.type = new FunctionTypeImpl(element);
}
return null;
}
@override
Object visitDeclaredIdentifier(DeclaredIdentifier node) {
super.visitDeclaredIdentifier(node);
DartType declaredType;
TypeName typeName = node.type;
if (typeName == null) {
declaredType = _dynamicType;
} else {
declaredType = _getType(typeName);
}
LocalVariableElementImpl element = node.element as LocalVariableElementImpl;
element.type = declaredType;
return null;
}
@override
Object visitFieldFormalParameter(FieldFormalParameter node) {
super.visitFieldFormalParameter(node);
Element element = node.identifier.staticElement;
if (element is ParameterElementImpl) {
ParameterElementImpl parameter = element;
FormalParameterList parameterList = node.parameters;
if (parameterList == null) {
DartType type;
TypeName typeName = node.type;
if (typeName == null) {
element.hasImplicitType = true;
type = _dynamicType;
if (parameter is FieldFormalParameterElement) {
FieldElement fieldElement =
(parameter as FieldFormalParameterElement).field;
if (fieldElement != null) {
type = fieldElement.type;
}
}
} else {
type = _getType(typeName);
}
parameter.type = type;
} else {
_setFunctionTypedParameterType(parameter, node.type, node.parameters);
}
} else {
// TODO(brianwilkerson) Report this internal error
}
return null;
}
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
super.visitFunctionDeclaration(node);
ExecutableElementImpl element = node.element as ExecutableElementImpl;
if (element == null) {
StringBuffer buffer = new StringBuffer();
buffer.write("The element for the top-level function ");
buffer.write(node.name);
buffer.write(" in ");
buffer.write(source.fullName);
buffer.write(" was not set while trying to resolve types.");
AnalysisEngine.instance.logger.logError(buffer.toString(),
new CaughtException(new AnalysisException(), null));
}
element.returnType = _computeReturnType(node.returnType);
element.type = new FunctionTypeImpl(element);
_inferSetterReturnType(element);
return null;
}
@override
Object visitFunctionTypeAlias(FunctionTypeAlias node) {
FunctionTypeAliasElementImpl element =
node.element as FunctionTypeAliasElementImpl;
super.visitFunctionTypeAlias(node);
element.returnType = _computeReturnType(node.returnType);
return null;
}
@override
Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
super.visitFunctionTypedFormalParameter(node);
Element element = node.identifier.staticElement;
if (element is ParameterElementImpl) {
_setFunctionTypedParameterType(element, node.returnType, node.parameters);
} else {
// TODO(brianwilkerson) Report this internal error
}
return null;
}
@override
Object visitMethodDeclaration(MethodDeclaration node) {
super.visitMethodDeclaration(node);
ExecutableElementImpl element = node.element as ExecutableElementImpl;
if (element == null) {
ClassDeclaration classNode =
node.getAncestor((node) => node is ClassDeclaration);
StringBuffer buffer = new StringBuffer();
buffer.write("The element for the method ");
buffer.write(node.name.name);
buffer.write(" in ");
if (classNode == null) {
buffer.write("<unknown class>");
} else {
buffer.write(classNode.name.name);
}
buffer.write(" in ");
buffer.write(source.fullName);
buffer.write(" was not set while trying to resolve types.");
AnalysisEngine.instance.logger.logError(buffer.toString(),
new CaughtException(new AnalysisException(), null));
}
element.returnType = _computeReturnType(node.returnType);
element.type = new FunctionTypeImpl(element);
_inferSetterReturnType(element);
if (element is PropertyAccessorElement) {
PropertyAccessorElement accessor = element as PropertyAccessorElement;
PropertyInducingElementImpl variable =
accessor.variable as PropertyInducingElementImpl;
if (accessor.isGetter) {
variable.type = element.returnType;
} else if (variable.type == null) {
List<ParameterElement> parameters = element.parameters;
if (parameters != null && parameters.length > 0) {
variable.type = parameters[0].type;
}
}
}
return null;
}
@override
Object visitSimpleFormalParameter(SimpleFormalParameter node) {
super.visitSimpleFormalParameter(node);
DartType declaredType;
TypeName typeName = node.type;
if (typeName == null) {
declaredType = _dynamicType;
} else {
declaredType = _getType(typeName);
}
Element element = node.identifier.staticElement;
if (element is ParameterElement) {
(element as ParameterElementImpl).type = declaredType;
} else {
// TODO(brianwilkerson) Report the internal error.
}
return null;
}
@override
Object visitSuperExpression(SuperExpression node) {
_hasReferenceToSuper = true;
return super.visitSuperExpression(node);
}
@override
Object visitTypeName(TypeName node) {
super.visitTypeName(node);
Identifier typeName = node.name;
TypeArgumentList argumentList = node.typeArguments;
Element element = nameScope.lookup(typeName, definingLibrary);
if (element == null) {
//
// Check to see whether the type name is either 'dynamic' or 'void',
// neither of which are in the name scope and hence will not be found by
// normal means.
//
if (typeName.name == _dynamicType.name) {
_setElement(typeName, _dynamicType.element);
if (argumentList != null) {
// TODO(brianwilkerson) Report this error
// reporter.reportError(StaticTypeWarningCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS, node, dynamicType.getName(), 0, argumentList.getArguments().size());
}
typeName.staticType = _dynamicType;
node.type = _dynamicType;
return null;
}
VoidTypeImpl voidType = VoidTypeImpl.instance;
if (typeName.name == voidType.name) {
// There is no element for 'void'.
if (argumentList != null) {
// TODO(brianwilkerson) Report this error
// reporter.reportError(StaticTypeWarningCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS, node, voidType.getName(), 0, argumentList.getArguments().size());
}
typeName.staticType = voidType;
node.type = voidType;
return null;
}
//
// If not, the look to see whether we might have created the wrong AST
// structure for a constructor name. If so, fix the AST structure and then
// proceed.
//
AstNode parent = node.parent;
if (typeName is PrefixedIdentifier &&
parent is ConstructorName &&
argumentList == null) {
ConstructorName name = parent;
if (name.name == null) {
PrefixedIdentifier prefixedIdentifier =
typeName as PrefixedIdentifier;
SimpleIdentifier prefix = prefixedIdentifier.prefix;
element = nameScope.lookup(prefix, definingLibrary);
if (element is PrefixElement) {
if (parent.parent is InstanceCreationExpression &&
(parent.parent as InstanceCreationExpression).isConst) {
// If, if this is a const expression, then generate a
// CompileTimeErrorCode.CONST_WITH_NON_TYPE error.
reportErrorForNode(
CompileTimeErrorCode.CONST_WITH_NON_TYPE,
prefixedIdentifier.identifier,
[prefixedIdentifier.identifier.name]);
} else {
// Else, if this expression is a new expression, report a
// NEW_WITH_NON_TYPE warning.
reportErrorForNode(
StaticWarningCode.NEW_WITH_NON_TYPE,
prefixedIdentifier.identifier,
[prefixedIdentifier.identifier.name]);
}
_setElement(prefix, element);
return null;
} else if (element != null) {
//
// Rewrite the constructor name. The parser, when it sees a
// constructor named "a.b", cannot tell whether "a" is a prefix and
// "b" is a class name, or whether "a" is a class name and "b" is a
// constructor name. It arbitrarily chooses the former, but in this
// case was wrong.
//
name.name = prefixedIdentifier.identifier;
name.period = prefixedIdentifier.period;
node.name = prefix;
typeName = prefix;
}
}
}
}
// check element
bool elementValid = element is! MultiplyDefinedElement;
if (elementValid &&
element is! ClassElement &&
_isTypeNameInInstanceCreationExpression(node)) {
SimpleIdentifier typeNameSimple = _getTypeSimpleIdentifier(typeName);
InstanceCreationExpression creation =
node.parent.parent as InstanceCreationExpression;
if (creation.isConst) {
if (element == null) {
reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_CLASS, typeNameSimple, [typeName]);
} else {
reportErrorForNode(CompileTimeErrorCode.CONST_WITH_NON_TYPE,
typeNameSimple, [typeName]);
}
elementValid = false;
} else {
if (element != null) {
reportErrorForNode(
StaticWarningCode.NEW_WITH_NON_TYPE, typeNameSimple, [typeName]);
elementValid = false;
}
}
}
if (elementValid && element == null) {
// We couldn't resolve the type name.
// TODO(jwren) Consider moving the check for
// CompileTimeErrorCode.BUILT_IN_IDENTIFIER_AS_TYPE from the
// ErrorVerifier, so that we don't have two errors on a built in
// identifier being used as a class name.
// See CompileTimeErrorCodeTest.test_builtInIdentifierAsType().
SimpleIdentifier typeNameSimple = _getTypeSimpleIdentifier(typeName);
RedirectingConstructorKind redirectingConstructorKind;
if (_isBuiltInIdentifier(node) && _isTypeAnnotation(node)) {
reportErrorForNode(CompileTimeErrorCode.BUILT_IN_IDENTIFIER_AS_TYPE,
typeName, [typeName.name]);
} else if (typeNameSimple.name == "boolean") {
reportErrorForNode(
StaticWarningCode.UNDEFINED_CLASS_BOOLEAN, typeNameSimple, []);
} else if (_isTypeNameInCatchClause(node)) {
reportErrorForNode(StaticWarningCode.NON_TYPE_IN_CATCH_CLAUSE, typeName,
[typeName.name]);
} else if (_isTypeNameInAsExpression(node)) {
reportErrorForNode(
StaticWarningCode.CAST_TO_NON_TYPE, typeName, [typeName.name]);
} else if (_isTypeNameInIsExpression(node)) {
reportErrorForNode(StaticWarningCode.TYPE_TEST_WITH_UNDEFINED_NAME,
typeName, [typeName.name]);
} else if ((redirectingConstructorKind =
_getRedirectingConstructorKind(node)) !=
null) {
ErrorCode errorCode =
(redirectingConstructorKind == RedirectingConstructorKind.CONST
? CompileTimeErrorCode.REDIRECT_TO_NON_CLASS
: StaticWarningCode.REDIRECT_TO_NON_CLASS);
reportErrorForNode(errorCode, typeName, [typeName.name]);
} else if (_isTypeNameInTypeArgumentList(node)) {
reportErrorForNode(StaticTypeWarningCode.NON_TYPE_AS_TYPE_ARGUMENT,
typeName, [typeName.name]);
} else {
reportErrorForNode(
StaticWarningCode.UNDEFINED_CLASS, typeName, [typeName.name]);
}
elementValid = false;
}
if (!elementValid) {
if (element is MultiplyDefinedElement) {
_setElement(typeName, element);
} else {
_setElement(typeName, _dynamicType.element);
}
typeName.staticType = _undefinedType;
node.type = _undefinedType;
return null;
}
DartType type = null;
if (element is ClassElement) {
_setElement(typeName, element);
type = element.type;
} else if (element is FunctionTypeAliasElement) {
_setElement(typeName, element);
type = element.type;
} else if (element is TypeParameterElement) {
_setElement(typeName, element);
type = element.type;
if (argumentList != null) {
// Type parameters cannot have type arguments.
// TODO(brianwilkerson) Report this error.
// resolver.reportError(ResolverErrorCode.?, keyType);
}
} else if (element is MultiplyDefinedElement) {
List<Element> elements = element.conflictingElements;
type = _getTypeWhenMultiplyDefined(elements);
if (type != null) {
node.type = type;
}
} else {
// The name does not represent a type.
RedirectingConstructorKind redirectingConstructorKind;
if (_isTypeNameInCatchClause(node)) {
reportErrorForNode(StaticWarningCode.NON_TYPE_IN_CATCH_CLAUSE, typeName,
[typeName.name]);
} else if (_isTypeNameInAsExpression(node)) {
reportErrorForNode(
StaticWarningCode.CAST_TO_NON_TYPE, typeName, [typeName.name]);
} else if (_isTypeNameInIsExpression(node)) {
reportErrorForNode(StaticWarningCode.TYPE_TEST_WITH_NON_TYPE, typeName,
[typeName.name]);
} else if ((redirectingConstructorKind =
_getRedirectingConstructorKind(node)) !=
null) {
ErrorCode errorCode =
(redirectingConstructorKind == RedirectingConstructorKind.CONST
? CompileTimeErrorCode.REDIRECT_TO_NON_CLASS
: StaticWarningCode.REDIRECT_TO_NON_CLASS);
reportErrorForNode(errorCode, typeName, [typeName.name]);
} else if (_isTypeNameInTypeArgumentList(node)) {
reportErrorForNode(StaticTypeWarningCode.NON_TYPE_AS_TYPE_ARGUMENT,
typeName, [typeName.name]);
} else {
AstNode parent = typeName.parent;
while (parent is TypeName) {
parent = parent.parent;
}
if (parent is ExtendsClause ||
parent is ImplementsClause ||
parent is WithClause ||
parent is ClassTypeAlias) {
// Ignored. The error will be reported elsewhere.
} else {
reportErrorForNode(
StaticWarningCode.NOT_A_TYPE, typeName, [typeName.name]);
}
}
_setElement(typeName, _dynamicType.element);
typeName.staticType = _dynamicType;
node.type = _dynamicType;
return null;
}
if (argumentList != null) {
NodeList<TypeName> arguments = argumentList.arguments;
int argumentCount = arguments.length;
List<DartType> parameters = _getTypeParameters(type);
int parameterCount = parameters.length;
List<DartType> typeArguments = new List<DartType>(parameterCount);
if (argumentCount == parameterCount) {
for (int i = 0; i < parameterCount; i++) {
TypeName argumentTypeName = arguments[i];
DartType argumentType = _getType(argumentTypeName);
if (argumentType == null) {
argumentType = _dynamicType;
}
typeArguments[i] = argumentType;
}
} else {
reportErrorForNode(_getInvalidTypeParametersErrorCode(node), node,
[typeName.name, parameterCount, argumentCount]);
for (int i = 0; i < parameterCount; i++) {
typeArguments[i] = _dynamicType;
}
}
type = _instantiateType(type, typeArguments);
} else {
//
// Check for the case where there are no type arguments given for a
// parameterized type.
//
List<DartType> parameters = _getTypeParameters(type);
int parameterCount = parameters.length;
if (parameterCount > 0) {
DynamicTypeImpl dynamicType = DynamicTypeImpl.instance;
List<DartType> arguments = new List<DartType>(parameterCount);
for (int i = 0; i < parameterCount; i++) {
arguments[i] = dynamicType;
}
type = _instantiateType(type, arguments);
}
}
typeName.staticType = type;
node.type = type;
return null;
}
@override
Object visitTypeParameter(TypeParameter node) {
super.visitTypeParameter(node);
TypeName bound = node.bound;
if (bound != null) {
TypeParameterElementImpl typeParameter =
node.name.staticElement as TypeParameterElementImpl;
if (typeParameter != null) {
typeParameter.bound = bound.type;
}
}
return null;
}
@override
Object visitVariableDeclaration(VariableDeclaration node) {
super.visitVariableDeclaration(node);
DartType declaredType;
TypeName typeName = (node.parent as VariableDeclarationList).type;
if (typeName == null) {
declaredType = _dynamicType;
} else {
declaredType = _getType(typeName);
}
Element element = node.name.staticElement;
if (element is VariableElement) {
(element as VariableElementImpl).type = declaredType;
if (element is PropertyInducingElement) {
PropertyInducingElement variableElement = element;
PropertyAccessorElementImpl getter =
variableElement.getter as PropertyAccessorElementImpl;
getter.returnType = declaredType;
getter.type = new FunctionTypeImpl(getter);
PropertyAccessorElementImpl setter =
variableElement.setter as PropertyAccessorElementImpl;
if (setter != null) {
List<ParameterElement> parameters = setter.parameters;
if (parameters.length > 0) {
(parameters[0] as ParameterElementImpl).type = declaredType;
}
setter.returnType = VoidTypeImpl.instance;
setter.type = new FunctionTypeImpl(setter);
}
}
} else {
// TODO(brianwilkerson) Report the internal error.
}
return null;
}
/**
* Given a type name representing the return type of a function, compute the return type of the
* function.
*
* @param returnType the type name representing the return type of the function
* @return the return type that was computed
*/
DartType _computeReturnType(TypeName returnType) {
if (returnType == null) {
return _dynamicType;
} else {
return returnType.type;
}
}
/**
* Return the class element that represents the class whose name was provided.
*
* @param identifier the name from the declaration of a class
* @return the class element that represents the class
*/
ClassElementImpl _getClassElement(SimpleIdentifier identifier) {
// TODO(brianwilkerson) Seems like we should be using
// ClassDeclaration.getElement().
if (identifier == null) {
// TODO(brianwilkerson) Report this
// Internal error: We should never build a class declaration without a
// name.
return null;
}
Element element = identifier.staticElement;
if (element is! ClassElementImpl) {
// TODO(brianwilkerson) Report this
// Internal error: Failed to create an element for a class declaration.
return null;
}
return element as ClassElementImpl;
}
/**
* Return an array containing all of the elements associated with the parameters in the given
* list.
*
* @param parameterList the list of parameters whose elements are to be returned
* @return the elements associated with the parameters
*/
List<ParameterElement> _getElements(FormalParameterList parameterList) {
List<ParameterElement> elements = new List<ParameterElement>();
for (FormalParameter parameter in parameterList.parameters) {
ParameterElement element =
parameter.identifier.staticElement as ParameterElement;
// TODO(brianwilkerson) Understand why the element would be null.
if (element != null) {
elements.add(element);
}
}
return elements;
}
/**
* The number of type arguments in the given type name does not match the number of parameters in
* the corresponding class element. Return the error code that should be used to report this
* error.
*
* @param node the type name with the wrong number of type arguments
* @return the error code that should be used to report that the wrong number of type arguments
* were provided
*/
ErrorCode _getInvalidTypeParametersErrorCode(TypeName node) {
AstNode parent = node.parent;
if (parent is ConstructorName) {
parent = parent.parent;
if (parent is InstanceCreationExpression) {
if (parent.isConst) {
return CompileTimeErrorCode.CONST_WITH_INVALID_TYPE_PARAMETERS;
} else {
return StaticWarningCode.NEW_WITH_INVALID_TYPE_PARAMETERS;
}
}
}
return StaticTypeWarningCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS;
}
/**
* Checks if the given type name is the target in a redirected constructor.
*
* @param typeName the type name to analyze
* @return some [RedirectingConstructorKind] if the given type name is used as the type in a
* redirected constructor, or `null` otherwise
*/
RedirectingConstructorKind _getRedirectingConstructorKind(TypeName typeName) {
AstNode parent = typeName.parent;
if (parent is ConstructorName) {
ConstructorName constructorName = parent as ConstructorName;
parent = constructorName.parent;
if (parent is ConstructorDeclaration) {
if (identical(parent.redirectedConstructor, constructorName)) {
if (parent.constKeyword != null) {
return RedirectingConstructorKind.CONST;
}
return RedirectingConstructorKind.NORMAL;
}
}
}
return null;
}
/**
* Return the type represented by the given type name.
*
* @param typeName the type name representing the type to be returned
* @return the type represented by the type name
*/
DartType _getType(TypeName typeName) {
DartType type = typeName.type;
if (type == null) {
return _undefinedType;
}
return type;
}
/**
* Return the type arguments associated with the given type.
*
* @param type the type whole type arguments are to be returned
* @return the type arguments associated with the given type
*/
List<DartType> _getTypeParameters(DartType type) {
if (type is InterfaceType) {
return type.typeArguments;
} else if (type is FunctionType) {
return TypeParameterTypeImpl.getTypes(type.typeFormals);
}
return DartType.EMPTY_LIST;
}
/**
* Returns the simple identifier of the given (may be qualified) type name.
*
* @param typeName the (may be qualified) qualified type name
* @return the simple identifier of the given (may be qualified) type name.
*/
SimpleIdentifier _getTypeSimpleIdentifier(Identifier typeName) {
if (typeName is SimpleIdentifier) {
return typeName;
} else {
return (typeName as PrefixedIdentifier).identifier;
}
}
/**
* Given the multiple elements to which a single name could potentially be resolved, return the
* single interface type that should be used, or `null` if there is no clear choice.
*
* @param elements the elements to which a single name could potentially be resolved
* @return the single interface type that should be used for the type name
*/
InterfaceType _getTypeWhenMultiplyDefined(List<Element> elements) {
InterfaceType type = null;
for (Element element in elements) {
if (element is ClassElement) {
if (type != null) {
return null;
}
type = element.type;
}
}
return type;
}
/**
* In strong mode we infer "void" as the setter return type (as void is the
* only legal return type for a setter). This allows us to give better
* errors later if an invalid type is returned.
*/
void _inferSetterReturnType(ExecutableElementImpl element) {
if (_strongMode &&
element is PropertyAccessorElementImpl &&
element.isSetter &&
element.hasImplicitReturnType) {
element.returnType = VoidTypeImpl.instance;
}
}
DartType _instantiateType(DartType type, List<DartType> typeArguments) {
// TODO(jmesserly): this should use TypeSystem.instantiateToBounds,
// from calling methods when they know they're just trying to fill in
// "dynamic" for the case of missing type arguments.
if (type is InterfaceTypeImpl) {
return type.substitute4(typeArguments);
} else if (type is FunctionTypeImpl) {
return type.instantiate(typeArguments);
} else {
// TODO(brianwilkerson) Report this internal error.
return type;
}
}
/**
* Checks if the given type name is used as the type in an as expression.
*
* @param typeName the type name to analyzer
* @return `true` if the given type name is used as the type in an as expression
*/
bool _isTypeNameInAsExpression(TypeName typeName) {
AstNode parent = typeName.parent;
if (parent is AsExpression) {
AsExpression asExpression = parent;
return identical(asExpression.type, typeName);
}
return false;
}
/**
* Checks if the given type name is used as the exception type in a catch clause.
*
* @param typeName the type name to analyzer
* @return `true` if the given type name is used as the exception type in a catch clause
*/
bool _isTypeNameInCatchClause(TypeName typeName) {
AstNode parent = typeName.parent;
if (parent is CatchClause) {
CatchClause catchClause = parent;
return identical(catchClause.exceptionType, typeName);
}
return false;
}
/**
* Checks if the given type name is used as the type in an instance creation expression.
*
* @param typeName the type name to analyzer
* @return `true` if the given type name is used as the type in an instance creation
* expression
*/
bool _isTypeNameInInstanceCreationExpression(TypeName typeName) {
AstNode parent = typeName.parent;
if (parent is ConstructorName &&
parent.parent is InstanceCreationExpression) {
ConstructorName constructorName = parent;
return constructorName != null &&
identical(constructorName.type, typeName);
}
return false;
}
/**
* Checks if the given type name is used as the type in an is expression.
*
* @param typeName the type name to analyzer
* @return `true` if the given type name is used as the type in an is expression
*/
bool _isTypeNameInIsExpression(TypeName typeName) {
AstNode parent = typeName.parent;
if (parent is IsExpression) {
IsExpression isExpression = parent;
return identical(isExpression.type, typeName);
}
return false;
}
/**
* Checks if the given type name used in a type argument list.
*
* @param typeName the type name to analyzer
* @return `true` if the given type name is in a type argument list
*/
bool _isTypeNameInTypeArgumentList(TypeName typeName) =>
typeName.parent is TypeArgumentList;
/**
* Record that the static type of the given node is the given type.
*
* @param expression the node whose type is to be recorded
* @param type the static type of the node
*/
Object _recordType(Expression expression, DartType type) {
if (type == null) {
expression.staticType = _dynamicType;
} else {
expression.staticType = type;
}
return null;
}
/**
* Resolve the types in the given with and implements clauses and associate those types with the
* given class element.
*
* @param classElement the class element with which the mixin and interface types are to be
* associated
* @param withClause the with clause to be resolved
* @param implementsClause the implements clause to be resolved
*/
void _resolve(ClassElementImpl classElement, WithClause withClause,
ImplementsClause implementsClause) {
if (withClause != null) {
List<InterfaceType> mixinTypes = _resolveTypes(
withClause.mixinTypes,
CompileTimeErrorCode.MIXIN_OF_NON_CLASS,
CompileTimeErrorCode.MIXIN_OF_ENUM,
CompileTimeErrorCode.MIXIN_OF_NON_CLASS);
if (classElement != null) {
classElement.mixins = mixinTypes;
classElement.withClauseRange =
new SourceRange(withClause.offset, withClause.length);
}
}
if (implementsClause != null) {
NodeList<TypeName> interfaces = implementsClause.interfaces;
List<InterfaceType> interfaceTypes = _resolveTypes(
interfaces,
CompileTimeErrorCode.IMPLEMENTS_NON_CLASS,
CompileTimeErrorCode.IMPLEMENTS_ENUM,
CompileTimeErrorCode.IMPLEMENTS_DYNAMIC);
if (classElement != null) {
classElement.interfaces = interfaceTypes;
}
// TODO(brianwilkerson) Move the following checks to ErrorVerifier.
int count = interfaces.length;
List<bool> detectedRepeatOnIndex = new List<bool>.filled(count, false);
for (int i = 0; i < detectedRepeatOnIndex.length; i++) {
detectedRepeatOnIndex[i] = false;
}
for (int i = 0; i < count; i++) {
TypeName typeName = interfaces[i];
if (!detectedRepeatOnIndex[i]) {
Element element = typeName.name.staticElement;
for (int j = i + 1; j < count; j++) {
TypeName typeName2 = interfaces[j];
Identifier identifier2 = typeName2.name;
String name2 = identifier2.name;
Element element2 = identifier2.staticElement;
if (element != null && element == element2) {
detectedRepeatOnIndex[j] = true;
reportErrorForNode(
CompileTimeErrorCode.IMPLEMENTS_REPEATED, typeName2, [name2]);
}
}
}
}
}
}
/**
* Return the type specified by the given name.
*
* @param typeName the type name specifying the type to be returned
* @param nonTypeError the error to produce if the type name is defined to be something other than
* a type
* @param enumTypeError the error to produce if the type name is defined to be an enum
* @param dynamicTypeError the error to produce if the type name is "dynamic"
* @return the type specified by the type name
*/
InterfaceType _resolveType(TypeName typeName, ErrorCode nonTypeError,
ErrorCode enumTypeError, ErrorCode dynamicTypeError) {
DartType type = typeName.type;
if (type is InterfaceType) {
ClassElement element = type.element;
if (element != null && element.isEnum) {
reportErrorForNode(enumTypeError, typeName);
return null;
}
return type;
}
// If the type is not an InterfaceType, then visitTypeName() sets the type
// to be a DynamicTypeImpl
Identifier name = typeName.name;
if (name.name == Keyword.DYNAMIC.syntax) {
reportErrorForNode(dynamicTypeError, name, [name.name]);
} else {
reportErrorForNode(nonTypeError, name, [name.name]);
}
return null;
}
/**
* Resolve the types in the given list of type names.
*
* @param typeNames the type names to be resolved
* @param nonTypeError the error to produce if the type name is defined to be something other than
* a type
* @param enumTypeError the error to produce if the type name is defined to be an enum
* @param dynamicTypeError the error to produce if the type name is "dynamic"
* @return an array containing all of the types that were resolved.
*/
List<InterfaceType> _resolveTypes(
NodeList<TypeName> typeNames,
ErrorCode nonTypeError,
ErrorCode enumTypeError,
ErrorCode dynamicTypeError) {
List<InterfaceType> types = new List<InterfaceType>();
for (TypeName typeName in typeNames) {
InterfaceType type =
_resolveType(typeName, nonTypeError, enumTypeError, dynamicTypeError);
if (type != null) {
types.add(type);
}
}
return types;
}
void _setElement(Identifier typeName, Element element) {
if (element != null) {
if (typeName is SimpleIdentifier) {
typeName.staticElement = element;
} else if (typeName is PrefixedIdentifier) {
PrefixedIdentifier identifier = typeName;
identifier.identifier.staticElement = element;
SimpleIdentifier prefix = identifier.prefix;
Element prefixElement = nameScope.lookup(prefix, definingLibrary);
if (prefixElement != null) {
prefix.staticElement = prefixElement;
}
}
}
}
/**
* Given a parameter element, create a function type based on the given return type and parameter
* list and associate the created type with the element.
*
* @param element the parameter element whose type is to be set
* @param returnType the (possibly `null`) return type of the function
* @param parameterList the list of parameters to the function
*/
void _setFunctionTypedParameterType(ParameterElementImpl element,
TypeName returnType, FormalParameterList parameterList) {
List<ParameterElement> parameters = _getElements(parameterList);
FunctionElementImpl functionElement = new FunctionElementImpl.forNode(null);
functionElement.synthetic = true;
functionElement.shareParameters(parameters);
functionElement.returnType = _computeReturnType(returnType);
functionElement.enclosingElement = element;
functionElement.shareTypeParameters(element.typeParameters);
element.type = new FunctionTypeImpl(functionElement);
functionElement.type = element.type;
}
/**
* @return `true` if the name of the given [TypeName] is an built-in identifier.
*/
static bool _isBuiltInIdentifier(TypeName node) {
Token token = node.name.beginToken;
return token.type == TokenType.KEYWORD;
}
/**
* @return `true` if given [TypeName] is used as a type annotation.
*/
static bool _isTypeAnnotation(TypeName node) {
AstNode parent = node.parent;
if (parent is VariableDeclarationList) {
return identical(parent.type, node);
}
if (parent is FieldFormalParameter) {
return identical(parent.type, node);
}
if (parent is SimpleFormalParameter) {
return identical(parent.type, node);
}
return false;
}
}
/**
* Instances of the class [UnusedLocalElementsVerifier] traverse an element
* structure looking for cases of [HintCode.UNUSED_ELEMENT],
* [HintCode.UNUSED_FIELD], [HintCode.UNUSED_LOCAL_VARIABLE], etc.
*/
class UnusedLocalElementsVerifier extends RecursiveElementVisitor {
/**
* The error listener to which errors will be reported.
*/
final AnalysisErrorListener _errorListener;
/**
* The elements know to be used.
*/
final UsedLocalElements _usedElements;
/**
* Create a new instance of the [UnusedLocalElementsVerifier].
*/
UnusedLocalElementsVerifier(this._errorListener, this._usedElements);
@override
visitClassElement(ClassElement element) {
if (!_isUsedElement(element)) {
_reportErrorForElement(HintCode.UNUSED_ELEMENT, element,
[element.kind.displayName, element.displayName]);
}
super.visitClassElement(element);
}
@override
visitFieldElement(FieldElement element) {
if (!_isReadMember(element)) {
_reportErrorForElement(
HintCode.UNUSED_FIELD, element, [element.displayName]);
}
super.visitFieldElement(element);
}
@override
visitFunctionElement(FunctionElement element) {
if (!_isUsedElement(element)) {
_reportErrorForElement(HintCode.UNUSED_ELEMENT, element,
[element.kind.displayName, element.displayName]);
}
super.visitFunctionElement(element);
}
@override
visitFunctionTypeAliasElement(FunctionTypeAliasElement element) {
if (!_isUsedElement(element)) {
_reportErrorForElement(HintCode.UNUSED_ELEMENT, element,
[element.kind.displayName, element.displayName]);
}
super.visitFunctionTypeAliasElement(element);
}
@override
visitLocalVariableElement(LocalVariableElement element) {
if (!_isUsedElement(element) && !_isNamedUnderscore(element)) {
HintCode errorCode;
if (_usedElements.isCatchException(element)) {
errorCode = HintCode.UNUSED_CATCH_CLAUSE;
} else if (_usedElements.isCatchStackTrace(element)) {
errorCode = HintCode.UNUSED_CATCH_STACK;
} else {
errorCode = HintCode.UNUSED_LOCAL_VARIABLE;
}
_reportErrorForElement(errorCode, element, [element.displayName]);
}
}
@override
visitMethodElement(MethodElement element) {
if (!_isUsedMember(element)) {
_reportErrorForElement(HintCode.UNUSED_ELEMENT, element,
[element.kind.displayName, element.displayName]);
}
super.visitMethodElement(element);
}
@override
visitPropertyAccessorElement(PropertyAccessorElement element) {
if (!_isUsedMember(element)) {
_reportErrorForElement(HintCode.UNUSED_ELEMENT, element,
[element.kind.displayName, element.displayName]);
}
super.visitPropertyAccessorElement(element);
}
bool _isNamedUnderscore(LocalVariableElement element) {
String name = element.name;
if (name != null) {
for (int index = name.length - 1; index >= 0; --index) {
if (name.codeUnitAt(index) != 0x5F) {
// 0x5F => '_'
return false;
}
}
return true;
}
return false;
}
bool _isReadMember(Element element) {
if (element.isPublic) {
return true;
}
if (element.isSynthetic) {
return true;
}
return _usedElements.readMembers.contains(element.displayName);
}
bool _isUsedElement(Element element) {
if (element.isSynthetic) {
return true;
}
if (element is LocalVariableElement ||
element is FunctionElement && !element.isStatic) {
// local variable or function
} else {
if (element.isPublic) {
return true;
}
}
return _usedElements.elements.contains(element);
}
bool _isUsedMember(Element element) {
if (element.isPublic) {
return true;
}
if (element.isSynthetic) {
return true;
}
if (_usedElements.members.contains(element.displayName)) {
return true;
}
return _usedElements.elements.contains(element);
}
void _reportErrorForElement(
ErrorCode errorCode, Element element, List<Object> arguments) {
if (element != null) {
_errorListener.onError(new AnalysisError(element.source,
element.nameOffset, element.nameLength, errorCode, arguments));
}
}
}
/**
* A container with information about used imports prefixes and used imported
* elements.
*/
class UsedImportedElements {
/**
* The set of referenced [PrefixElement]s.
*/
final Set<PrefixElement> prefixes = new HashSet<PrefixElement>();
/**
* The set of referenced top-level [Element]s.
*/
final Set<Element> elements = new HashSet<Element>();
}
/**
* A container with sets of used [Element]s.
* All these elements are defined in a single compilation unit or a library.
*/
class UsedLocalElements {
/**
* Resolved, locally defined elements that are used or potentially can be
* used.
*/
final HashSet<Element> elements = new HashSet<Element>();
/**
* [LocalVariableElement]s that represent exceptions in [CatchClause]s.
*/
final HashSet<LocalVariableElement> catchExceptionElements =
new HashSet<LocalVariableElement>();
/**
* [LocalVariableElement]s that represent stack traces in [CatchClause]s.
*/
final HashSet<LocalVariableElement> catchStackTraceElements =
new HashSet<LocalVariableElement>();
/**
* Names of resolved or unresolved class members that are referenced in the
* library.
*/
final HashSet<String> members = new HashSet<String>();
/**
* Names of resolved or unresolved class members that are read in the
* library.
*/
final HashSet<String> readMembers = new HashSet<String>();
UsedLocalElements();
factory UsedLocalElements.merge(List<UsedLocalElements> parts) {
UsedLocalElements result = new UsedLocalElements();
for (UsedLocalElements part in parts) {
result.elements.addAll(part.elements);
result.catchExceptionElements.addAll(part.catchExceptionElements);
result.catchStackTraceElements.addAll(part.catchStackTraceElements);
result.members.addAll(part.members);
result.readMembers.addAll(part.readMembers);
}
return result;
}
void addCatchException(LocalVariableElement element) {
if (element != null) {
catchExceptionElements.add(element);
}
}
void addCatchStackTrace(LocalVariableElement element) {
if (element != null) {
catchStackTraceElements.add(element);
}
}
void addElement(Element element) {
if (element != null) {
elements.add(element);
}
}
bool isCatchException(LocalVariableElement element) {
return catchExceptionElements.contains(element);
}
bool isCatchStackTrace(LocalVariableElement element) {
return catchStackTraceElements.contains(element);
}
}
/**
* Instances of the class `VariableResolverVisitor` are used to resolve
* [SimpleIdentifier]s to local variables and formal parameters.
*/
class VariableResolverVisitor extends ScopedVisitor {
/**
* The method or function that we are currently visiting, or `null` if we are not inside a
* method or function.
*/
ExecutableElement _enclosingFunction;
/**
* Initialize a newly created visitor to resolve the nodes in an AST node.
*
* [definingLibrary] is the element for the library containing the node being
* visited.
* [source] is the source representing the compilation unit containing the
* node being visited
* [typeProvider] is the object used to access the types from the core
* library.
* [errorListener] is the error listener that will be informed of any errors
* that are found during resolution.
* [nameScope] is the scope used to resolve identifiers in the node that will
* first be visited. If `null` or unspecified, a new [LibraryScope] will be
* created based on [definingLibrary] and [typeProvider].
*/
VariableResolverVisitor(LibraryElement definingLibrary, Source source,
TypeProvider typeProvider, AnalysisErrorListener errorListener,
{Scope nameScope})
: super(definingLibrary, source, typeProvider, errorListener,
nameScope: nameScope);
@override
Object visitExportDirective(ExportDirective node) => null;
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement outerFunction = _enclosingFunction;
try {
_enclosingFunction = node.element;
return super.visitFunctionDeclaration(node);
} finally {
_enclosingFunction = outerFunction;
}
}
@override
Object visitFunctionExpression(FunctionExpression node) {
if (node.parent is! FunctionDeclaration) {
ExecutableElement outerFunction = _enclosingFunction;
try {
_enclosingFunction = node.element;
return super.visitFunctionExpression(node);
} finally {
_enclosingFunction = outerFunction;
}
} else {
return super.visitFunctionExpression(node);
}
}
@override
Object visitImportDirective(ImportDirective node) => null;
@override
Object visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement outerFunction = _enclosingFunction;
try {
_enclosingFunction = node.element;
return super.visitMethodDeclaration(node);
} finally {
_enclosingFunction = outerFunction;
}
}
@override
Object visitSimpleIdentifier(SimpleIdentifier node) {
// Ignore if already resolved - declaration or type.
if (node.inDeclarationContext()) {
return null;
}
// Ignore if it cannot be a reference to a local variable.
AstNode parent = node.parent;
if (parent is FieldFormalParameter) {
return null;
} else if (parent is ConstructorDeclaration && parent.returnType == node) {
return null;
} else if (parent is ConstructorFieldInitializer &&
parent.fieldName == node) {
return null;
}
// Ignore if qualified.
if (parent is PrefixedIdentifier && identical(parent.identifier, node)) {
return null;
}
if (parent is PropertyAccess && identical(parent.propertyName, node)) {
return null;
}
if (parent is MethodInvocation &&
identical(parent.methodName, node) &&
parent.realTarget != null) {
return null;
}
if (parent is ConstructorName) {
return null;
}
if (parent is Label) {
return null;
}
// Prepare VariableElement.
Element element = nameScope.lookup(node, definingLibrary);
if (element is! VariableElement) {
return null;
}
// Must be local or parameter.
ElementKind kind = element.kind;
if (kind == ElementKind.LOCAL_VARIABLE) {
node.staticElement = element;
LocalVariableElementImpl variableImpl =
element as LocalVariableElementImpl;
if (node.inSetterContext()) {
variableImpl.markPotentiallyMutatedInScope();
if (element.enclosingElement != _enclosingFunction) {
variableImpl.markPotentiallyMutatedInClosure();
}
}
} else if (kind == ElementKind.PARAMETER) {
node.staticElement = element;
if (node.inSetterContext()) {
ParameterElementImpl parameterImpl = element as ParameterElementImpl;
parameterImpl.markPotentiallyMutatedInScope();
// If we are in some closure, check if it is not the same as where
// variable is declared.
if (_enclosingFunction != null &&
(element.enclosingElement != _enclosingFunction)) {
parameterImpl.markPotentiallyMutatedInClosure();
}
}
}
return null;
}
@override
Object visitTypeName(TypeName node) {
return null;
}
}
class _ConstantVerifier_validateInitializerExpression extends ConstantVisitor {
final ConstantVerifier verifier;
List<ParameterElement> parameterElements;
TypeSystem _typeSystem;
_ConstantVerifier_validateInitializerExpression(
TypeProvider typeProvider,
ErrorReporter errorReporter,
this.verifier,
this.parameterElements,
DeclaredVariables declaredVariables,
{TypeSystem typeSystem})
: _typeSystem = (typeSystem != null) ? typeSystem : new TypeSystemImpl(),
super(
new ConstantEvaluationEngine(typeProvider, declaredVariables,
typeSystem: typeSystem),
errorReporter);
@override
DartObjectImpl visitSimpleIdentifier(SimpleIdentifier node) {
Element element = node.staticElement;
for (ParameterElement parameterElement in parameterElements) {
if (identical(parameterElement, element) && parameterElement != null) {
DartType type = parameterElement.type;
if (type != null) {
if (type.isDynamic) {
return new DartObjectImpl(
verifier._typeProvider.objectType, DynamicState.DYNAMIC_STATE);
} else if (_typeSystem.isSubtypeOf(type, verifier._boolType)) {
return new DartObjectImpl(
verifier._typeProvider.boolType, BoolState.UNKNOWN_VALUE);
} else if (_typeSystem.isSubtypeOf(
type, verifier._typeProvider.doubleType)) {
return new DartObjectImpl(
verifier._typeProvider.doubleType, DoubleState.UNKNOWN_VALUE);
} else if (_typeSystem.isSubtypeOf(type, verifier._intType)) {
return new DartObjectImpl(
verifier._typeProvider.intType, IntState.UNKNOWN_VALUE);
} else if (_typeSystem.isSubtypeOf(type, verifier._numType)) {
return new DartObjectImpl(
verifier._typeProvider.numType, NumState.UNKNOWN_VALUE);
} else if (_typeSystem.isSubtypeOf(type, verifier._stringType)) {
return new DartObjectImpl(
verifier._typeProvider.stringType, StringState.UNKNOWN_VALUE);
}
//
// We don't test for other types of objects (such as List, Map,
// Function or Type) because there are no operations allowed on such
// types other than '==' and '!=', which means that we don't need to
// know the type when there is no specific data about the state of
// such objects.
//
}
return new DartObjectImpl(
type is InterfaceType ? type : verifier._typeProvider.objectType,
GenericState.UNKNOWN_VALUE);
}
}
return super.visitSimpleIdentifier(node);
}
}
class _ElementBuilder_visitClassDeclaration extends UnifyingAstVisitor<Object> {
final ElementBuilder builder;
List<ClassMember> nonFields;
_ElementBuilder_visitClassDeclaration(this.builder, this.nonFields) : super();
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
nonFields.add(node);
return null;
}
@override
Object visitMethodDeclaration(MethodDeclaration node) {
nonFields.add(node);
return null;
}
@override
Object visitNode(AstNode node) => node.accept(builder);
}
class _ResolverVisitor_isVariableAccessedInClosure
extends RecursiveAstVisitor<Object> {
final Element variable;
bool result = false;
bool _inClosure = false;
_ResolverVisitor_isVariableAccessedInClosure(this.variable);
@override
Object visitFunctionExpression(FunctionExpression node) {
bool inClosure = this._inClosure;
try {
this._inClosure = true;
return super.visitFunctionExpression(node);
} finally {
this._inClosure = inClosure;
}
}
@override
Object visitSimpleIdentifier(SimpleIdentifier node) {
if (result) {
return null;
}
if (_inClosure && identical(node.staticElement, variable)) {
result = true;
}
return null;
}
}
class _ResolverVisitor_isVariablePotentiallyMutatedIn
extends RecursiveAstVisitor<Object> {
final Element variable;
bool result = false;
_ResolverVisitor_isVariablePotentiallyMutatedIn(this.variable);
@override
Object visitSimpleIdentifier(SimpleIdentifier node) {
if (result) {
return null;
}
if (identical(node.staticElement, variable)) {
if (node.inSetterContext()) {
result = true;
}
}
return null;
}
}
class _TypeResolverVisitor_visitClassMembersInScope
extends UnifyingAstVisitor<Object> {
final TypeResolverVisitor TypeResolverVisitor_this;
List<ClassMember> nonFields;
_TypeResolverVisitor_visitClassMembersInScope(
this.TypeResolverVisitor_this, this.nonFields)
: super();
@override
Object visitConstructorDeclaration(ConstructorDeclaration node) {
nonFields.add(node);
return null;
}
@override
Object visitExtendsClause(ExtendsClause node) => null;
@override
Object visitImplementsClause(ImplementsClause node) => null;
@override
Object visitMethodDeclaration(MethodDeclaration node) {
nonFields.add(node);
return null;
}
@override
Object visitNode(AstNode node) => node.accept(TypeResolverVisitor_this);
@override
Object visitWithClause(WithClause node) => null;
}