/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.internal.verifier;
import com.google.dart.engine.ast.Annotation;
import com.google.dart.engine.ast.ArgumentList;
import com.google.dart.engine.ast.AsExpression;
import com.google.dart.engine.ast.AssertStatement;
import com.google.dart.engine.ast.AssignmentExpression;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.AwaitExpression;
import com.google.dart.engine.ast.BinaryExpression;
import com.google.dart.engine.ast.BlockFunctionBody;
import com.google.dart.engine.ast.BreakStatement;
import com.google.dart.engine.ast.CatchClause;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.ClassMember;
import com.google.dart.engine.ast.ClassTypeAlias;
import com.google.dart.engine.ast.Comment;
import com.google.dart.engine.ast.CommentReference;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.ConditionalExpression;
import com.google.dart.engine.ast.ConstructorDeclaration;
import com.google.dart.engine.ast.ConstructorFieldInitializer;
import com.google.dart.engine.ast.ConstructorInitializer;
import com.google.dart.engine.ast.ConstructorName;
import com.google.dart.engine.ast.ContinueStatement;
import com.google.dart.engine.ast.Declaration;
import com.google.dart.engine.ast.DefaultFormalParameter;
import com.google.dart.engine.ast.Directive;
import com.google.dart.engine.ast.DoStatement;
import com.google.dart.engine.ast.ExportDirective;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.ExpressionFunctionBody;
import com.google.dart.engine.ast.ExpressionStatement;
import com.google.dart.engine.ast.ExtendsClause;
import com.google.dart.engine.ast.FieldDeclaration;
import com.google.dart.engine.ast.FieldFormalParameter;
import com.google.dart.engine.ast.FormalParameter;
import com.google.dart.engine.ast.FormalParameterList;
import com.google.dart.engine.ast.FunctionBody;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.FunctionExpression;
import com.google.dart.engine.ast.FunctionExpressionInvocation;
import com.google.dart.engine.ast.FunctionTypeAlias;
import com.google.dart.engine.ast.FunctionTypedFormalParameter;
import com.google.dart.engine.ast.Identifier;
import com.google.dart.engine.ast.IfStatement;
import com.google.dart.engine.ast.ImplementsClause;
import com.google.dart.engine.ast.ImportDirective;
import com.google.dart.engine.ast.IndexExpression;
import com.google.dart.engine.ast.InstanceCreationExpression;
import com.google.dart.engine.ast.IsExpression;
import com.google.dart.engine.ast.ListLiteral;
import com.google.dart.engine.ast.MapLiteral;
import com.google.dart.engine.ast.MapLiteralEntry;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.MethodInvocation;
import com.google.dart.engine.ast.NativeClause;
import com.google.dart.engine.ast.NativeFunctionBody;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.NormalFormalParameter;
import com.google.dart.engine.ast.PostfixExpression;
import com.google.dart.engine.ast.PrefixExpression;
import com.google.dart.engine.ast.PrefixedIdentifier;
import com.google.dart.engine.ast.PropertyAccess;
import com.google.dart.engine.ast.RedirectingConstructorInvocation;
import com.google.dart.engine.ast.RethrowExpression;
import com.google.dart.engine.ast.ReturnStatement;
import com.google.dart.engine.ast.SimpleFormalParameter;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.SuperConstructorInvocation;
import com.google.dart.engine.ast.SwitchCase;
import com.google.dart.engine.ast.SwitchDefault;
import com.google.dart.engine.ast.SwitchMember;
import com.google.dart.engine.ast.SwitchStatement;
import com.google.dart.engine.ast.ThisExpression;
import com.google.dart.engine.ast.ThrowExpression;
import com.google.dart.engine.ast.TopLevelVariableDeclaration;
import com.google.dart.engine.ast.TypeArgumentList;
import com.google.dart.engine.ast.TypeName;
import com.google.dart.engine.ast.TypeParameter;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.VariableDeclarationList;
import com.google.dart.engine.ast.VariableDeclarationStatement;
import com.google.dart.engine.ast.WhileStatement;
import com.google.dart.engine.ast.WithClause;
import com.google.dart.engine.ast.YieldStatement;
import com.google.dart.engine.ast.visitor.RecursiveAstVisitor;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.ConstructorElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ExecutableElement;
import com.google.dart.engine.element.ExportElement;
import com.google.dart.engine.element.FieldElement;
import com.google.dart.engine.element.FieldFormalParameterElement;
import com.google.dart.engine.element.FunctionElement;
import com.google.dart.engine.element.FunctionTypeAliasElement;
import com.google.dart.engine.element.ImportElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.MethodElement;
import com.google.dart.engine.element.MultiplyInheritedExecutableElement;
import com.google.dart.engine.element.ParameterElement;
import com.google.dart.engine.element.PrefixElement;
import com.google.dart.engine.element.PropertyAccessorElement;
import com.google.dart.engine.element.TypeParameterElement;
import com.google.dart.engine.element.VariableElement;
import com.google.dart.engine.element.visitor.GeneralizingElementVisitor;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.error.AnalysisErrorWithProperties;
import com.google.dart.engine.error.CompileTimeErrorCode;
import com.google.dart.engine.error.ErrorCode;
import com.google.dart.engine.error.ErrorProperty;
import com.google.dart.engine.error.StaticTypeWarningCode;
import com.google.dart.engine.error.StaticWarningCode;
import com.google.dart.engine.internal.constant.EvaluationResultImpl;
import com.google.dart.engine.internal.constant.ValidResult;
import com.google.dart.engine.internal.element.FieldElementImpl;
import com.google.dart.engine.internal.element.FieldFormalParameterElementImpl;
import com.google.dart.engine.internal.element.LabelElementImpl;
import com.google.dart.engine.internal.element.ParameterElementImpl;
import com.google.dart.engine.internal.element.member.ConstructorMember;
import com.google.dart.engine.internal.error.ErrorReporter;
import com.google.dart.engine.internal.resolver.ElementResolver;
import com.google.dart.engine.internal.resolver.InheritanceManager;
import com.google.dart.engine.internal.resolver.MemberMap;
import com.google.dart.engine.internal.resolver.TypeProvider;
import com.google.dart.engine.internal.scope.Namespace;
import com.google.dart.engine.internal.scope.NamespaceBuilder;
import com.google.dart.engine.internal.type.DynamicTypeImpl;
import com.google.dart.engine.internal.type.VoidTypeImpl;
import com.google.dart.engine.parser.ParserErrorCode;
import com.google.dart.engine.resolver.ResolverErrorCode;
import com.google.dart.engine.scanner.Keyword;
import com.google.dart.engine.scanner.KeywordToken;
import com.google.dart.engine.scanner.Token;
import com.google.dart.engine.scanner.TokenType;
import com.google.dart.engine.sdk.DartSdk;
import com.google.dart.engine.sdk.SdkLibrary;
import com.google.dart.engine.type.FunctionType;
import com.google.dart.engine.type.InterfaceType;
import com.google.dart.engine.type.Type;
import com.google.dart.engine.type.TypeParameterType;
import com.google.dart.engine.utilities.dart.ParameterKind;
import com.google.dart.engine.utilities.general.ObjectUtilities;
import com.google.dart.engine.utilities.general.StringUtilities;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Instances of the class {@code ErrorVerifier} traverse an AST structure looking for additional
* errors and warnings not covered by the parser and resolver.
*
* @coverage dart.engine.resolver
*/
public class ErrorVerifier extends RecursiveAstVisitor<Void> {
/**
* 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.
*/
private enum INIT_STATE {
NOT_INIT,
INIT_IN_DECLARATION,
INIT_IN_FIELD_FORMAL,
INIT_IN_INITIALIZERS
}
/**
* Return the static type of the given expression that is to be used for type analysis.
*
* @param expression the expression whose type is to be returned
* @return the static type of the given expression
*/
public static Type getStaticType(Expression expression) {
Type type = expression.getStaticType();
if (type == null) {
// TODO(brianwilkerson) This should never happen.
return DynamicTypeImpl.getInstance();
}
return type;
}
/**
* Return the variable element represented by the given expression, or {@code null} if there is no
* such element.
*
* @param expression the expression whose element is to be returned
* @return the variable element represented by the expression
*/
public static VariableElement getVariableElement(Expression expression) {
if (expression instanceof Identifier) {
Element element = ((Identifier) expression).getStaticElement();
if (element instanceof VariableElement) {
return (VariableElement) element;
}
}
return null;
}
/**
* The error reporter by which errors will be reported.
*/
private ErrorReporter errorReporter;
/**
* The current library that is being analyzed.
*/
private LibraryElement currentLibrary;
/**
* The type representing the type 'bool'.
*/
private final InterfaceType boolType;
/**
* The type representing the type 'int'.
*/
private final InterfaceType intType;
/**
* The object providing access to the types defined by the language.
*/
private final TypeProvider typeProvider;
/**
* The manager for the inheritance mappings.
*/
private final InheritanceManager inheritanceManager;
/**
* This is set to {@code true} iff the visitor is currently visiting children nodes of a
* {@link ConstructorDeclaration} and the constructor is 'const'.
*
* @see #visitConstructorDeclaration(ConstructorDeclaration)
*/
private boolean isEnclosingConstructorConst;
/**
* A flag indicating whether we are currently within a function body marked as being asynchronous.
*/
private boolean inAsync = false;
/**
* A flag indicating whether we are currently within a function body marked as being a generator.
*/
private boolean inGenerator = false;
/**
* This is set to {@code true} iff the visitor is currently visiting children nodes of a
* {@link CatchClause}.
*
* @see #visitCatchClause(CatchClause)
*/
private boolean isInCatchClause;
/**
* This is set to {@code true} iff the visitor is currently visiting children nodes of an
* {@link Comment}.
*/
private boolean isInComment;
/**
* This is set to {@code true} iff the visitor is currently visiting children nodes of an
* {@link InstanceCreationExpression}.
*/
private boolean isInConstInstanceCreation;
/**
* This is set to {@code true} iff the visitor is currently visiting children nodes of a native
* {@link ClassDeclaration}.
*/
private boolean isInNativeClass;
/**
* This is set to {@code true} iff the visitor is currently visiting a static variable
* declaration.
*/
private boolean isInStaticVariableDeclaration;
/**
* This is set to {@code true} iff the visitor is currently visiting an instance variable
* declaration.
*/
private boolean isInInstanceVariableDeclaration;
/**
* This is set to {@code true} iff the visitor is currently visiting an instance variable
* initializer.
*/
private boolean isInInstanceVariableInitializer;
/**
* This is set to {@code true} iff the visitor is currently visiting a
* {@link ConstructorInitializer}.
*/
private boolean isInConstructorInitializer;
/**
* This is set to {@code true} iff the visitor is currently visiting a
* {@link FunctionTypedFormalParameter}.
*/
private boolean isInFunctionTypedFormalParameter;
/**
* This is set to {@code true} iff the visitor is currently visiting a static method. By "method"
* here getter, setter and operator declarations are also implied since they are all represented
* with a {@link MethodDeclaration} in the AST structure.
*/
private boolean isInStaticMethod;
/**
* This is set to {@code true} iff the visitor is currently visiting a factory constructor.
*/
private boolean isInFactory;
/**
* This is set to {@code true} iff the visitor is currently visiting code in the SDK.
*/
private boolean isInSystemLibrary;
/**
* A flag indicating whether the current library contains at least one import directive with a URI
* that uses the "dart-ext" scheme.
*/
private boolean hasExtUri;
/**
* This is set to {@code false} on the entry of every {@link BlockFunctionBody}, and is restored
* to the enclosing value on exit. The value is used in
* {@link #checkForMixedReturns(BlockFunctionBody)} to prevent both
* {@link StaticWarningCode#MIXED_RETURN_TYPES} and {@link StaticWarningCode#RETURN_WITHOUT_VALUE}
* from being generated in the same function body.
*/
private boolean hasReturnWithoutValue = false;
/**
* The class containing the AST nodes being visited, or {@code null} if we are not in the scope of
* a class.
*/
private ClassElement enclosingClass;
/**
* The method or function that we are currently visiting, or {@code null} if we are not inside a
* method or function.
*/
private ExecutableElement enclosingFunction;
/**
* The return statements found in the method or function that we are currently visiting that have
* a return value.
*/
private ArrayList<ReturnStatement> returnsWith = new ArrayList<ReturnStatement>();
/**
* The return statements found in the method or function that we are currently visiting that do
* not have a return value.
*/
private ArrayList<ReturnStatement> returnsWithout = new ArrayList<ReturnStatement>();
/**
* This map is initialized when visiting the contents of a class declaration. If the visitor is
* not in an enclosing class declaration, then the map is set to {@code null}.
* <p>
* When set the map maps the set of {@link FieldElement}s in the class to an
* {@link INIT_STATE#NOT_INIT} or {@link INIT_STATE#INIT_IN_DECLARATION}. <code>checkFor*</code>
* methods, specifically {@link #checkForAllFinalInitializedErrorCodes(ConstructorDeclaration)},
* can make a copy of the map to compute error code states. <code>checkFor*</code> methods should
* only ever make a copy, or read from this map after it has been set in
* {@link #visitClassDeclaration(ClassDeclaration)}.
*
* @see #visitClassDeclaration(ClassDeclaration)
* @see #checkForAllFinalInitializedErrorCodes(ConstructorDeclaration)
*/
private HashMap<FieldElement, INIT_STATE> initialFieldElementsMap;
/**
* A table mapping name of the library to the export directive which export this library.
*/
private HashMap<String, LibraryElement> nameToExportElement = new HashMap<String, LibraryElement>();
/**
* A table mapping name of the library to the import directive which import this library.
*/
private HashMap<String, LibraryElement> nameToImportElement = new HashMap<String, LibraryElement>();
/**
* A table mapping names to the exported elements.
*/
private HashMap<String, Element> exportedElements = new HashMap<String, Element>();
/**
* A set of the names of the variable initializers we are visiting now.
*/
private HashSet<String> namesForReferenceToDeclaredVariableInInitializer = new HashSet<String>();
/**
* A list of types used by the {@link CompileTimeErrorCode#EXTENDS_DISALLOWED_CLASS} and
* {@link CompileTimeErrorCode#IMPLEMENTS_DISALLOWED_CLASS} error codes.
*/
private final InterfaceType[] DISALLOWED_TYPES_TO_EXTEND_OR_IMPLEMENT;
/**
* Static final string with value {@code "getter "} used in the construction of the
* {@link StaticWarningCode#NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE}, and similar, error
* code messages.
*
* @see #checkForNonAbstractClassInheritsAbstractMember(ClassDeclaration)
*/
private final static String GETTER_SPACE = "getter ";
/**
* Static final string with value {@code "setter "} used in the construction of the
* {@link StaticWarningCode#NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE}, and similar, error
* code messages.
*
* @see #checkForNonAbstractClassInheritsAbstractMember(ClassDeclaration)
*/
private final static String SETTER_SPACE = "setter ";
/**
* Initialize the {@link ErrorVerifier} visitor.
*/
public ErrorVerifier(ErrorReporter errorReporter, LibraryElement currentLibrary,
TypeProvider typeProvider, InheritanceManager inheritanceManager) {
this.errorReporter = errorReporter;
this.currentLibrary = currentLibrary;
this.isInSystemLibrary = currentLibrary.getSource().isInSystemLibrary();
this.hasExtUri = currentLibrary.hasExtUri();
this.typeProvider = typeProvider;
this.inheritanceManager = inheritanceManager;
isEnclosingConstructorConst = false;
isInCatchClause = false;
isInStaticVariableDeclaration = false;
isInInstanceVariableDeclaration = false;
isInInstanceVariableInitializer = false;
isInConstructorInitializer = false;
isInStaticMethod = false;
boolType = typeProvider.getBoolType();
intType = typeProvider.getIntType();
DISALLOWED_TYPES_TO_EXTEND_OR_IMPLEMENT = new InterfaceType[] {
typeProvider.getNullType(), typeProvider.getNumType(), intType,
typeProvider.getDoubleType(), boolType, typeProvider.getStringType()};
}
@Override
public Void visitAnnotation(Annotation node) {
checkForInvalidAnnotationFromDeferredLibrary(node);
return super.visitAnnotation(node);
}
@Override
public Void visitArgumentList(ArgumentList node) {
checkForArgumentTypesNotAssignableInList(node);
return super.visitArgumentList(node);
}
@Override
public Void visitAsExpression(AsExpression node) {
checkForTypeAnnotationDeferredClass(node.getType());
return super.visitAsExpression(node);
}
@Override
public Void visitAssertStatement(AssertStatement node) {
checkForNonBoolExpression(node);
return super.visitAssertStatement(node);
}
@Override
public Void visitAssignmentExpression(AssignmentExpression node) {
TokenType operatorType = node.getOperator().getType();
Expression lhs = node.getLeftHandSide();
Expression rhs = node.getRightHandSide();
if (operatorType == TokenType.EQ) {
checkForInvalidAssignment(lhs, rhs);
} else {
checkForInvalidCompoundAssignment(node, lhs, rhs);
checkForArgumentTypeNotAssignableForArgument(rhs);
}
checkForAssignmentToFinal(lhs);
return super.visitAssignmentExpression(node);
}
@Override
public Void visitAwaitExpression(AwaitExpression node) {
if (!inAsync) {
errorReporter.reportErrorForToken(
CompileTimeErrorCode.AWAIT_IN_WRONG_CONTEXT,
node.getAwaitKeyword());
}
return super.visitAwaitExpression(node);
}
@Override
public Void visitBinaryExpression(BinaryExpression node) {
Token operator = node.getOperator();
TokenType type = operator.getType();
if (type == TokenType.AMPERSAND_AMPERSAND || type == TokenType.BAR_BAR) {
String lexeme = operator.getLexeme();
checkForAssignability(
node.getLeftOperand(),
boolType,
StaticTypeWarningCode.NON_BOOL_OPERAND,
lexeme);
checkForAssignability(
node.getRightOperand(),
boolType,
StaticTypeWarningCode.NON_BOOL_OPERAND,
lexeme);
} else {
checkForArgumentTypeNotAssignableForArgument(node.getRightOperand());
}
return super.visitBinaryExpression(node);
}
@Override
public Void visitBlockFunctionBody(BlockFunctionBody node) {
boolean wasInAsync = inAsync;
boolean wasInGenerator = inGenerator;
boolean previousHasReturnWithoutValue = hasReturnWithoutValue;
hasReturnWithoutValue = false;
ArrayList<ReturnStatement> previousReturnsWith = returnsWith;
ArrayList<ReturnStatement> previousReturnsWithout = returnsWithout;
try {
inAsync = node.isAsynchronous();
inGenerator = node.isGenerator();
returnsWith = new ArrayList<ReturnStatement>();
returnsWithout = new ArrayList<ReturnStatement>();
super.visitBlockFunctionBody(node);
checkForMixedReturns(node);
} finally {
inAsync = wasInAsync;
inGenerator = wasInGenerator;
returnsWith = previousReturnsWith;
returnsWithout = previousReturnsWithout;
hasReturnWithoutValue = previousHasReturnWithoutValue;
}
return null;
}
@Override
public Void visitBreakStatement(BreakStatement node) {
SimpleIdentifier labelNode = node.getLabel();
if (labelNode != null) {
Element labelElement = labelNode.getStaticElement();
if (labelElement instanceof LabelElementImpl
&& ((LabelElementImpl) labelElement).isOnSwitchMember()) {
errorReporter.reportErrorForNode(ResolverErrorCode.BREAK_LABEL_ON_SWITCH_MEMBER, labelNode);
}
}
return null;
}
@Override
public Void visitCatchClause(CatchClause node) {
boolean previousIsInCatchClause = isInCatchClause;
try {
isInCatchClause = true;
checkForTypeAnnotationDeferredClass(node.getExceptionType());
return super.visitCatchClause(node);
} finally {
isInCatchClause = previousIsInCatchClause;
}
}
@Override
public Void visitClassDeclaration(ClassDeclaration node) {
ClassElement outerClass = enclosingClass;
try {
isInNativeClass = node.getNativeClause() != null;
enclosingClass = node.getElement();
ExtendsClause extendsClause = node.getExtendsClause();
ImplementsClause implementsClause = node.getImplementsClause();
WithClause withClause = node.getWithClause();
checkForBuiltInIdentifierAsName(
node.getName(),
CompileTimeErrorCode.BUILT_IN_IDENTIFIER_AS_TYPE_NAME);
checkForMemberWithClassName();
checkForNoDefaultSuperConstructorImplicit(node);
checkForConflictingTypeVariableErrorCodes(node);
// Only do error checks on the clause nodes if there is a non-null clause
if (implementsClause != null || extendsClause != null || withClause != null) {
// Only check for all of the inheritance logic around clauses if there isn't an error code
// such as "Cannot extend double" already on the class.
if (!checkForImplementsDisallowedClass(implementsClause)
&& !checkForExtendsDisallowedClass(extendsClause)
&& !checkForAllMixinErrorCodes(withClause)) {
checkForExtendsDeferredClass(extendsClause);
checkForImplementsDeferredClass(implementsClause);
checkForNonAbstractClassInheritsAbstractMember(node.getName());
checkForInconsistentMethodInheritance();
checkForRecursiveInterfaceInheritance(enclosingClass);
checkForConflictingGetterAndMethod();
checkForConflictingInstanceGetterAndSuperclassMember();
checkImplementsSuperClass(node);
checkImplementsFunctionWithoutCall(node);
}
}
// initialize initialFieldElementsMap
if (enclosingClass != null) {
FieldElement[] fieldElements = enclosingClass.getFields();
initialFieldElementsMap = new HashMap<FieldElement, INIT_STATE>(fieldElements.length);
for (FieldElement fieldElement : fieldElements) {
if (!fieldElement.isSynthetic()) {
initialFieldElementsMap.put(fieldElement, fieldElement.getInitializer() == null
? INIT_STATE.NOT_INIT : INIT_STATE.INIT_IN_DECLARATION);
}
}
}
checkForFinalNotInitializedInClass(node);
checkForDuplicateDefinitionInheritance();
checkForConflictingInstanceMethodSetter(node);
return super.visitClassDeclaration(node);
} finally {
isInNativeClass = false;
initialFieldElementsMap = null;
enclosingClass = outerClass;
}
}
@Override
public Void visitClassTypeAlias(ClassTypeAlias node) {
checkForBuiltInIdentifierAsName(
node.getName(),
CompileTimeErrorCode.BUILT_IN_IDENTIFIER_AS_TYPEDEF_NAME);
ClassElement outerClassElement = enclosingClass;
try {
enclosingClass = node.getElement();
ImplementsClause implementsClause = node.getImplementsClause();
// Only check for all of the inheritance logic around clauses if there isn't an error code
// such as "Cannot extend double" already on the class.
if (!checkForExtendsDisallowedClassInTypeAlias(node)
&& !checkForImplementsDisallowedClass(implementsClause)
&& !checkForAllMixinErrorCodes(node.getWithClause())) {
checkForExtendsDeferredClassInTypeAlias(node);
checkForImplementsDeferredClass(implementsClause);
checkForRecursiveInterfaceInheritance(enclosingClass);
checkForNonAbstractClassInheritsAbstractMember(node.getName());
}
} finally {
enclosingClass = outerClassElement;
}
return super.visitClassTypeAlias(node);
}
@Override
public Void visitComment(Comment node) {
isInComment = true;
try {
return super.visitComment(node);
} finally {
isInComment = false;
}
}
@Override
public Void visitCompilationUnit(CompilationUnit node) {
checkForDeferredPrefixCollisions(node);
return super.visitCompilationUnit(node);
}
@Override
public Void visitConditionalExpression(ConditionalExpression node) {
checkForNonBoolCondition(node.getCondition());
return super.visitConditionalExpression(node);
}
@Override
public Void visitConstructorDeclaration(ConstructorDeclaration node) {
ExecutableElement outerFunction = enclosingFunction;
try {
ConstructorElement constructorElement = node.getElement();
enclosingFunction = constructorElement;
isEnclosingConstructorConst = node.getConstKeyword() != null;
isInFactory = node.getFactoryKeyword() != null;
checkForInvalidModifierOnBody(
node.getBody(),
CompileTimeErrorCode.INVALID_MODIFIER_ON_CONSTRUCTOR);
checkForConstConstructorWithNonFinalField(node, constructorElement);
checkForConstConstructorWithNonConstSuper(node);
checkForConflictingConstructorNameAndMember(node, constructorElement);
checkForAllFinalInitializedErrorCodes(node);
checkForRedirectingConstructorErrorCodes(node);
checkForMultipleSuperInitializers(node);
checkForRecursiveConstructorRedirect(node, constructorElement);
if (!checkForRecursiveFactoryRedirect(node, constructorElement)) {
checkForAllRedirectConstructorErrorCodes(node);
}
checkForUndefinedConstructorInInitializerImplicit(node);
checkForRedirectToNonConstConstructor(node, constructorElement);
checkForReturnInGenerativeConstructor(node);
return super.visitConstructorDeclaration(node);
} finally {
isEnclosingConstructorConst = false;
isInFactory = false;
enclosingFunction = outerFunction;
}
}
@Override
public Void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
isInConstructorInitializer = true;
try {
SimpleIdentifier fieldName = node.getFieldName();
Element staticElement = fieldName.getStaticElement();
checkForInvalidField(node, fieldName, staticElement);
checkForFieldInitializerNotAssignable(node, staticElement);
return super.visitConstructorFieldInitializer(node);
} finally {
isInConstructorInitializer = false;
}
}
@Override
public Void visitContinueStatement(ContinueStatement node) {
SimpleIdentifier labelNode = node.getLabel();
if (labelNode != null) {
Element labelElement = labelNode.getStaticElement();
if (labelElement instanceof LabelElementImpl
&& ((LabelElementImpl) labelElement).isOnSwitchStatement()) {
errorReporter.reportErrorForNode(ResolverErrorCode.CONTINUE_LABEL_ON_SWITCH, labelNode);
}
}
return null;
}
@Override
public Void visitDefaultFormalParameter(DefaultFormalParameter node) {
checkForInvalidAssignment(node.getIdentifier(), node.getDefaultValue());
checkForDefaultValueInFunctionTypedParameter(node);
return super.visitDefaultFormalParameter(node);
}
@Override
public Void visitDoStatement(DoStatement node) {
checkForNonBoolCondition(node.getCondition());
return super.visitDoStatement(node);
}
@Override
public Void visitExportDirective(ExportDirective node) {
ExportElement exportElement = node.getElement();
if (exportElement != null) {
LibraryElement exportedLibrary = exportElement.getExportedLibrary();
checkForAmbiguousExport(node, exportElement, exportedLibrary);
checkForExportDuplicateLibraryName(node, exportElement, exportedLibrary);
checkForExportInternalLibrary(node, exportElement);
}
return super.visitExportDirective(node);
}
@Override
public Void visitExpressionFunctionBody(ExpressionFunctionBody node) {
boolean wasInAsync = inAsync;
boolean wasInGenerator = inGenerator;
try {
inAsync = node.isAsynchronous();
inGenerator = node.isGenerator();
FunctionType functionType = enclosingFunction == null ? null : enclosingFunction.getType();
Type expectedReturnType = functionType == null ? DynamicTypeImpl.getInstance()
: functionType.getReturnType();
checkForReturnOfInvalidType(node.getExpression(), expectedReturnType);
return super.visitExpressionFunctionBody(node);
} finally {
inAsync = wasInAsync;
inGenerator = wasInGenerator;
}
}
@Override
public Void visitFieldDeclaration(FieldDeclaration node) {
isInStaticVariableDeclaration = node.isStatic();
isInInstanceVariableDeclaration = !isInStaticVariableDeclaration;
if (isInInstanceVariableDeclaration) {
VariableDeclarationList variables = node.getFields();
if (variables.isConst()) {
errorReporter.reportErrorForToken(
CompileTimeErrorCode.CONST_INSTANCE_FIELD,
variables.getKeyword());
}
}
try {
checkForAllInvalidOverrideErrorCodesForField(node);
return super.visitFieldDeclaration(node);
} finally {
isInStaticVariableDeclaration = false;
isInInstanceVariableDeclaration = false;
}
}
@Override
public Void visitFieldFormalParameter(FieldFormalParameter node) {
checkForValidField(node);
checkForConstFormalParameter(node);
checkForPrivateOptionalParameter(node);
checkForFieldInitializingFormalRedirectingConstructor(node);
checkForTypeAnnotationDeferredClass(node.getType());
return super.visitFieldFormalParameter(node);
}
@Override
public Void visitFunctionDeclaration(FunctionDeclaration node) {
ExecutableElement outerFunction = enclosingFunction;
try {
SimpleIdentifier identifier = node.getName();
String methodName = "";
if (identifier != null) {
methodName = identifier.getName();
}
enclosingFunction = node.getElement();
TypeName returnType = node.getReturnType();
if (node.isSetter() || node.isGetter()) {
checkForMismatchedAccessorTypes(node, methodName);
if (node.isSetter()) {
FunctionExpression functionExpression = node.getFunctionExpression();
if (functionExpression != null) {
checkForWrongNumberOfParametersForSetter(identifier, functionExpression.getParameters());
}
checkForNonVoidReturnTypeForSetter(returnType);
}
}
if (node.isSetter()) {
checkForInvalidModifierOnBody(
node.getFunctionExpression().getBody(),
CompileTimeErrorCode.INVALID_MODIFIER_ON_SETTER);
}
checkForTypeAnnotationDeferredClass(returnType);
return super.visitFunctionDeclaration(node);
} finally {
enclosingFunction = outerFunction;
}
}
@Override
public Void visitFunctionExpression(FunctionExpression node) {
// If this function expression is wrapped in a function declaration, don't change the
// enclosingFunction field.
if (!(node.getParent() instanceof FunctionDeclaration)) {
ExecutableElement outerFunction = enclosingFunction;
try {
enclosingFunction = node.getElement();
return super.visitFunctionExpression(node);
} finally {
enclosingFunction = outerFunction;
}
} else {
return super.visitFunctionExpression(node);
}
}
@Override
public Void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
Expression functionExpression = node.getFunction();
Type expressionType = functionExpression.getStaticType();
if (!isFunctionType(expressionType)) {
errorReporter.reportErrorForNode(
StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
functionExpression);
}
return super.visitFunctionExpressionInvocation(node);
}
@Override
public Void visitFunctionTypeAlias(FunctionTypeAlias node) {
checkForBuiltInIdentifierAsName(
node.getName(),
CompileTimeErrorCode.BUILT_IN_IDENTIFIER_AS_TYPEDEF_NAME);
checkForDefaultValueInFunctionTypeAlias(node);
checkForTypeAliasCannotReferenceItself_function(node);
return super.visitFunctionTypeAlias(node);
}
@Override
public Void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
boolean old = isInFunctionTypedFormalParameter;
isInFunctionTypedFormalParameter = true;
try {
checkForTypeAnnotationDeferredClass(node.getReturnType());
return super.visitFunctionTypedFormalParameter(node);
} finally {
isInFunctionTypedFormalParameter = old;
}
}
@Override
public Void visitIfStatement(IfStatement node) {
checkForNonBoolCondition(node.getCondition());
return super.visitIfStatement(node);
}
@Override
public Void visitImportDirective(ImportDirective node) {
ImportElement importElement = node.getElement();
if (importElement != null) {
checkForImportDuplicateLibraryName(node, importElement);
checkForImportInternalLibrary(node, importElement);
}
return super.visitImportDirective(node);
}
@Override
public Void visitIndexExpression(IndexExpression node) {
checkForArgumentTypeNotAssignableForArgument(node.getIndex());
return super.visitIndexExpression(node);
}
@Override
public Void visitInstanceCreationExpression(InstanceCreationExpression node) {
isInConstInstanceCreation = node.isConst();
try {
ConstructorName constructorName = node.getConstructorName();
TypeName typeName = constructorName.getType();
Type type = typeName.getType();
if (type instanceof InterfaceType) {
InterfaceType interfaceType = (InterfaceType) type;
checkForConstOrNewWithAbstractClass(node, typeName, interfaceType);
checkForConstOrNewWithEnum(node, typeName, interfaceType);
if (isInConstInstanceCreation) {
checkForConstWithNonConst(node);
checkForConstWithUndefinedConstructor(node, constructorName, typeName);
checkForConstWithTypeParameters(typeName);
checkForConstDeferredClass(node, constructorName, typeName);
} else {
checkForNewWithUndefinedConstructor(node, constructorName, typeName);
}
}
return super.visitInstanceCreationExpression(node);
} finally {
isInConstInstanceCreation = false;
}
}
@Override
public Void visitIsExpression(IsExpression node) {
checkForTypeAnnotationDeferredClass(node.getType());
return super.visitIsExpression(node);
}
@Override
public Void visitListLiteral(ListLiteral node) {
TypeArgumentList typeArguments = node.getTypeArguments();
if (typeArguments != null) {
if (node.getConstKeyword() != null) {
NodeList<TypeName> arguments = typeArguments.getArguments();
if (arguments.size() != 0) {
checkForInvalidTypeArgumentInConstTypedLiteral(
arguments,
CompileTimeErrorCode.INVALID_TYPE_ARGUMENT_IN_CONST_LIST);
}
}
checkForExpectedOneListTypeArgument(node, typeArguments);
checkForListElementTypeNotAssignable(node, typeArguments);
}
return super.visitListLiteral(node);
}
@Override
public Void visitMapLiteral(MapLiteral node) {
TypeArgumentList typeArguments = node.getTypeArguments();
if (typeArguments != null) {
NodeList<TypeName> arguments = typeArguments.getArguments();
if (arguments.size() != 0) {
if (node.getConstKeyword() != null) {
checkForInvalidTypeArgumentInConstTypedLiteral(
arguments,
CompileTimeErrorCode.INVALID_TYPE_ARGUMENT_IN_CONST_MAP);
}
}
checkExpectedTwoMapTypeArguments(typeArguments);
checkForMapTypeNotAssignable(node, typeArguments);
}
checkForNonConstMapAsExpressionStatement(node);
return super.visitMapLiteral(node);
}
@Override
public Void visitMethodDeclaration(MethodDeclaration node) {
ExecutableElement previousFunction = enclosingFunction;
try {
isInStaticMethod = node.isStatic();
enclosingFunction = node.getElement();
SimpleIdentifier identifier = node.getName();
String methodName = "";
if (identifier != null) {
methodName = identifier.getName();
}
TypeName returnTypeName = node.getReturnType();
if (node.isSetter() || node.isGetter()) {
checkForMismatchedAccessorTypes(node, methodName);
}
if (node.isGetter()) {
checkForVoidReturnType(node);
checkForConflictingStaticGetterAndInstanceSetter(node);
} else if (node.isSetter()) {
checkForInvalidModifierOnBody(
node.getBody(),
CompileTimeErrorCode.INVALID_MODIFIER_ON_SETTER);
checkForWrongNumberOfParametersForSetter(node.getName(), node.getParameters());
checkForNonVoidReturnTypeForSetter(returnTypeName);
checkForConflictingStaticSetterAndInstanceMember(node);
} else if (node.isOperator()) {
checkForOptionalParameterInOperator(node);
checkForWrongNumberOfParametersForOperator(node);
checkForNonVoidReturnTypeForOperator(node);
}
checkForConcreteClassWithAbstractMember(node);
checkForAllInvalidOverrideErrorCodesForMethod(node);
checkForTypeAnnotationDeferredClass(returnTypeName);
return super.visitMethodDeclaration(node);
} finally {
enclosingFunction = previousFunction;
isInStaticMethod = false;
}
}
@Override
public Void visitMethodInvocation(MethodInvocation node) {
Expression target = node.getRealTarget();
SimpleIdentifier methodName = node.getMethodName();
if (target != null) {
ClassElement typeReference = ElementResolver.getTypeReference(target);
checkForStaticAccessToInstanceMember(typeReference, methodName);
checkForInstanceAccessToStaticMember(typeReference, methodName);
} else {
checkForUnqualifiedReferenceToNonLocalStaticMember(methodName);
}
return super.visitMethodInvocation(node);
}
@Override
public Void visitNativeClause(NativeClause node) {
// TODO(brianwilkerson) Figure out the right rule for when 'native' is allowed.
if (!isInSystemLibrary) {
errorReporter.reportErrorForNode(ParserErrorCode.NATIVE_CLAUSE_IN_NON_SDK_CODE, node);
}
return super.visitNativeClause(node);
}
@Override
public Void visitNativeFunctionBody(NativeFunctionBody node) {
checkForNativeFunctionBodyInNonSDKCode(node);
return super.visitNativeFunctionBody(node);
}
@Override
public Void visitPostfixExpression(PostfixExpression node) {
checkForAssignmentToFinal(node.getOperand());
checkForIntNotAssignable(node.getOperand());
return super.visitPostfixExpression(node);
}
@Override
public Void visitPrefixedIdentifier(PrefixedIdentifier node) {
if (!(node.getParent() instanceof Annotation)) {
ClassElement typeReference = ElementResolver.getTypeReference(node.getPrefix());
SimpleIdentifier name = node.getIdentifier();
checkForStaticAccessToInstanceMember(typeReference, name);
checkForInstanceAccessToStaticMember(typeReference, name);
}
return super.visitPrefixedIdentifier(node);
}
@Override
public Void visitPrefixExpression(PrefixExpression node) {
TokenType operatorType = node.getOperator().getType();
Expression operand = node.getOperand();
if (operatorType == TokenType.BANG) {
checkForNonBoolNegationExpression(operand);
} else if (operatorType.isIncrementOperator()) {
checkForAssignmentToFinal(operand);
}
checkForIntNotAssignable(operand);
return super.visitPrefixExpression(node);
}
@Override
public Void visitPropertyAccess(PropertyAccess node) {
ClassElement typeReference = ElementResolver.getTypeReference(node.getRealTarget());
SimpleIdentifier propertyName = node.getPropertyName();
checkForStaticAccessToInstanceMember(typeReference, propertyName);
checkForInstanceAccessToStaticMember(typeReference, propertyName);
return super.visitPropertyAccess(node);
}
@Override
public Void visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
isInConstructorInitializer = true;
try {
return super.visitRedirectingConstructorInvocation(node);
} finally {
isInConstructorInitializer = false;
}
}
@Override
public Void visitRethrowExpression(RethrowExpression node) {
checkForRethrowOutsideCatch(node);
return super.visitRethrowExpression(node);
}
@Override
public Void visitReturnStatement(ReturnStatement node) {
if (node.getExpression() == null) {
returnsWithout.add(node);
} else {
returnsWith.add(node);
}
checkForAllReturnStatementErrorCodes(node);
return super.visitReturnStatement(node);
}
@Override
public Void visitSimpleFormalParameter(SimpleFormalParameter node) {
checkForConstFormalParameter(node);
checkForPrivateOptionalParameter(node);
checkForTypeAnnotationDeferredClass(node.getType());
return super.visitSimpleFormalParameter(node);
}
@Override
public Void visitSimpleIdentifier(SimpleIdentifier node) {
checkForImplicitThisReferenceInInitializer(node);
if (!isUnqualifiedReferenceToNonLocalStaticMemberAllowed(node)) {
checkForUnqualifiedReferenceToNonLocalStaticMember(node);
}
return super.visitSimpleIdentifier(node);
}
@Override
public Void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
isInConstructorInitializer = true;
try {
return super.visitSuperConstructorInvocation(node);
} finally {
isInConstructorInitializer = false;
}
}
@Override
public Void visitSwitchStatement(SwitchStatement node) {
checkForSwitchExpressionNotAssignable(node);
checkForCaseBlocksNotTerminated(node);
checkForMissingEnumConstantInSwitch(node);
return super.visitSwitchStatement(node);
}
@Override
public Void visitThisExpression(ThisExpression node) {
checkForInvalidReferenceToThis(node);
return super.visitThisExpression(node);
}
@Override
public Void visitThrowExpression(ThrowExpression node) {
checkForConstEvalThrowsException(node);
return super.visitThrowExpression(node);
}
@Override
public Void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
checkForFinalNotInitialized(node.getVariables());
return super.visitTopLevelVariableDeclaration(node);
}
@Override
public Void visitTypeArgumentList(TypeArgumentList node) {
NodeList<TypeName> list = node.getArguments();
for (TypeName typeName : list) {
checkForTypeAnnotationDeferredClass(typeName);
}
return super.visitTypeArgumentList(node);
}
@Override
public Void visitTypeName(TypeName node) {
checkForTypeArgumentNotMatchingBounds(node);
checkForTypeParameterReferencedByStatic(node);
return super.visitTypeName(node);
}
@Override
public Void visitTypeParameter(TypeParameter node) {
checkForBuiltInIdentifierAsName(
node.getName(),
CompileTimeErrorCode.BUILT_IN_IDENTIFIER_AS_TYPE_PARAMETER_NAME);
checkForTypeParameterSupertypeOfItsBound(node);
checkForTypeAnnotationDeferredClass(node.getBound());
return super.visitTypeParameter(node);
}
@Override
public Void visitVariableDeclaration(VariableDeclaration node) {
SimpleIdentifier nameNode = node.getName();
Expression initializerNode = node.getInitializer();
// do checks
checkForInvalidAssignment(nameNode, initializerNode);
// visit name
nameNode.accept(this);
// visit initializer
String name = nameNode.getName();
namesForReferenceToDeclaredVariableInInitializer.add(name);
isInInstanceVariableInitializer = isInInstanceVariableDeclaration;
try {
if (initializerNode != null) {
initializerNode.accept(this);
}
} finally {
isInInstanceVariableInitializer = false;
namesForReferenceToDeclaredVariableInInitializer.remove(name);
}
// done
return null;
}
@Override
public Void visitVariableDeclarationList(VariableDeclarationList node) {
checkForTypeAnnotationDeferredClass(node.getType());
return super.visitVariableDeclarationList(node);
}
@Override
public Void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
checkForFinalNotInitialized(node.getVariables());
return super.visitVariableDeclarationStatement(node);
}
@Override
public Void visitWhileStatement(WhileStatement node) {
checkForNonBoolCondition(node.getCondition());
return super.visitWhileStatement(node);
}
@Override
public Void visitYieldStatement(YieldStatement node) {
if (!inGenerator) {
CompileTimeErrorCode errorCode;
if (node.getStar() != null) {
errorCode = CompileTimeErrorCode.YIELD_EACH_IN_NON_GENERATOR;
} else {
errorCode = CompileTimeErrorCode.YIELD_IN_NON_GENERATOR;
}
errorReporter.reportErrorForNode(errorCode, node);
}
return super.visitYieldStatement(node);
}
/**
* This verifies if the passed map literal has type arguments then there is exactly two.
*
* @param typeArguments the type arguments, always non-{@code null}
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#EXPECTED_TWO_MAP_TYPE_ARGUMENTS
*/
private boolean checkExpectedTwoMapTypeArguments(TypeArgumentList typeArguments) {
// check number of type arguments
int num = typeArguments.getArguments().size();
if (num == 2) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
StaticTypeWarningCode.EXPECTED_TWO_MAP_TYPE_ARGUMENTS,
typeArguments,
num);
return true;
}
/**
* This verifies that the passed constructor declaration does not violate any of the error codes
* relating to the initialization of fields in the enclosing class.
*
* @param node the {@link ConstructorDeclaration} to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see #initialFieldElementsMap
* @see CompileTimeErrorCode#FINAL_INITIALIZED_IN_DECLARATION_AND_CONSTRUCTOR
* @see CompileTimeErrorCode#FINAL_INITIALIZED_MULTIPLE_TIMES
*/
private boolean checkForAllFinalInitializedErrorCodes(ConstructorDeclaration node) {
if (node.getFactoryKeyword() != null || node.getRedirectedConstructor() != null
|| node.getExternalKeyword() != null) {
return false;
}
// Ignore if native class.
if (isInNativeClass) {
return false;
}
boolean foundError = false;
HashMap<FieldElement, INIT_STATE> fieldElementsMap = new HashMap<FieldElement, INIT_STATE>(
initialFieldElementsMap);
// Visit all of the field formal parameters
NodeList<FormalParameter> formalParameters = node.getParameters().getParameters();
for (FormalParameter formalParameter : formalParameters) {
FormalParameter parameter = formalParameter;
if (parameter instanceof DefaultFormalParameter) {
parameter = ((DefaultFormalParameter) parameter).getParameter();
}
if (parameter instanceof FieldFormalParameter) {
FieldElement fieldElement = ((FieldFormalParameterElementImpl) parameter.getElement()).getField();
INIT_STATE state = fieldElementsMap.get(fieldElement);
if (state == INIT_STATE.NOT_INIT) {
fieldElementsMap.put(fieldElement, INIT_STATE.INIT_IN_FIELD_FORMAL);
} else if (state == INIT_STATE.INIT_IN_DECLARATION) {
if (fieldElement.isFinal() || fieldElement.isConst()) {
errorReporter.reportErrorForNode(
StaticWarningCode.FINAL_INITIALIZED_IN_DECLARATION_AND_CONSTRUCTOR,
formalParameter.getIdentifier(),
fieldElement.getDisplayName());
foundError = true;
}
} else if (state == INIT_STATE.INIT_IN_FIELD_FORMAL) {
if (fieldElement.isFinal() || fieldElement.isConst()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.FINAL_INITIALIZED_MULTIPLE_TIMES,
formalParameter.getIdentifier(),
fieldElement.getDisplayName());
foundError = true;
}
}
}
}
// Visit all of the initializers
NodeList<ConstructorInitializer> initializers = node.getInitializers();
for (ConstructorInitializer constructorInitializer : initializers) {
if (constructorInitializer instanceof RedirectingConstructorInvocation) {
return false;
}
if (constructorInitializer instanceof ConstructorFieldInitializer) {
ConstructorFieldInitializer constructorFieldInitializer = (ConstructorFieldInitializer) constructorInitializer;
SimpleIdentifier fieldName = constructorFieldInitializer.getFieldName();
Element element = fieldName.getStaticElement();
if (element instanceof FieldElement) {
FieldElement fieldElement = (FieldElement) element;
INIT_STATE state = fieldElementsMap.get(fieldElement);
if (state == INIT_STATE.NOT_INIT) {
fieldElementsMap.put(fieldElement, INIT_STATE.INIT_IN_INITIALIZERS);
} else if (state == INIT_STATE.INIT_IN_DECLARATION) {
if (fieldElement.isFinal() || fieldElement.isConst()) {
errorReporter.reportErrorForNode(
StaticWarningCode.FIELD_INITIALIZED_IN_INITIALIZER_AND_DECLARATION,
fieldName);
foundError = true;
}
} else if (state == INIT_STATE.INIT_IN_FIELD_FORMAL) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.FIELD_INITIALIZED_IN_PARAMETER_AND_INITIALIZER,
fieldName);
foundError = true;
} else if (state == INIT_STATE.INIT_IN_INITIALIZERS) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.FIELD_INITIALIZED_BY_MULTIPLE_INITIALIZERS,
fieldName,
fieldElement.getDisplayName());
foundError = true;
}
}
}
}
// Visit all of the states in the map to ensure that none were never initialized.
for (Entry<FieldElement, INIT_STATE> entry : fieldElementsMap.entrySet()) {
if (entry.getValue() == INIT_STATE.NOT_INIT) {
FieldElement fieldElement = entry.getKey();
if (fieldElement.isConst()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_NOT_INITIALIZED,
node.getReturnType(),
fieldElement.getName());
foundError = true;
} else if (fieldElement.isFinal()) {
errorReporter.reportErrorForNode(
StaticWarningCode.FINAL_NOT_INITIALIZED,
node.getReturnType(),
fieldElement.getName());
foundError = true;
}
}
}
return foundError;
}
/**
* This checks the passed executable element against override-error codes.
*
* @param executableElement a non-null {@link ExecutableElement} to evaluate
* @param overriddenExecutable the element that the executableElement is overriding
* @param parameters the parameters of the executable element
* @param errorNameTarget the node to report problems on
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#INSTANCE_METHOD_NAME_COLLIDES_WITH_SUPERCLASS_STATIC
* @see CompileTimeErrorCode#INVALID_OVERRIDE_REQUIRED
* @see CompileTimeErrorCode#INVALID_OVERRIDE_POSITIONAL
* @see CompileTimeErrorCode#INVALID_OVERRIDE_NAMED
* @see StaticWarningCode#INVALID_GETTER_OVERRIDE_RETURN_TYPE
* @see StaticWarningCode#INVALID_METHOD_OVERRIDE_RETURN_TYPE
* @see StaticWarningCode#INVALID_METHOD_OVERRIDE_NORMAL_PARAM_TYPE
* @see StaticWarningCode#INVALID_SETTER_OVERRIDE_NORMAL_PARAM_TYPE
* @see StaticWarningCode#INVALID_METHOD_OVERRIDE_OPTIONAL_PARAM_TYPE
* @see StaticWarningCode#INVALID_METHOD_OVERRIDE_NAMED_PARAM_TYPE
* @see StaticWarningCode#INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES
*/
private boolean checkForAllInvalidOverrideErrorCodes(ExecutableElement executableElement,
ExecutableElement overriddenExecutable, ParameterElement[] parameters,
AstNode[] parameterLocations, SimpleIdentifier errorNameTarget) {
boolean isGetter = false;
boolean isSetter = false;
if (executableElement instanceof PropertyAccessorElement) {
PropertyAccessorElement accessorElement = (PropertyAccessorElement) executableElement;
isGetter = accessorElement.isGetter();
isSetter = accessorElement.isSetter();
}
String executableElementName = executableElement.getName();
FunctionType overridingFT = executableElement.getType();
FunctionType overriddenFT = overriddenExecutable.getType();
InterfaceType enclosingType = enclosingClass.getType();
overriddenFT = inheritanceManager.substituteTypeArgumentsInMemberFromInheritance(
overriddenFT,
executableElementName,
enclosingType);
if (overridingFT == null || overriddenFT == null) {
return false;
}
Type overridingFTReturnType = overridingFT.getReturnType();
Type overriddenFTReturnType = overriddenFT.getReturnType();
Type[] overridingNormalPT = overridingFT.getNormalParameterTypes();
Type[] overriddenNormalPT = overriddenFT.getNormalParameterTypes();
Type[] overridingPositionalPT = overridingFT.getOptionalParameterTypes();
Type[] overriddenPositionalPT = overriddenFT.getOptionalParameterTypes();
Map<String, Type> overridingNamedPT = overridingFT.getNamedParameterTypes();
Map<String, Type> overriddenNamedPT = overriddenFT.getNamedParameterTypes();
// CTEC.INVALID_OVERRIDE_REQUIRED, CTEC.INVALID_OVERRIDE_POSITIONAL and CTEC.INVALID_OVERRIDE_NAMED
if (overridingNormalPT.length > overriddenNormalPT.length) {
errorReporter.reportErrorForNode(
StaticWarningCode.INVALID_OVERRIDE_REQUIRED,
errorNameTarget,
overriddenNormalPT.length,
overriddenExecutable.getEnclosingElement().getDisplayName());
return true;
}
if (overridingNormalPT.length + overridingPositionalPT.length < overriddenPositionalPT.length
+ overriddenNormalPT.length) {
errorReporter.reportErrorForNode(
StaticWarningCode.INVALID_OVERRIDE_POSITIONAL,
errorNameTarget,
overriddenPositionalPT.length + overriddenNormalPT.length,
overriddenExecutable.getEnclosingElement().getDisplayName());
return true;
}
// For each named parameter in the overridden method, verify that there is the same name in
// the overriding method, and in the same order.
Set<String> overridingParameterNameSet = overridingNamedPT.keySet();
Iterator<String> overriddenParameterNameIterator = overriddenNamedPT.keySet().iterator();
while (overriddenParameterNameIterator.hasNext()) {
String overriddenParamName = overriddenParameterNameIterator.next();
if (!overridingParameterNameSet.contains(overriddenParamName)) {
// The overridden method expected the overriding method to have overridingParamName,
// but it does not.
errorReporter.reportErrorForNode(
StaticWarningCode.INVALID_OVERRIDE_NAMED,
errorNameTarget,
overriddenParamName,
overriddenExecutable.getEnclosingElement().getDisplayName());
return true;
}
}
// SWC.INVALID_METHOD_OVERRIDE_*
// The following (comparing the function types with isSubtypeOf):
// !overridingFT.isSubtypeOf(overriddenFT)
// is equivalent to the following checks, we break it is split up for the purposes of
// providing better error messages.
// SWC.INVALID_METHOD_OVERRIDE_RETURN_TYPE
if (!overriddenFTReturnType.equals(VoidTypeImpl.getInstance())
&& !overridingFTReturnType.isAssignableTo(overriddenFTReturnType)) {
errorReporter.reportTypeErrorForNode(
!isGetter ? StaticWarningCode.INVALID_METHOD_OVERRIDE_RETURN_TYPE
: StaticWarningCode.INVALID_GETTER_OVERRIDE_RETURN_TYPE,
errorNameTarget,
overridingFTReturnType,
overriddenFTReturnType,
overriddenExecutable.getEnclosingElement().getDisplayName());
return true;
}
// SWC.INVALID_METHOD_OVERRIDE_NORMAL_PARAM_TYPE
if (parameterLocations == null) {
return false;
}
int parameterIndex = 0;
for (int i = 0; i < overridingNormalPT.length; i++) {
if (!overridingNormalPT[i].isAssignableTo(overriddenNormalPT[i])) {
errorReporter.reportTypeErrorForNode(
!isSetter ? StaticWarningCode.INVALID_METHOD_OVERRIDE_NORMAL_PARAM_TYPE
: StaticWarningCode.INVALID_SETTER_OVERRIDE_NORMAL_PARAM_TYPE,
parameterLocations[parameterIndex],
overridingNormalPT[i],
overriddenNormalPT[i],
overriddenExecutable.getEnclosingElement().getDisplayName());
return true;
}
parameterIndex++;
}
// SWC.INVALID_METHOD_OVERRIDE_OPTIONAL_PARAM_TYPE
for (int i = 0; i < overriddenPositionalPT.length; i++) {
if (!overridingPositionalPT[i].isAssignableTo(overriddenPositionalPT[i])) {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.INVALID_METHOD_OVERRIDE_OPTIONAL_PARAM_TYPE,
parameterLocations[parameterIndex],
overridingPositionalPT[i],
overriddenPositionalPT[i],
overriddenExecutable.getEnclosingElement().getDisplayName());
return true;
}
parameterIndex++;
}
// SWC.INVALID_METHOD_OVERRIDE_NAMED_PARAM_TYPE & SWC.INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES
Iterator<Entry<String, Type>> overriddenNamedPTIterator = overriddenNamedPT.entrySet().iterator();
while (overriddenNamedPTIterator.hasNext()) {
Entry<String, Type> overriddenNamedPTEntry = overriddenNamedPTIterator.next();
Type overridingType = overridingNamedPT.get(overriddenNamedPTEntry.getKey());
if (overridingType == null) {
// Error, this is never reached- INVALID_OVERRIDE_NAMED would have been created above if
// this could be reached.
continue;
}
if (!overriddenNamedPTEntry.getValue().isAssignableTo(overridingType)) {
// lookup the parameter for the error to select
ParameterElement parameterToSelect = null;
AstNode parameterLocationToSelect = null;
for (int i = 0; i < parameters.length; i++) {
ParameterElement parameter = parameters[i];
if (parameter.getParameterKind() == ParameterKind.NAMED
&& overriddenNamedPTEntry.getKey().equals(parameter.getName())) {
parameterToSelect = parameter;
parameterLocationToSelect = parameterLocations[i];
break;
}
}
if (parameterToSelect != null) {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.INVALID_METHOD_OVERRIDE_NAMED_PARAM_TYPE,
parameterLocationToSelect,
overridingType,
overriddenNamedPTEntry.getValue(),
overriddenExecutable.getEnclosingElement().getDisplayName());
return true;
}
}
}
// SWC.INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES
//
// Create three arrays: an array of the optional parameter ASTs (FormalParameters), an array of
// the optional parameters elements from our method, and finally an array of the optional
// parameter elements from the method we are overriding.
//
boolean foundError = false;
ArrayList<AstNode> formalParameters = new ArrayList<AstNode>();
ArrayList<ParameterElementImpl> parameterElts = new ArrayList<ParameterElementImpl>();
ArrayList<ParameterElementImpl> overriddenParameterElts = new ArrayList<ParameterElementImpl>();
ParameterElement[] overriddenPEs = overriddenExecutable.getParameters();
for (int i = 0; i < parameters.length; i++) {
ParameterElement parameter = parameters[i];
if (parameter.getParameterKind().isOptional()) {
formalParameters.add(parameterLocations[i]);
parameterElts.add((ParameterElementImpl) parameter);
}
}
for (ParameterElement parameterElt : overriddenPEs) {
if (parameterElt.getParameterKind().isOptional()) {
if (parameterElt instanceof ParameterElementImpl) {
overriddenParameterElts.add((ParameterElementImpl) parameterElt);
}
}
}
//
// Next compare the list of optional parameter elements to the list of overridden optional
// parameter elements.
//
if (parameterElts.size() > 0) {
if (parameterElts.get(0).getParameterKind() == ParameterKind.NAMED) {
// Named parameters, consider the names when matching the parameterElts to the overriddenParameterElts
for (int i = 0; i < parameterElts.size(); i++) {
ParameterElementImpl parameterElt = parameterElts.get(i);
EvaluationResultImpl result = parameterElt.getEvaluationResult();
// TODO (jwren) Ignore Object types, see Dart bug 11287
if (isUserDefinedObject(result)) {
continue;
}
String parameterName = parameterElt.getName();
for (int j = 0; j < overriddenParameterElts.size(); j++) {
ParameterElementImpl overriddenParameterElt = overriddenParameterElts.get(j);
String overriddenParameterName = overriddenParameterElt.getName();
if (parameterName != null && parameterName.equals(overriddenParameterName)) {
EvaluationResultImpl overriddenResult = overriddenParameterElt.getEvaluationResult();
if (isUserDefinedObject(overriddenResult)) {
break;
}
if (!result.equalValues(typeProvider, overriddenResult)) {
errorReporter.reportErrorForNode(
StaticWarningCode.INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES_NAMED,
formalParameters.get(i),
overriddenExecutable.getEnclosingElement().getDisplayName(),
overriddenExecutable.getDisplayName(),
parameterName);
foundError = true;
}
}
}
}
} else {
// Positional parameters, consider the positions when matching the parameterElts to the overriddenParameterElts
for (int i = 0; i < parameterElts.size() && i < overriddenParameterElts.size(); i++) {
ParameterElementImpl parameterElt = parameterElts.get(i);
EvaluationResultImpl result = parameterElt.getEvaluationResult();
// TODO (jwren) Ignore Object types, see Dart bug 11287
if (isUserDefinedObject(result)) {
continue;
}
ParameterElementImpl overriddenParameterElt = overriddenParameterElts.get(i);
EvaluationResultImpl overriddenResult = overriddenParameterElt.getEvaluationResult();
if (isUserDefinedObject(overriddenResult)) {
continue;
}
if (!result.equalValues(typeProvider, overriddenResult)) {
errorReporter.reportErrorForNode(
StaticWarningCode.INVALID_OVERRIDE_DIFFERENT_DEFAULT_VALUES_POSITIONAL,
formalParameters.get(i),
overriddenExecutable.getEnclosingElement().getDisplayName(),
overriddenExecutable.getDisplayName());
foundError = true;
}
}
}
}
return foundError;
}
/**
* This checks the passed executable element against override-error codes. This method computes
* the passed executableElement is overriding and calls
* {@link #checkForAllInvalidOverrideErrorCodes(ExecutableElement, ExecutableElement, ParameterElement[], AstNode[], SimpleIdentifier)}
* when the {@link InheritanceManager} returns a {@link MultiplyInheritedExecutableElement}, this
* method loops through the array in the {@link MultiplyInheritedExecutableElement}.
*
* @param executableElement a non-null {@link ExecutableElement} to evaluate
* @param parameters the parameters of the executable element
* @param errorNameTarget the node to report problems on
* @return {@code true} if and only if an error code is generated on the passed node
*/
private boolean checkForAllInvalidOverrideErrorCodesForExecutable(
ExecutableElement executableElement, ParameterElement[] parameters,
AstNode[] parameterLocations, SimpleIdentifier errorNameTarget) {
//
// Compute the overridden executable from the InheritanceManager
//
ArrayList<ExecutableElement> overriddenExecutables = inheritanceManager.lookupOverrides(
enclosingClass,
executableElement.getName());
if (overriddenExecutables.isEmpty()) {
// Nothing is overridden, so we just have to check if the new name collides
// with a static defined in the superclass.
// TODO(paulberry): currently we don't do this check if the new element
// overrides a method in an interface (see issue 18947).
return checkForInstanceMethodNameCollidesWithSuperclassStatic(
executableElement,
errorNameTarget);
}
for (ExecutableElement overriddenElement : overriddenExecutables) {
if (checkForAllInvalidOverrideErrorCodes(
executableElement,
overriddenElement,
parameters,
parameterLocations,
errorNameTarget)) {
return true;
}
}
return false;
}
/**
* This checks the passed field declaration against override-error codes.
*
* @param node the {@link MethodDeclaration} to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see #checkForAllInvalidOverrideErrorCodes(ExecutableElement)
*/
private boolean checkForAllInvalidOverrideErrorCodesForField(FieldDeclaration node) {
if (enclosingClass == null || node.isStatic()) {
return false;
}
boolean hasProblems = false;
VariableDeclarationList fields = node.getFields();
for (VariableDeclaration field : fields.getVariables()) {
FieldElement element = (FieldElement) field.getElement();
if (element == null) {
continue;
}
PropertyAccessorElement getter = element.getGetter();
PropertyAccessorElement setter = element.getSetter();
SimpleIdentifier fieldName = field.getName();
if (getter != null) {
hasProblems |= checkForAllInvalidOverrideErrorCodesForExecutable(
getter,
ParameterElementImpl.EMPTY_ARRAY,
AstNode.EMPTY_ARRAY,
fieldName);
}
if (setter != null) {
hasProblems |= checkForAllInvalidOverrideErrorCodesForExecutable(
setter,
setter.getParameters(),
new AstNode[] {fieldName},
fieldName);
}
}
return hasProblems;
}
/**
* This checks the passed method declaration against override-error codes.
*
* @param node the {@link MethodDeclaration} to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see #checkForAllInvalidOverrideErrorCodes(ExecutableElement)
*/
private boolean checkForAllInvalidOverrideErrorCodesForMethod(MethodDeclaration node) {
if (enclosingClass == null || node.isStatic() || node.getBody() instanceof NativeFunctionBody) {
return false;
}
ExecutableElement executableElement = node.getElement();
if (executableElement == null) {
return false;
}
SimpleIdentifier methodName = node.getName();
if (methodName.isSynthetic()) {
return false;
}
FormalParameterList formalParameterList = node.getParameters();
NodeList<FormalParameter> parameterList = formalParameterList != null
? formalParameterList.getParameters() : null;
AstNode[] parameters = parameterList != null
? parameterList.toArray(new AstNode[parameterList.size()]) : null;
return checkForAllInvalidOverrideErrorCodesForExecutable(
executableElement,
executableElement.getParameters(),
parameters,
methodName);
}
/**
* This verifies that all classes of the passed 'with' clause are valid.
*
* @param node the 'with' clause to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#MIXIN_DECLARES_CONSTRUCTOR
* @see CompileTimeErrorCode#MIXIN_INHERITS_FROM_NOT_OBJECT
* @see CompileTimeErrorCode#MIXIN_REFERENCES_SUPER
*/
private boolean checkForAllMixinErrorCodes(WithClause withClause) {
if (withClause == null) {
return false;
}
boolean problemReported = false;
for (TypeName mixinName : withClause.getMixinTypes()) {
Type mixinType = mixinName.getType();
if (!(mixinType instanceof InterfaceType)) {
continue;
}
if (checkForExtendsOrImplementsDisallowedClass(
mixinName,
CompileTimeErrorCode.MIXIN_OF_DISALLOWED_CLASS)) {
problemReported = true;
} else {
ClassElement mixinElement = ((InterfaceType) mixinType).getElement();
problemReported |= checkForExtendsOrImplementsDeferredClass(
mixinName,
CompileTimeErrorCode.MIXIN_DEFERRED_CLASS);
problemReported |= checkForMixinDeclaresConstructor(mixinName, mixinElement);
problemReported |= checkForMixinInheritsNotFromObject(mixinName, mixinElement);
problemReported |= checkForMixinReferencesSuper(mixinName, mixinElement);
}
}
return problemReported;
}
/**
* This checks error related to the redirected constructors.
*
* @param node the constructor declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#REDIRECT_TO_INVALID_RETURN_TYPE
* @see StaticWarningCode#REDIRECT_TO_INVALID_FUNCTION_TYPE
* @see StaticWarningCode#REDIRECT_TO_MISSING_CONSTRUCTOR
*/
private boolean checkForAllRedirectConstructorErrorCodes(ConstructorDeclaration node) {
//
// Prepare redirected constructor node
//
ConstructorName redirectedConstructor = node.getRedirectedConstructor();
if (redirectedConstructor == null) {
return false;
}
//
// Prepare redirected constructor type
//
ConstructorElement redirectedElement = redirectedConstructor.getStaticElement();
if (redirectedElement == null) {
//
// If the element is null, we check for the REDIRECT_TO_MISSING_CONSTRUCTOR case
//
TypeName constructorTypeName = redirectedConstructor.getType();
Type redirectedType = constructorTypeName.getType();
if (redirectedType != null && redirectedType.getElement() != null
&& !redirectedType.isDynamic()) {
//
// Prepare the constructor name
//
String constructorStrName = constructorTypeName.getName().getName();
if (redirectedConstructor.getName() != null) {
constructorStrName += '.' + redirectedConstructor.getName().getName();
}
ErrorCode errorCode = node.getConstKeyword() != null
? CompileTimeErrorCode.REDIRECT_TO_MISSING_CONSTRUCTOR
: StaticWarningCode.REDIRECT_TO_MISSING_CONSTRUCTOR;
errorReporter.reportErrorForNode(
errorCode,
redirectedConstructor,
constructorStrName,
redirectedType.getDisplayName());
return true;
}
return false;
}
FunctionType redirectedType = redirectedElement.getType();
Type redirectedReturnType = redirectedType.getReturnType();
//
// Report specific problem when return type is incompatible
//
FunctionType constructorType = node.getElement().getType();
Type constructorReturnType = constructorType.getReturnType();
if (!redirectedReturnType.isAssignableTo(constructorReturnType)) {
errorReporter.reportErrorForNode(
StaticWarningCode.REDIRECT_TO_INVALID_RETURN_TYPE,
redirectedConstructor,
redirectedReturnType,
constructorReturnType);
return true;
}
//
// Check parameters
//
if (!redirectedType.isSubtypeOf(constructorType)) {
errorReporter.reportErrorForNode(
StaticWarningCode.REDIRECT_TO_INVALID_FUNCTION_TYPE,
redirectedConstructor,
redirectedType,
constructorType);
return true;
}
return false;
}
/**
* This checks that the return statement of the form <i>return e;</i> is not in a generative
* constructor.
* <p>
* This checks that return statements without expressions are not in a generative constructor and
* the return type is not assignable to {@code null}; that is, we don't have {@code return;} if
* the enclosing method has a return type.
* <p>
* This checks that the return type matches the type of the declared return type in the enclosing
* method or function.
*
* @param node the return statement to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#RETURN_IN_GENERATIVE_CONSTRUCTOR
* @see StaticWarningCode#RETURN_WITHOUT_VALUE
* @see StaticTypeWarningCode#RETURN_OF_INVALID_TYPE
*/
private boolean checkForAllReturnStatementErrorCodes(ReturnStatement node) {
FunctionType functionType = enclosingFunction == null ? null : enclosingFunction.getType();
Type expectedReturnType = functionType == null ? DynamicTypeImpl.getInstance()
: functionType.getReturnType();
Expression returnExpression = node.getExpression();
// RETURN_IN_GENERATIVE_CONSTRUCTOR
boolean isGenerativeConstructor = enclosingFunction instanceof ConstructorElement
&& !((ConstructorElement) enclosingFunction).isFactory();
if (isGenerativeConstructor) {
if (returnExpression == null) {
return false;
}
errorReporter.reportErrorForNode(
CompileTimeErrorCode.RETURN_IN_GENERATIVE_CONSTRUCTOR,
returnExpression);
return true;
}
// RETURN_WITHOUT_VALUE
if (returnExpression == null) {
if (VoidTypeImpl.getInstance().isAssignableTo(expectedReturnType)) {
return false;
}
hasReturnWithoutValue = true;
errorReporter.reportErrorForNode(StaticWarningCode.RETURN_WITHOUT_VALUE, node);
return true;
} else if (inGenerator) {
// RETURN_IN_GENERATOR
errorReporter.reportErrorForNode(CompileTimeErrorCode.RETURN_IN_GENERATOR, node);
}
// RETURN_OF_INVALID_TYPE
return checkForReturnOfInvalidType(returnExpression, expectedReturnType);
}
/**
* This verifies that the export namespace of the passed export directive does not export any name
* already exported by other export directive.
*
* @param node the export directive node to report problem on
* @param exportElement the {@link ExportElement} retrieved from the node, if the element in the
* node was {@code null}, then this method is not called
* @param exportedLibrary the library element containing the exported element
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#AMBIGUOUS_EXPORT
*/
private boolean checkForAmbiguousExport(ExportDirective node, ExportElement exportElement,
LibraryElement exportedLibrary) {
if (exportedLibrary == null) {
return false;
}
// check exported names
Namespace namespace = new NamespaceBuilder().createExportNamespaceForDirective(exportElement);
Map<String, Element> definedNames = namespace.getDefinedNames();
for (Entry<String, Element> definedEntry : definedNames.entrySet()) {
String name = definedEntry.getKey();
Element element = definedEntry.getValue();
Element prevElement = exportedElements.get(name);
if (element != null && prevElement != null && !prevElement.equals(element)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.AMBIGUOUS_EXPORT,
node,
name,
prevElement.getLibrary().getDefiningCompilationUnit().getDisplayName(),
element.getLibrary().getDefiningCompilationUnit().getDisplayName());
return true;
} else {
exportedElements.put(name, element);
}
}
return false;
}
/**
* This verifies that the passed expression can be assigned to its corresponding parameters.
* <p>
* This method corresponds to BestPracticesVerifier.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
* {@code null}
* @param actualPropagatedType the expected propagated type of the parameter, may be {@code null}
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#ARGUMENT_TYPE_NOT_ASSIGNABLE
* @see CompileTimeErrorCode#LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
* @see StaticWarningCode#LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
* @see CompileTimeErrorCode#MAP_KEY_TYPE_NOT_ASSIGNABLE
* @see CompileTimeErrorCode#MAP_VALUE_TYPE_NOT_ASSIGNABLE
* @see StaticWarningCode#MAP_KEY_TYPE_NOT_ASSIGNABLE
* @see StaticWarningCode#MAP_VALUE_TYPE_NOT_ASSIGNABLE
*/
private boolean checkForArgumentTypeNotAssignable(Expression expression, Type expectedStaticType,
Type actualStaticType, ErrorCode errorCode) {
//
// Warning case: test static type information
//
if (actualStaticType != null && expectedStaticType != null) {
if (!actualStaticType.isAssignableTo(expectedStaticType)) {
errorReporter.reportTypeErrorForNode(
errorCode,
expression,
actualStaticType,
expectedStaticType);
return true;
}
}
return false;
}
/**
* This verifies that the passed argument can be assigned to its corresponding parameter.
* <p>
* This method corresponds to BestPracticesVerifier.checkForArgumentTypeNotAssignableForArgument.
*
* @param argument the argument to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#ARGUMENT_TYPE_NOT_ASSIGNABLE
*/
private boolean checkForArgumentTypeNotAssignableForArgument(Expression argument) {
if (argument == null) {
return false;
}
ParameterElement staticParameterElement = argument.getStaticParameterElement();
Type staticParameterType = staticParameterElement == null ? null
: staticParameterElement.getType();
return checkForArgumentTypeNotAssignableWithExpectedTypes(
argument,
staticParameterType,
StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE);
}
/**
* This verifies that the passed expression can be assigned to its corresponding parameters.
* <p>
* This method corresponds to
* BestPracticesVerifier.checkForArgumentTypeNotAssignableWithExpectedTypes.
*
* @param expression the expression to evaluate
* @param expectedStaticType the expected static type
* @param expectedPropagatedType the expected propagated type, may be {@code null}
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#ARGUMENT_TYPE_NOT_ASSIGNABLE
* @see CompileTimeErrorCode#LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
* @see StaticWarningCode#LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
* @see CompileTimeErrorCode#MAP_KEY_TYPE_NOT_ASSIGNABLE
* @see CompileTimeErrorCode#MAP_VALUE_TYPE_NOT_ASSIGNABLE
* @see StaticWarningCode#MAP_KEY_TYPE_NOT_ASSIGNABLE
* @see StaticWarningCode#MAP_VALUE_TYPE_NOT_ASSIGNABLE
*/
private boolean checkForArgumentTypeNotAssignableWithExpectedTypes(Expression expression,
Type expectedStaticType, ErrorCode errorCode) {
return checkForArgumentTypeNotAssignable(
expression,
expectedStaticType,
getStaticType(expression),
errorCode);
}
/**
* This verifies that the passed arguments can be assigned to their corresponding parameters.
* <p>
* This method corresponds to BestPracticesVerifier.checkForArgumentTypesNotAssignableInList.
*
* @param node the arguments to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#ARGUMENT_TYPE_NOT_ASSIGNABLE
*/
private boolean checkForArgumentTypesNotAssignableInList(ArgumentList argumentList) {
if (argumentList == null) {
return false;
}
boolean problemReported = false;
for (Expression argument : argumentList.getArguments()) {
problemReported |= checkForArgumentTypeNotAssignableForArgument(argument);
}
return problemReported;
}
/**
* Check that the static type of the given expression is assignable to the given type. If it
* isn't, report an error with the given error code.
*
* @param expression the expression being tested
* @param type the type that the expression must be assignable to
* @param errorCode the error code to be reported
* @param arguments the arguments to pass in when creating the error
* @return {@code true} if an error was reported
*/
private boolean checkForAssignability(Expression expression, InterfaceType type,
ErrorCode errorCode, Object... arguments) {
if (expression == null) {
return false;
}
Type expressionType = expression.getStaticType();
if (expressionType == null) {
return false;
}
if (expressionType.isAssignableTo(type)) {
return false;
}
errorReporter.reportErrorForNode(errorCode, expression, arguments);
return true;
}
/**
* This verifies that the passed expression is not final.
*
* @param node the expression to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#ASSIGNMENT_TO_CONST
* @see StaticWarningCode#ASSIGNMENT_TO_FINAL
* @see StaticWarningCode#ASSIGNMENT_TO_METHOD
*/
private boolean checkForAssignmentToFinal(Expression expression) {
// prepare element
Element element = null;
AstNode highlightedNode = expression;
if (expression instanceof Identifier) {
element = ((Identifier) expression).getStaticElement();
if (expression instanceof PrefixedIdentifier) {
highlightedNode = ((PrefixedIdentifier) expression).getIdentifier();
}
} else if (expression instanceof PropertyAccess) {
PropertyAccess propertyAccess = (PropertyAccess) expression;
element = propertyAccess.getPropertyName().getStaticElement();
highlightedNode = propertyAccess.getPropertyName();
}
// check if element is assignable
if (element instanceof PropertyAccessorElement) {
PropertyAccessorElement accessor = (PropertyAccessorElement) element;
element = accessor.getVariable();
}
if (element instanceof VariableElement) {
VariableElement variable = (VariableElement) element;
if (variable.isConst()) {
errorReporter.reportErrorForNode(StaticWarningCode.ASSIGNMENT_TO_CONST, expression);
return true;
}
if (variable.isFinal()) {
if (variable instanceof FieldElementImpl
&& ((FieldElementImpl) variable).getSetter() == null && variable.isSynthetic()) {
errorReporter.reportErrorForNode(
StaticWarningCode.ASSIGNMENT_TO_FINAL_NO_SETTER,
highlightedNode,
variable.getName(),
variable.getEnclosingElement().getDisplayName());
return true;
}
errorReporter.reportErrorForNode(
StaticWarningCode.ASSIGNMENT_TO_FINAL,
highlightedNode,
variable.getName());
return true;
}
return false;
}
if (element instanceof FunctionElement) {
errorReporter.reportErrorForNode(StaticWarningCode.ASSIGNMENT_TO_FUNCTION, expression);
return true;
}
if (element instanceof MethodElement) {
errorReporter.reportErrorForNode(StaticWarningCode.ASSIGNMENT_TO_METHOD, expression);
return true;
}
return false;
}
/**
* This verifies that the passed identifier is not a keyword, and generates the passed error code
* on the identifier if it is a keyword.
*
* @param identifier the identifier to check to ensure that it is not a keyword
* @param errorCode if the passed identifier is a keyword then this error code is created on the
* identifier, the error code will be one of
* {@link CompileTimeErrorCode#BUILT_IN_IDENTIFIER_AS_TYPE_NAME},
* {@link CompileTimeErrorCode#BUILT_IN_IDENTIFIER_AS_TYPE_PARAMETER_NAME} or
* {@link CompileTimeErrorCode#BUILT_IN_IDENTIFIER_AS_TYPEDEF_NAME}
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#BUILT_IN_IDENTIFIER_AS_TYPE_NAME
* @see CompileTimeErrorCode#BUILT_IN_IDENTIFIER_AS_TYPE_PARAMETER_NAME
* @see CompileTimeErrorCode#BUILT_IN_IDENTIFIER_AS_TYPEDEF_NAME
*/
private boolean checkForBuiltInIdentifierAsName(SimpleIdentifier identifier, ErrorCode errorCode) {
Token token = identifier.getToken();
if (token.getType() == TokenType.KEYWORD) {
errorReporter.reportErrorForNode(errorCode, identifier, identifier.getName());
return true;
}
return false;
}
/**
* This verifies that the given switch case is terminated with 'break', 'continue', 'return' or
* 'throw'.
*
* @param node the switch case to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#CASE_BLOCK_NOT_TERMINATED
*/
private boolean checkForCaseBlockNotTerminated(SwitchCase node) {
NodeList<Statement> statements = node.getStatements();
if (statements.isEmpty()) {
// fall-through without statements at all
AstNode parent = node.getParent();
if (parent instanceof SwitchStatement) {
SwitchStatement switchStatement = (SwitchStatement) parent;
NodeList<SwitchMember> members = switchStatement.getMembers();
int index = members.indexOf(node);
if (index != -1 && index < members.size() - 1) {
return false;
}
}
// no other switch member after this one
} else {
Statement statement = statements.get(statements.size() - 1);
// terminated with statement
if (statement instanceof BreakStatement || statement instanceof ContinueStatement
|| statement instanceof ReturnStatement) {
return false;
}
// terminated with 'throw' expression
if (statement instanceof ExpressionStatement) {
Expression expression = ((ExpressionStatement) statement).getExpression();
if (expression instanceof ThrowExpression) {
return false;
}
}
}
// report error
errorReporter.reportErrorForToken(
StaticWarningCode.CASE_BLOCK_NOT_TERMINATED,
node.getKeyword());
return true;
}
/**
* This verifies that the switch cases in the given switch statement is terminated with 'break',
* 'continue', 'return' or 'throw'.
*
* @param node the switch statement containing the cases to be checked
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#CASE_BLOCK_NOT_TERMINATED
*/
private boolean checkForCaseBlocksNotTerminated(SwitchStatement node) {
boolean foundError = false;
NodeList<SwitchMember> members = node.getMembers();
int lastMember = members.size() - 1;
for (int i = 0; i < lastMember; i++) {
SwitchMember member = members.get(i);
if (member instanceof SwitchCase) {
foundError |= checkForCaseBlockNotTerminated((SwitchCase) member);
}
}
return foundError;
}
/**
* This verifies that the passed method declaration is abstract only if the enclosing class is
* also abstract.
*
* @param node the method declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#CONCRETE_CLASS_WITH_ABSTRACT_MEMBER
*/
private boolean checkForConcreteClassWithAbstractMember(MethodDeclaration node) {
if (node.isAbstract() && enclosingClass != null && !enclosingClass.isAbstract()) {
SimpleIdentifier nameNode = node.getName();
String memberName = nameNode.getName();
ExecutableElement overriddenMember;
if (node.isGetter()) {
overriddenMember = enclosingClass.lookUpInheritedConcreteGetter(memberName, currentLibrary);
} else if (node.isSetter()) {
overriddenMember = enclosingClass.lookUpInheritedConcreteSetter(memberName, currentLibrary);
} else {
overriddenMember = enclosingClass.lookUpInheritedConcreteMethod(memberName, currentLibrary);
}
if (overriddenMember == null) {
errorReporter.reportErrorForNode(
StaticWarningCode.CONCRETE_CLASS_WITH_ABSTRACT_MEMBER,
nameNode,
memberName,
enclosingClass.getDisplayName());
return true;
}
}
return false;
}
/**
* This verifies all possible conflicts of the constructor name with other constructors and
* members of the same class.
*
* @param node the constructor declaration to evaluate
* @param constructorElement the constructor element
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#DUPLICATE_CONSTRUCTOR_DEFAULT
* @see CompileTimeErrorCode#DUPLICATE_CONSTRUCTOR_NAME
* @see CompileTimeErrorCode#CONFLICTING_CONSTRUCTOR_NAME_AND_FIELD
* @see CompileTimeErrorCode#CONFLICTING_CONSTRUCTOR_NAME_AND_METHOD
*/
private boolean checkForConflictingConstructorNameAndMember(ConstructorDeclaration node,
ConstructorElement constructorElement) {
SimpleIdentifier constructorName = node.getName();
String name = constructorElement.getName();
ClassElement classElement = constructorElement.getEnclosingElement();
// constructors
ConstructorElement[] constructors = classElement.getConstructors();
for (ConstructorElement otherConstructor : constructors) {
if (otherConstructor == constructorElement) {
continue;
}
if (ObjectUtilities.equals(name, otherConstructor.getName())) {
if (name == null || name.length() == 0) {
errorReporter.reportErrorForNode(CompileTimeErrorCode.DUPLICATE_CONSTRUCTOR_DEFAULT, node);
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.DUPLICATE_CONSTRUCTOR_NAME,
node,
name);
}
return true;
}
}
// conflict with class member
if (constructorName != null && constructorElement != null && !constructorName.isSynthetic()) {
// fields
FieldElement field = classElement.getField(name);
if (field != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_NAME_AND_FIELD,
node,
name);
return true;
}
// methods
MethodElement method = classElement.getMethod(name);
if (method != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONFLICTING_CONSTRUCTOR_NAME_AND_METHOD,
node,
name);
return true;
}
}
return false;
}
/**
* This verifies that the {@link #enclosingClass} does not have a method and getter pair with the
* same name on, via inheritance.
*
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONFLICTING_GETTER_AND_METHOD
* @see CompileTimeErrorCode#CONFLICTING_METHOD_AND_GETTER
*/
private boolean checkForConflictingGetterAndMethod() {
if (enclosingClass == null) {
return false;
}
boolean hasProblem = false;
// method declared in the enclosing class vs. inherited getter
for (MethodElement method : enclosingClass.getMethods()) {
String name = method.getName();
// find inherited property accessor (and can be only getter)
ExecutableElement inherited = inheritanceManager.lookupInheritance(enclosingClass, name);
if (!(inherited instanceof PropertyAccessorElement)) {
continue;
}
// report problem
hasProblem = true;
errorReporter.reportErrorForOffset(
CompileTimeErrorCode.CONFLICTING_GETTER_AND_METHOD,
method.getNameOffset(),
name.length(),
enclosingClass.getDisplayName(),
inherited.getEnclosingElement().getDisplayName(),
name);
}
// getter declared in the enclosing class vs. inherited method
for (PropertyAccessorElement accessor : enclosingClass.getAccessors()) {
if (!accessor.isGetter()) {
continue;
}
String name = accessor.getName();
// find inherited method
ExecutableElement inherited = inheritanceManager.lookupInheritance(enclosingClass, name);
if (!(inherited instanceof MethodElement)) {
continue;
}
// report problem
hasProblem = true;
errorReporter.reportErrorForOffset(
CompileTimeErrorCode.CONFLICTING_METHOD_AND_GETTER,
accessor.getNameOffset(),
name.length(),
enclosingClass.getDisplayName(),
inherited.getEnclosingElement().getDisplayName(),
name);
}
// done
return hasProblem;
}
/**
* This verifies that the superclass of the {@link #enclosingClass} does not declare accessible
* static members with the same name as the instance getters/setters declared in
* {@link #enclosingClass}.
*
* @param node the method declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#CONFLICTING_INSTANCE_GETTER_AND_SUPERCLASS_MEMBER
* @see StaticWarningCode#CONFLICTING_INSTANCE_SETTER_AND_SUPERCLASS_MEMBER
*/
private boolean checkForConflictingInstanceGetterAndSuperclassMember() {
if (enclosingClass == null) {
return false;
}
InterfaceType enclosingType = enclosingClass.getType();
// check every accessor
boolean hasProblem = false;
for (PropertyAccessorElement accessor : enclosingClass.getAccessors()) {
// we analyze instance accessors here
if (accessor.isStatic()) {
continue;
}
// prepare accessor properties
String name = accessor.getDisplayName();
boolean getter = accessor.isGetter();
// if non-final variable, ignore setter - we alreay reported problem for getter
if (accessor.isSetter() && accessor.isSynthetic()) {
continue;
}
// try to find super element
ExecutableElement superElement;
superElement = enclosingType.lookUpGetterInSuperclass(name, currentLibrary);
if (superElement == null) {
superElement = enclosingType.lookUpSetterInSuperclass(name, currentLibrary);
}
if (superElement == null) {
superElement = enclosingType.lookUpMethodInSuperclass(name, currentLibrary);
}
if (superElement == null) {
continue;
}
// OK, not static
if (!superElement.isStatic()) {
continue;
}
// prepare "super" type to report its name
ClassElement superElementClass = (ClassElement) superElement.getEnclosingElement();
InterfaceType superElementType = superElementClass.getType();
// report problem
hasProblem = true;
if (getter) {
errorReporter.reportErrorForElement(
StaticWarningCode.CONFLICTING_INSTANCE_GETTER_AND_SUPERCLASS_MEMBER,
accessor,
superElementType.getDisplayName());
} else {
errorReporter.reportErrorForElement(
StaticWarningCode.CONFLICTING_INSTANCE_SETTER_AND_SUPERCLASS_MEMBER,
accessor,
superElementType.getDisplayName());
}
}
// done
return hasProblem;
}
/**
* This verifies that the enclosing class does not have a setter with the same name as the passed
* instance method declaration.
* <p>
* TODO(jwren) add other "conflicting" error codes into algorithm/ data structure
*
* @param node the method declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#CONFLICTING_INSTANCE_METHOD_SETTER
*/
private boolean checkForConflictingInstanceMethodSetter(ClassDeclaration node) {
// Reference all of the class members in this class.
NodeList<ClassMember> classMembers = node.getMembers();
if (classMembers.isEmpty()) {
return false;
}
// Create a HashMap to track conflicting members, and then loop through members in the class to
// construct the HashMap, at the same time, look for violations. Don't add members if they are
// part of a conflict, this prevents multiple warnings for one issue.
boolean foundError = false;
HashMap<String, ClassMember> memberHashMap = new HashMap<String, ClassMember>(
classMembers.size());
for (ClassMember classMember : classMembers) {
if (classMember instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) classMember;
if (method.isStatic()) {
continue;
}
// prepare name
SimpleIdentifier name = method.getName();
if (name == null) {
continue;
}
boolean addThisMemberToTheMap = true;
boolean isGetter = method.isGetter();
boolean isSetter = method.isSetter();
boolean isOperator = method.isOperator();
boolean isMethod = !isGetter && !isSetter && !isOperator;
// Do lookups in the enclosing class (and the inherited member) if the member is a method or
// a setter for StaticWarningCode.CONFLICTING_INSTANCE_METHOD_SETTER warning.
if (isMethod) {
String setterName = name.getName() + "=";
Element enclosingElementOfSetter = null;
ClassMember conflictingSetter = memberHashMap.get(setterName);
if (conflictingSetter != null) {
enclosingElementOfSetter = conflictingSetter.getElement().getEnclosingElement();
} else {
ExecutableElement elementFromInheritance = inheritanceManager.lookupInheritance(
enclosingClass,
setterName);
if (elementFromInheritance != null) {
enclosingElementOfSetter = elementFromInheritance.getEnclosingElement();
}
}
if (enclosingElementOfSetter != null) {
// report problem
errorReporter.reportErrorForNode(
StaticWarningCode.CONFLICTING_INSTANCE_METHOD_SETTER,
name,
enclosingClass.getDisplayName(),
name.getName(),
enclosingElementOfSetter.getDisplayName());
foundError |= true;
addThisMemberToTheMap = false;
}
} else if (isSetter) {
String methodName = name.getName();
ClassMember conflictingMethod = memberHashMap.get(methodName);
if (conflictingMethod != null && conflictingMethod instanceof MethodDeclaration
&& !((MethodDeclaration) conflictingMethod).isGetter()) {
// report problem
errorReporter.reportErrorForNode(
StaticWarningCode.CONFLICTING_INSTANCE_METHOD_SETTER2,
name,
enclosingClass.getDisplayName(),
name.getName());
foundError |= true;
addThisMemberToTheMap = false;
}
}
// Finally, add this member into the HashMap.
if (addThisMemberToTheMap) {
if (method.isSetter()) {
memberHashMap.put(name.getName() + "=", method);
} else {
memberHashMap.put(name.getName(), method);
}
}
}
}
return foundError;
}
/**
* This verifies that the enclosing class does not have an instance member with the same name as
* the passed static getter method declaration.
*
* @param node the method declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#CONFLICTING_STATIC_GETTER_AND_INSTANCE_SETTER
*/
private boolean checkForConflictingStaticGetterAndInstanceSetter(MethodDeclaration node) {
if (!node.isStatic()) {
return false;
}
// prepare name
SimpleIdentifier nameNode = node.getName();
if (nameNode == null) {
return false;
}
String name = nameNode.getName();
// prepare enclosing type
if (enclosingClass == null) {
return false;
}
InterfaceType enclosingType = enclosingClass.getType();
// try to find setter
ExecutableElement setter = enclosingType.lookUpSetter(name, currentLibrary);
if (setter == null) {
return false;
}
// OK, also static
if (setter.isStatic()) {
return false;
}
// prepare "setter" type to report its name
ClassElement setterClass = (ClassElement) setter.getEnclosingElement();
InterfaceType setterType = setterClass.getType();
// report problem
errorReporter.reportErrorForNode(
StaticWarningCode.CONFLICTING_STATIC_GETTER_AND_INSTANCE_SETTER,
nameNode,
setterType.getDisplayName());
return true;
}
/**
* This verifies that the enclosing class does not have an instance member with the same name as
* the passed static getter method declaration.
*
* @param node the method declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#CONFLICTING_STATIC_SETTER_AND_INSTANCE_MEMBER
*/
private boolean checkForConflictingStaticSetterAndInstanceMember(MethodDeclaration node) {
if (!node.isStatic()) {
return false;
}
// prepare name
SimpleIdentifier nameNode = node.getName();
if (nameNode == null) {
return false;
}
String name = nameNode.getName();
// prepare enclosing type
if (enclosingClass == null) {
return false;
}
InterfaceType enclosingType = enclosingClass.getType();
// try to find member
ExecutableElement member;
member = enclosingType.lookUpMethod(name, currentLibrary);
if (member == null) {
member = enclosingType.lookUpGetter(name, currentLibrary);
}
if (member == null) {
member = enclosingType.lookUpSetter(name, currentLibrary);
}
if (member == null) {
return false;
}
// OK, also static
if (member.isStatic()) {
return false;
}
// prepare "member" type to report its name
ClassElement memberClass = (ClassElement) member.getEnclosingElement();
InterfaceType memberType = memberClass.getType();
// report problem
errorReporter.reportErrorForNode(
StaticWarningCode.CONFLICTING_STATIC_SETTER_AND_INSTANCE_MEMBER,
nameNode,
memberType.getDisplayName());
return true;
}
/**
* This verifies all conflicts between type variable and enclosing class. TODO(scheglov)
*
* @param node the class declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONFLICTING_TYPE_VARIABLE_AND_CLASS
* @see CompileTimeErrorCode#CONFLICTING_TYPE_VARIABLE_AND_MEMBER
*/
private boolean checkForConflictingTypeVariableErrorCodes(ClassDeclaration node) {
boolean problemReported = false;
for (TypeParameterElement typeParameter : enclosingClass.getTypeParameters()) {
String name = typeParameter.getName();
// name is same as the name of the enclosing class
if (enclosingClass.getName().equals(name)) {
errorReporter.reportErrorForOffset(
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_CLASS,
typeParameter.getNameOffset(),
name.length(),
name);
problemReported = true;
}
// check members
if (enclosingClass.getMethod(name) != null || enclosingClass.getGetter(name) != null
|| enclosingClass.getSetter(name) != null) {
errorReporter.reportErrorForOffset(
CompileTimeErrorCode.CONFLICTING_TYPE_VARIABLE_AND_MEMBER,
typeParameter.getNameOffset(),
name.length(),
name);
problemReported = true;
}
}
return problemReported;
}
/**
* This verifies that if the passed constructor declaration is 'const' then there are no
* invocations of non-'const' super constructors.
*
* @param node the constructor declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_CONSTRUCTOR_WITH_NON_CONST_SUPER
*/
private boolean checkForConstConstructorWithNonConstSuper(ConstructorDeclaration node) {
if (!isEnclosingConstructorConst) {
return false;
}
// OK, const factory, checked elsewhere
if (node.getFactoryKeyword() != null) {
return false;
}
// check for mixins
if (enclosingClass.getMixins().length != 0) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_CONSTRUCTOR_WITH_MIXIN,
node.getReturnType());
return true;
}
// try to find and check super constructor invocation
for (ConstructorInitializer initializer : node.getInitializers()) {
if (initializer instanceof SuperConstructorInvocation) {
SuperConstructorInvocation superInvocation = (SuperConstructorInvocation) initializer;
ConstructorElement element = superInvocation.getStaticElement();
if (element == null || element.isConst()) {
return false;
}
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_CONSTRUCTOR_WITH_NON_CONST_SUPER,
superInvocation,
element.getEnclosingElement().getDisplayName());
return true;
}
}
// no explicit super constructor invocation, check default constructor
InterfaceType supertype = enclosingClass.getSupertype();
if (supertype == null) {
return false;
}
if (supertype.isObject()) {
return false;
}
ConstructorElement unnamedConstructor = supertype.getElement().getUnnamedConstructor();
if (unnamedConstructor == null) {
return false;
}
if (unnamedConstructor.isConst()) {
return false;
}
// default constructor is not 'const', report problem
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_CONSTRUCTOR_WITH_NON_CONST_SUPER,
node.getReturnType(),
supertype.getDisplayName());
return true;
}
/**
* This verifies that if the passed constructor declaration is 'const' then there are no non-final
* instance variable.
*
* @param node the constructor declaration to evaluate
* @param constructorElement the constructor element
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_CONSTRUCTOR_WITH_NON_FINAL_FIELD
*/
private boolean checkForConstConstructorWithNonFinalField(ConstructorDeclaration node,
ConstructorElement constructorElement) {
if (!isEnclosingConstructorConst) {
return false;
}
// check if there is non-final field
ClassElement classElement = constructorElement.getEnclosingElement();
if (!classElement.hasNonFinalField()) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_CONSTRUCTOR_WITH_NON_FINAL_FIELD,
node);
return true;
}
/**
* This verifies that the passed 'const' instance creation expression is not creating a deferred
* type.
*
* @param node the instance creation expression to evaluate
* @param constructorName the constructor name, always non-{@code null}
* @param typeName the name of the type defining the constructor, always non-{@code null}
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_DEFERRED_CLASS
*/
private boolean checkForConstDeferredClass(InstanceCreationExpression node,
ConstructorName constructorName, TypeName typeName) {
if (typeName.isDeferred()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_DEFERRED_CLASS,
constructorName,
typeName.getName().getName());
return true;
}
return false;
}
/**
* This verifies that the passed throw expression is not enclosed in a 'const' constructor
* declaration.
*
* @param node the throw expression expression to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_CONSTRUCTOR_THROWS_EXCEPTION
*/
private boolean checkForConstEvalThrowsException(ThrowExpression node) {
if (isEnclosingConstructorConst) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_CONSTRUCTOR_THROWS_EXCEPTION,
node);
return true;
}
return false;
}
/**
* This verifies that the passed normal formal parameter is not 'const'.
*
* @param node the normal formal parameter to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_FORMAL_PARAMETER
*/
private boolean checkForConstFormalParameter(NormalFormalParameter node) {
if (node.isConst()) {
errorReporter.reportErrorForNode(CompileTimeErrorCode.CONST_FORMAL_PARAMETER, node);
return true;
}
return false;
}
/**
* This verifies that the passed instance creation expression is not being invoked on an abstract
* class.
*
* @param node the instance creation expression to evaluate
* @param typeName the {@link TypeName} of the {@link ConstructorName} from the
* {@link InstanceCreationExpression}, this is the AST node that the error is attached to
* @param type the type being constructed with this {@link InstanceCreationExpression}
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#CONST_WITH_ABSTRACT_CLASS
* @see StaticWarningCode#NEW_WITH_ABSTRACT_CLASS
*/
private boolean checkForConstOrNewWithAbstractClass(InstanceCreationExpression node,
TypeName typeName, InterfaceType type) {
if (type.getElement().isAbstract()) {
ConstructorElement element = node.getStaticElement();
if (element != null && !element.isFactory()) {
if (((KeywordToken) node.getKeyword()).getKeyword() == Keyword.CONST) {
errorReporter.reportErrorForNode(StaticWarningCode.CONST_WITH_ABSTRACT_CLASS, typeName);
} else {
errorReporter.reportErrorForNode(StaticWarningCode.NEW_WITH_ABSTRACT_CLASS, typeName);
}
return true;
}
}
return false;
}
/**
* This verifies that the passed instance creation expression is not being invoked on an enum.
*
* @param node the instance creation expression to verify
* @param typeName the {@link TypeName} of the {@link ConstructorName} from the
* {@link InstanceCreationExpression}, this is the AST node that the error is attached to
* @param type the type being constructed with this {@link InstanceCreationExpression}
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#INSTANTIATE_ENUM
*/
private boolean checkForConstOrNewWithEnum(InstanceCreationExpression node, TypeName typeName,
InterfaceType type) {
if (type.getElement().isEnum()) {
errorReporter.reportErrorForNode(CompileTimeErrorCode.INSTANTIATE_ENUM, typeName);
return true;
}
return false;
}
/**
* This verifies that the passed 'const' instance creation expression is not being invoked on a
* constructor that is not 'const'.
* <p>
* This method assumes that the instance creation was tested to be 'const' before being called.
*
* @param node the instance creation expression to verify
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_WITH_NON_CONST
*/
private boolean checkForConstWithNonConst(InstanceCreationExpression node) {
ConstructorElement constructorElement = node.getStaticElement();
if (constructorElement != null && !constructorElement.isConst()) {
errorReporter.reportErrorForNode(CompileTimeErrorCode.CONST_WITH_NON_CONST, node);
return true;
}
return false;
}
/**
* This verifies that the passed type name does not reference any type parameters.
*
* @param typeName the type name to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_WITH_TYPE_PARAMETERS
*/
private boolean checkForConstWithTypeParameters(TypeName typeName) {
// something wrong with AST
if (typeName == null) {
return false;
}
Identifier name = typeName.getName();
if (name == null) {
return false;
}
// should not be a type parameter
if (name.getStaticElement() instanceof TypeParameterElement) {
errorReporter.reportErrorForNode(CompileTimeErrorCode.CONST_WITH_TYPE_PARAMETERS, name);
}
// check type arguments
TypeArgumentList typeArguments = typeName.getTypeArguments();
if (typeArguments != null) {
boolean hasError = false;
for (TypeName argument : typeArguments.getArguments()) {
hasError |= checkForConstWithTypeParameters(argument);
}
return hasError;
}
// OK
return false;
}
/**
* This verifies that if the passed 'const' instance creation expression is being invoked on the
* resolved constructor.
* <p>
* This method assumes that the instance creation was tested to be 'const' before being called.
*
* @param node the instance creation expression to evaluate
* @param constructorName the constructor name, always non-{@code null}
* @param typeName the name of the type defining the constructor, always non-{@code null}
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_WITH_UNDEFINED_CONSTRUCTOR
* @see CompileTimeErrorCode#CONST_WITH_UNDEFINED_CONSTRUCTOR_DEFAULT
*/
private boolean checkForConstWithUndefinedConstructor(InstanceCreationExpression node,
ConstructorName constructorName, TypeName typeName) {
// OK if resolved
if (node.getStaticElement() != null) {
return false;
}
Type type = typeName.getType();
if (type instanceof InterfaceType) {
ClassElement element = ((InterfaceType) type).getElement();
if (element != null && element.isEnum()) {
// We have already reported the error.
return false;
}
}
Identifier className = typeName.getName();
// report as named or default constructor absence
SimpleIdentifier name = constructorName.getName();
if (name != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_WITH_UNDEFINED_CONSTRUCTOR,
name,
className,
name);
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_WITH_UNDEFINED_CONSTRUCTOR_DEFAULT,
constructorName,
className);
}
return true;
}
/**
* This verifies that there are no default parameters in the passed function type alias.
*
* @param node the function type alias to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#DEFAULT_VALUE_IN_FUNCTION_TYPE_ALIAS
*/
private boolean checkForDefaultValueInFunctionTypeAlias(FunctionTypeAlias node) {
boolean result = false;
FormalParameterList formalParameterList = node.getParameters();
NodeList<FormalParameter> parameters = formalParameterList.getParameters();
for (FormalParameter formalParameter : parameters) {
if (formalParameter instanceof DefaultFormalParameter) {
DefaultFormalParameter defaultFormalParameter = (DefaultFormalParameter) formalParameter;
if (defaultFormalParameter.getDefaultValue() != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.DEFAULT_VALUE_IN_FUNCTION_TYPE_ALIAS,
node);
result = true;
}
}
}
return result;
}
/**
* This verifies that the given default formal parameter is not part of a function typed
* parameter.
*
* @param node the default formal parameter to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#DEFAULT_VALUE_IN_FUNCTION_TYPED_PARAMETER
*/
private boolean checkForDefaultValueInFunctionTypedParameter(DefaultFormalParameter node) {
// OK, not in a function typed parameter.
if (!isInFunctionTypedFormalParameter) {
return false;
}
// OK, no default value.
if (node.getDefaultValue() == null) {
return false;
}
// Report problem.
errorReporter.reportErrorForNode(
CompileTimeErrorCode.DEFAULT_VALUE_IN_FUNCTION_TYPED_PARAMETER,
node);
return true;
}
/**
* This verifies that any deferred imports in the given compilation unit have a unique prefix.
*
* @param node the compilation unit containing the imports to be checked
* @return {@code true} if an error was generated
* @see CompileTimeErrorCode#SHARED_DEFERRED_PREFIX
*/
private boolean checkForDeferredPrefixCollisions(CompilationUnit node) {
boolean foundError = false;
NodeList<Directive> directives = node.getDirectives();
int count = directives.size();
if (count > 0) {
HashMap<PrefixElement, ArrayList<ImportDirective>> prefixToDirectivesMap = new HashMap<PrefixElement, ArrayList<ImportDirective>>();
for (int i = 0; i < count; i++) {
Directive directive = directives.get(i);
if (directive instanceof ImportDirective) {
ImportDirective importDirective = (ImportDirective) directive;
SimpleIdentifier prefix = importDirective.getPrefix();
if (prefix != null) {
Element element = prefix.getStaticElement();
if (element instanceof PrefixElement) {
PrefixElement prefixElement = (PrefixElement) element;
ArrayList<ImportDirective> elements = prefixToDirectivesMap.get(prefixElement);
if (elements == null) {
elements = new ArrayList<ImportDirective>();
prefixToDirectivesMap.put(prefixElement, elements);
}
elements.add(importDirective);
}
}
}
}
for (ArrayList<ImportDirective> imports : prefixToDirectivesMap.values()) {
if (hasDeferredPrefixCollision(imports)) {
foundError = true;
}
}
}
return foundError;
}
/**
* This verifies that the enclosing class does not have an instance member with the given name of
* the static member.
*
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#DUPLICATE_DEFINITION_INHERITANCE
*/
private boolean checkForDuplicateDefinitionInheritance() {
if (enclosingClass == null) {
return false;
}
boolean hasProblem = false;
for (ExecutableElement member : enclosingClass.getMethods()) {
if (!member.isStatic()) {
continue;
}
hasProblem |= checkForDuplicateDefinitionOfMember(member);
}
for (ExecutableElement member : enclosingClass.getAccessors()) {
if (!member.isStatic()) {
continue;
}
hasProblem |= checkForDuplicateDefinitionOfMember(member);
}
return hasProblem;
}
/**
* This verifies that the enclosing class does not have an instance member with the given name of
* the static member.
*
* @param staticMember the static member to check conflict for
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#DUPLICATE_DEFINITION_INHERITANCE
*/
private boolean checkForDuplicateDefinitionOfMember(ExecutableElement staticMember) {
// prepare name
String name = staticMember.getName();
if (name == null) {
return false;
}
// try to find member
ExecutableElement inheritedMember = inheritanceManager.lookupInheritance(enclosingClass, name);
if (inheritedMember == null) {
return false;
}
// OK, also static
if (inheritedMember.isStatic()) {
return false;
}
// determine the display name, use the extended display name if the enclosing class of the
// inherited member is in a different source
String displayName;
Element enclosingElement = inheritedMember.getEnclosingElement();
if (enclosingElement.getSource().equals(enclosingClass.getSource())) {
displayName = enclosingElement.getDisplayName();
} else {
displayName = enclosingElement.getExtendedDisplayName(null);
}
// report problem
errorReporter.reportErrorForOffset(
CompileTimeErrorCode.DUPLICATE_DEFINITION_INHERITANCE,
staticMember.getNameOffset(),
name.length(),
name,
displayName);
return true;
}
/**
* This verifies if the passed list literal has type arguments then there is exactly one.
*
* @param node the list literal to evaluate
* @param typeArguments the type arguments, always non-{@code null}
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#EXPECTED_ONE_LIST_TYPE_ARGUMENTS
*/
private boolean checkForExpectedOneListTypeArgument(ListLiteral node,
TypeArgumentList typeArguments) {
// check number of type arguments
int num = typeArguments.getArguments().size();
if (num == 1) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
StaticTypeWarningCode.EXPECTED_ONE_LIST_TYPE_ARGUMENTS,
typeArguments,
num);
return true;
}
/**
* This verifies the passed import has unique name among other exported libraries.
*
* @param node the export directive to evaluate
* @param exportElement the {@link ExportElement} retrieved from the node, if the element in the
* node was {@code null}, then this method is not called
* @param exportedLibrary the library element containing the exported element
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#EXPORT_DUPLICATED_LIBRARY_NAME
*/
private boolean checkForExportDuplicateLibraryName(ExportDirective node,
ExportElement exportElement, LibraryElement exportedLibrary) {
if (exportedLibrary == null) {
return false;
}
String name = exportedLibrary.getName();
// check if there is other exported library with the same name
LibraryElement prevLibrary = nameToExportElement.get(name);
if (prevLibrary != null) {
if (!prevLibrary.equals(exportedLibrary)) {
errorReporter.reportErrorForNode(
StaticWarningCode.EXPORT_DUPLICATED_LIBRARY_NAME,
node,
prevLibrary.getDefiningCompilationUnit().getDisplayName(),
exportedLibrary.getDefiningCompilationUnit().getDisplayName(),
name);
return true;
}
} else {
nameToExportElement.put(name, exportedLibrary);
}
// OK
return false;
}
/**
* Check that if the visiting library is not system, then any passed library should not be SDK
* internal library.
*
* @param node the export directive to evaluate
* @param exportElement the {@link ExportElement} retrieved from the node, if the element in the
* node was {@code null}, then this method is not called
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#EXPORT_INTERNAL_LIBRARY
*/
private boolean checkForExportInternalLibrary(ExportDirective node, ExportElement exportElement) {
if (isInSystemLibrary) {
return false;
}
// should be private
DartSdk sdk = currentLibrary.getContext().getSourceFactory().getDartSdk();
String uri = exportElement.getUri();
SdkLibrary sdkLibrary = sdk.getSdkLibrary(uri);
if (sdkLibrary == null) {
return false;
}
if (!sdkLibrary.isInternal()) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
CompileTimeErrorCode.EXPORT_INTERNAL_LIBRARY,
node,
node.getUri());
return true;
}
/**
* This verifies that the passed extends clause does not extend a deferred class.
*
* @param node the extends clause to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#EXTENDS_DEFERRED_CLASS
*/
private boolean checkForExtendsDeferredClass(ExtendsClause node) {
if (node == null) {
return false;
}
return checkForExtendsOrImplementsDeferredClass(
node.getSuperclass(),
CompileTimeErrorCode.EXTENDS_DEFERRED_CLASS);
}
/**
* This verifies that the passed type alias does not extend a deferred class.
*
* @param node the extends clause to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#EXTENDS_DISALLOWED_CLASS
*/
private boolean checkForExtendsDeferredClassInTypeAlias(ClassTypeAlias node) {
if (node == null) {
return false;
}
return checkForExtendsOrImplementsDeferredClass(
node.getSuperclass(),
CompileTimeErrorCode.EXTENDS_DEFERRED_CLASS);
}
/**
* This verifies that the passed extends clause does not extend classes such as num or String.
*
* @param node the extends clause to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#EXTENDS_DISALLOWED_CLASS
*/
private boolean checkForExtendsDisallowedClass(ExtendsClause node) {
if (node == null) {
return false;
}
return checkForExtendsOrImplementsDisallowedClass(
node.getSuperclass(),
CompileTimeErrorCode.EXTENDS_DISALLOWED_CLASS);
}
/**
* This verifies that the passed type alias does not extend classes such as num or String.
*
* @param node the extends clause to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#EXTENDS_DISALLOWED_CLASS
*/
private boolean checkForExtendsDisallowedClassInTypeAlias(ClassTypeAlias node) {
if (node == null) {
return false;
}
return checkForExtendsOrImplementsDisallowedClass(
node.getSuperclass(),
CompileTimeErrorCode.EXTENDS_DISALLOWED_CLASS);
}
/**
* This verifies that the passed type name does not extend, implement or mixin classes that are
* deferred.
*
* @param node the type name to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see #checkForExtendsDeferredClass(ExtendsClause)
* @see #checkForExtendsDeferredClassInTypeAlias(ClassTypeAlias)
* @see #checkForImplementsDeferredClass(ImplementsClause)
* @see #checkForAllMixinErrorCodes(WithClause)
* @see CompileTimeErrorCode#EXTENDS_DEFERRED_CLASS
* @see CompileTimeErrorCode#IMPLEMENTS_DEFERRED_CLASS
* @see CompileTimeErrorCode#MIXIN_DEFERRED_CLASS
*/
private boolean checkForExtendsOrImplementsDeferredClass(TypeName typeName, ErrorCode errorCode) {
if (typeName.isSynthetic()) {
return false;
}
if (typeName.isDeferred()) {
errorReporter.reportErrorForNode(errorCode, typeName, typeName.getName().getName());
return true;
}
return false;
}
/**
* This verifies that the passed type name does not extend, implement or mixin classes such as
* 'num' or 'String'.
*
* @param node the type name to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see #checkForExtendsDisallowedClass(ExtendsClause)
* @see #checkForExtendsDisallowedClassInTypeAlias(ClassTypeAlias)
* @see #checkForImplementsDisallowedClass(ImplementsClause)
* @see #checkForAllMixinErrorCodes(WithClause)
* @see CompileTimeErrorCode#EXTENDS_DISALLOWED_CLASS
* @see CompileTimeErrorCode#IMPLEMENTS_DISALLOWED_CLASS
* @see CompileTimeErrorCode#MIXIN_OF_DISALLOWED_CLASS
*/
private boolean checkForExtendsOrImplementsDisallowedClass(TypeName typeName, ErrorCode errorCode) {
if (typeName.isSynthetic()) {
return false;
}
Type superType = typeName.getType();
for (InterfaceType disallowedType : DISALLOWED_TYPES_TO_EXTEND_OR_IMPLEMENT) {
if (superType != null && superType.equals(disallowedType)) {
// if the violating type happens to be 'num', we need to rule out the case where the
// enclosing class is 'int' or 'double'
if (superType.equals(typeProvider.getNumType())) {
AstNode grandParent = typeName.getParent().getParent();
// Note: this is a corner case that won't happen often, so adding a field currentClass
// (see currentFunction) to ErrorVerifier isn't worth if for this case, but if the field
// currentClass is added, then this message should become a todo to not lookup the
// grandparent node
if (grandParent instanceof ClassDeclaration) {
ClassElement classElement = ((ClassDeclaration) grandParent).getElement();
Type classType = classElement.getType();
if (classType != null
&& (classType.equals(intType) || classType.equals(typeProvider.getDoubleType()))) {
return false;
}
}
}
// otherwise, report the error
errorReporter.reportErrorForNode(errorCode, typeName, disallowedType.getDisplayName());
return true;
}
}
return false;
}
/**
* This verifies that the passed constructor field initializer has compatible field and
* initializer expression types.
*
* @param node the constructor field initializer to test
* @param staticElement the static element from the name in the
* {@link ConstructorFieldInitializer}
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_FIELD_INITIALIZER_NOT_ASSIGNABLE
* @see StaticWarningCode#FIELD_INITIALIZER_NOT_ASSIGNABLE
*/
private boolean checkForFieldInitializerNotAssignable(ConstructorFieldInitializer node,
Element staticElement) {
// prepare field element
if (!(staticElement instanceof FieldElement)) {
return false;
}
FieldElement fieldElement = (FieldElement) staticElement;
// prepare field type
Type fieldType = fieldElement.getType();
// prepare expression type
Expression expression = node.getExpression();
if (expression == null) {
return false;
}
// test the static type of the expression
Type staticType = getStaticType(expression);
if (staticType == null) {
return false;
}
if (staticType.isAssignableTo(fieldType)) {
return false;
}
// report problem
if (isEnclosingConstructorConst) {
errorReporter.reportTypeErrorForNode(
CompileTimeErrorCode.CONST_FIELD_INITIALIZER_NOT_ASSIGNABLE,
expression,
staticType,
fieldType);
} else {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.FIELD_INITIALIZER_NOT_ASSIGNABLE,
expression,
staticType,
fieldType);
}
return true;
// TODO(brianwilkerson) Define a hint corresponding to these errors and report it if appropriate.
// // test the propagated type of the expression
// Type propagatedType = expression.getPropagatedType();
// if (propagatedType != null && propagatedType.isAssignableTo(fieldType)) {
// return false;
// }
// // report problem
// if (isEnclosingConstructorConst) {
// errorReporter.reportTypeErrorForNode(
// CompileTimeErrorCode.CONST_FIELD_INITIALIZER_NOT_ASSIGNABLE,
// expression,
// propagatedType == null ? staticType : propagatedType,
// fieldType);
// } else {
// errorReporter.reportTypeErrorForNode(
// StaticWarningCode.FIELD_INITIALIZER_NOT_ASSIGNABLE,
// expression,
// propagatedType == null ? staticType : propagatedType,
// fieldType);
// }
// return true;
}
/**
* This verifies that the passed field formal parameter is in a constructor declaration.
*
* @param node the field formal parameter to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#FIELD_INITIALIZER_OUTSIDE_CONSTRUCTOR
*/
private boolean checkForFieldInitializingFormalRedirectingConstructor(FieldFormalParameter node) {
ConstructorDeclaration constructor = node.getAncestor(ConstructorDeclaration.class);
if (constructor == null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.FIELD_INITIALIZER_OUTSIDE_CONSTRUCTOR,
node);
return true;
}
// constructor cannot be a factory
if (constructor.getFactoryKeyword() != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.FIELD_INITIALIZER_FACTORY_CONSTRUCTOR,
node);
return true;
}
// constructor cannot have a redirection
for (ConstructorInitializer initializer : constructor.getInitializers()) {
if (initializer instanceof RedirectingConstructorInvocation) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.FIELD_INITIALIZER_REDIRECTING_CONSTRUCTOR,
node);
return true;
}
}
// OK
return false;
}
/**
* This verifies that the passed variable declaration list has only initialized variables if the
* list is final or const. This method is called by
* {@link #checkForFinalNotInitializedInClass(ClassDeclaration)},
* {@link #visitTopLevelVariableDeclaration(TopLevelVariableDeclaration)} and
* {@link #visitVariableDeclarationStatement(VariableDeclarationStatement)}.
*
* @param node the class declaration to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_NOT_INITIALIZED
* @see StaticWarningCode#FINAL_NOT_INITIALIZED
*/
private boolean checkForFinalNotInitialized(VariableDeclarationList node) {
if (isInNativeClass) {
return false;
}
boolean foundError = false;
if (!node.isSynthetic()) {
NodeList<VariableDeclaration> variables = node.getVariables();
for (VariableDeclaration variable : variables) {
if (variable.getInitializer() == null) {
if (node.isConst()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_NOT_INITIALIZED,
variable.getName(),
variable.getName().getName());
} else if (node.isFinal()) {
errorReporter.reportErrorForNode(
StaticWarningCode.FINAL_NOT_INITIALIZED,
variable.getName(),
variable.getName().getName());
}
foundError = true;
}
}
}
return foundError;
}
/**
* This verifies that final fields that are declared, without any constructors in the enclosing
* class, are initialized. Cases in which there is at least one constructor are handled at the end
* of {@link #checkForAllFinalInitializedErrorCodes(ConstructorDeclaration)}.
*
* @param node the class declaration to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#CONST_NOT_INITIALIZED
* @see StaticWarningCode#FINAL_NOT_INITIALIZED
*/
private boolean checkForFinalNotInitializedInClass(ClassDeclaration node) {
NodeList<ClassMember> classMembers = node.getMembers();
for (ClassMember classMember : classMembers) {
if (classMember instanceof ConstructorDeclaration) {
return false;
}
}
boolean foundError = false;
for (ClassMember classMember : classMembers) {
if (classMember instanceof FieldDeclaration) {
FieldDeclaration field = (FieldDeclaration) classMember;
foundError = foundError | checkForFinalNotInitialized(field.getFields());
}
}
return foundError;
}
/**
* This verifies that the passed implements clause does not implement classes that are deferred.
*
* @param node the implements clause to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#IMPLEMENTS_DEFERRED_CLASS
*/
private boolean checkForImplementsDeferredClass(ImplementsClause node) {
if (node == null) {
return false;
}
boolean foundError = false;
for (TypeName type : node.getInterfaces()) {
foundError = foundError
| checkForExtendsOrImplementsDeferredClass(
type,
CompileTimeErrorCode.IMPLEMENTS_DEFERRED_CLASS);
}
return foundError;
}
/**
* This verifies that the passed implements clause does not implement classes such as 'num' or
* 'String'.
*
* @param node the implements clause to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#IMPLEMENTS_DISALLOWED_CLASS
*/
private boolean checkForImplementsDisallowedClass(ImplementsClause node) {
if (node == null) {
return false;
}
boolean foundError = false;
for (TypeName type : node.getInterfaces()) {
foundError = foundError
| checkForExtendsOrImplementsDisallowedClass(
type,
CompileTimeErrorCode.IMPLEMENTS_DISALLOWED_CLASS);
}
return foundError;
}
/**
* This verifies that if the passed identifier is part of constructor initializer, then it does
* not reference implicitly 'this' expression.
*
* @param node the simple identifier to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#IMPLICIT_THIS_REFERENCE_IN_INITIALIZER
* @see CompileTimeErrorCode#INSTANCE_MEMBER_ACCESS_FROM_STATIC TODO(scheglov) rename thid method
*/
private boolean checkForImplicitThisReferenceInInitializer(SimpleIdentifier node) {
if (!isInConstructorInitializer && !isInStaticMethod && !isInFactory
&& !isInInstanceVariableInitializer && !isInStaticVariableDeclaration) {
return false;
}
// prepare element
Element element = node.getStaticElement();
if (!(element instanceof MethodElement || element instanceof PropertyAccessorElement)) {
return false;
}
// static element
ExecutableElement executableElement = (ExecutableElement) element;
if (executableElement.isStatic()) {
return false;
}
// not a class member
Element enclosingElement = element.getEnclosingElement();
if (!(enclosingElement instanceof ClassElement)) {
return false;
}
// comment
AstNode parent = node.getParent();
if (parent instanceof CommentReference) {
return false;
}
// qualified method invocation
if (parent instanceof MethodInvocation) {
MethodInvocation invocation = (MethodInvocation) parent;
if (invocation.getMethodName() == node && invocation.getRealTarget() != null) {
return false;
}
}
// qualified property access
if (parent instanceof PropertyAccess) {
PropertyAccess access = (PropertyAccess) parent;
if (access.getPropertyName() == node && access.getRealTarget() != null) {
return false;
}
}
if (parent instanceof PrefixedIdentifier) {
PrefixedIdentifier prefixed = (PrefixedIdentifier) parent;
if (prefixed.getIdentifier() == node) {
return false;
}
}
// report problem
if (isInStaticMethod) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_STATIC,
node);
} else if (isInFactory) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INSTANCE_MEMBER_ACCESS_FROM_FACTORY,
node);
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.IMPLICIT_THIS_REFERENCE_IN_INITIALIZER,
node);
}
return true;
}
/**
* This verifies the passed import has unique name among other imported libraries.
*
* @param node the import directive to evaluate
* @param importElement the {@link ImportElement} retrieved from the node, if the element in the
* node was {@code null}, then this method is not called
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#IMPORT_DUPLICATED_LIBRARY_NAME
*/
private boolean checkForImportDuplicateLibraryName(ImportDirective node,
ImportElement importElement) {
// prepare imported library
LibraryElement nodeLibrary = importElement.getImportedLibrary();
if (nodeLibrary == null) {
return false;
}
String name = nodeLibrary.getName();
// check if there is other imported library with the same name
LibraryElement prevLibrary = nameToImportElement.get(name);
if (prevLibrary != null) {
if (!prevLibrary.equals(nodeLibrary)) {
errorReporter.reportErrorForNode(
StaticWarningCode.IMPORT_DUPLICATED_LIBRARY_NAME,
node,
prevLibrary.getDefiningCompilationUnit().getDisplayName(),
nodeLibrary.getDefiningCompilationUnit().getDisplayName(),
name);
return true;
}
} else {
nameToImportElement.put(name, nodeLibrary);
}
// OK
return false;
}
/**
* Check that if the visiting library is not system, then any passed library should not be SDK
* internal library.
*
* @param node the import directive to evaluate
* @param importElement the {@link ImportElement} retrieved from the node, if the element in the
* node was {@code null}, then this method is not called
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#IMPORT_INTERNAL_LIBRARY
*/
private boolean checkForImportInternalLibrary(ImportDirective node, ImportElement importElement) {
if (isInSystemLibrary) {
return false;
}
// should be private
DartSdk sdk = currentLibrary.getContext().getSourceFactory().getDartSdk();
String uri = importElement.getUri();
SdkLibrary sdkLibrary = sdk.getSdkLibrary(uri);
if (sdkLibrary == null) {
return false;
}
if (!sdkLibrary.isInternal()) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
CompileTimeErrorCode.IMPORT_INTERNAL_LIBRARY,
node,
node.getUri());
return true;
}
/**
* For each class declaration, this method is called which verifies that all inherited members are
* inherited consistently.
*
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#INCONSISTENT_METHOD_INHERITANCE
*/
private boolean checkForInconsistentMethodInheritance() {
// Ensure that the inheritance manager has a chance to generate all errors we may care about,
// note that we ensure that the interfaces data since there are no errors.
inheritanceManager.getMapOfMembersInheritedFromInterfaces(enclosingClass);
HashSet<AnalysisError> errors = inheritanceManager.getErrors(enclosingClass);
if (errors == null || errors.isEmpty()) {
return false;
}
for (AnalysisError error : errors) {
errorReporter.reportError(error);
}
return true;
}
/**
* This checks the given "typeReference" is not a type reference and that then the "name" is
* reference to an instance member.
*
* @param typeReference the resolved {@link ClassElement} of the left hand side of the expression,
* or {@code null}, aka, the class element of 'C' in 'C.x', see
* {@link #getTypeReference(Expression)}
* @param name the accessed name to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#INSTANCE_ACCESS_TO_STATIC_MEMBER
*/
private boolean checkForInstanceAccessToStaticMember(ClassElement typeReference,
SimpleIdentifier name) {
// OK, in comment
if (isInComment) {
return false;
}
// OK, target is a type
if (typeReference != null) {
return false;
}
// prepare member Element
Element element = name.getStaticElement();
if (!(element instanceof ExecutableElement)) {
return false;
}
ExecutableElement executableElement = (ExecutableElement) element;
// OK, top-level element
if (!(executableElement.getEnclosingElement() instanceof ClassElement)) {
return false;
}
// OK, instance member
if (!executableElement.isStatic()) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
StaticTypeWarningCode.INSTANCE_ACCESS_TO_STATIC_MEMBER,
name,
name.getName());
return true;
}
/**
* This checks whether the given {@link executableElement} collides with the name of a static
* method in one of its superclasses, and reports the appropriate warning if it does.
*
* @param executableElement the method to check.
* @param errorNameTarget the node to report problems on.
* @return {@code true} if and only if a warning was generated.
* @see StaticTypeWarningCode#INSTANCE_METHOD_NAME_COLLIDES_WITH_SUPERCLASS_STATIC
*/
private boolean checkForInstanceMethodNameCollidesWithSuperclassStatic(
ExecutableElement executableElement, SimpleIdentifier errorNameTarget) {
String executableElementName = executableElement.getName();
if (!(executableElement instanceof PropertyAccessorElement) && !executableElement.isOperator()) {
HashSet<ClassElement> visitedClasses = new HashSet<ClassElement>();
InterfaceType superclassType = enclosingClass.getSupertype();
ClassElement superclassElement = superclassType == null ? null : superclassType.getElement();
boolean executableElementPrivate = Identifier.isPrivateName(executableElementName);
while (superclassElement != null && !visitedClasses.contains(superclassElement)) {
visitedClasses.add(superclassElement);
LibraryElement superclassLibrary = superclassElement.getLibrary();
// Check fields.
FieldElement[] fieldElts = superclassElement.getFields();
for (FieldElement fieldElt : fieldElts) {
// We need the same name.
if (!fieldElt.getName().equals(executableElementName)) {
continue;
}
// Ignore if private in a different library - cannot collide.
if (executableElementPrivate && !currentLibrary.equals(superclassLibrary)) {
continue;
}
// instance vs. static
if (fieldElt.isStatic()) {
errorReporter.reportErrorForNode(
StaticWarningCode.INSTANCE_METHOD_NAME_COLLIDES_WITH_SUPERCLASS_STATIC,
errorNameTarget,
executableElementName,
fieldElt.getEnclosingElement().getDisplayName());
return true;
}
}
// Check methods.
MethodElement[] methodElements = superclassElement.getMethods();
for (MethodElement methodElement : methodElements) {
// We need the same name.
if (!methodElement.getName().equals(executableElementName)) {
continue;
}
// Ignore if private in a different library - cannot collide.
if (executableElementPrivate && !currentLibrary.equals(superclassLibrary)) {
continue;
}
// instance vs. static
if (methodElement.isStatic()) {
errorReporter.reportErrorForNode(
StaticWarningCode.INSTANCE_METHOD_NAME_COLLIDES_WITH_SUPERCLASS_STATIC,
errorNameTarget,
executableElementName,
methodElement.getEnclosingElement().getDisplayName());
return true;
}
}
superclassType = superclassElement.getSupertype();
superclassElement = superclassType == null ? null : superclassType.getElement();
}
}
return false;
}
/**
* This verifies that an 'int' can be assigned to the parameter corresponding to the given
* expression. This is used for prefix and postfix expressions where the argument value is
* implicit.
*
* @param argument the expression to which the operator is being applied
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#ARGUMENT_TYPE_NOT_ASSIGNABLE
*/
private boolean checkForIntNotAssignable(Expression argument) {
if (argument == null) {
return false;
}
ParameterElement staticParameterElement = argument.getStaticParameterElement();
Type staticParameterType = staticParameterElement == null ? null
: staticParameterElement.getType();
return checkForArgumentTypeNotAssignable(
argument,
staticParameterType,
intType,
StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE);
}
/**
* This verifies that the passed {@link Annotation} isn't defined in a deferred library.
*
* @param node the {@link Annotation}
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode.INVALID_ANNOTATION_FROM_DEFERRED_LIBRARY
*/
private boolean checkForInvalidAnnotationFromDeferredLibrary(Annotation node) {
Identifier nameIdentifier = node.getName();
if (nameIdentifier instanceof PrefixedIdentifier) {
if (((PrefixedIdentifier) nameIdentifier).isDeferred()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_ANNOTATION_FROM_DEFERRED_LIBRARY,
node.getName());
return true;
}
}
return false;
}
/**
* This verifies that the passed left hand side and right hand side represent a valid assignment.
*
* @param lhs the left hand side expression
* @param rhs the right hand side expression
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#INVALID_ASSIGNMENT
*/
private boolean checkForInvalidAssignment(Expression lhs, Expression rhs) {
if (lhs == null || rhs == null) {
return false;
}
VariableElement leftVariableElement = getVariableElement(lhs);
Type leftType = (leftVariableElement == null) ? getStaticType(lhs)
: leftVariableElement.getType();
Type staticRightType = getStaticType(rhs);
if (!staticRightType.isAssignableTo(leftType)) {
errorReporter.reportTypeErrorForNode(
StaticTypeWarningCode.INVALID_ASSIGNMENT,
rhs,
staticRightType,
leftType);
return true;
}
return false;
}
/**
* Given an assignment using a compound assignment operator, this verifies that the given
* assignment is valid.
*
* @param node the assignment expression being tested
* @param lhs the left hand side expression
* @param rhs the right hand side expression
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#INVALID_ASSIGNMENT
*/
private boolean checkForInvalidCompoundAssignment(AssignmentExpression node, Expression lhs,
Expression rhs) {
if (lhs == null) {
return false;
}
VariableElement leftVariableElement = getVariableElement(lhs);
Type leftType = (leftVariableElement == null) ? getStaticType(lhs)
: leftVariableElement.getType();
MethodElement invokedMethod = node.getStaticElement();
if (invokedMethod == null) {
return false;
}
Type rightType = invokedMethod.getType().getReturnType();
if (leftType == null || rightType == null) {
return false;
}
if (!rightType.isAssignableTo(leftType)) {
errorReporter.reportTypeErrorForNode(
StaticTypeWarningCode.INVALID_ASSIGNMENT,
rhs,
rightType,
leftType);
return true;
}
return false;
}
/**
* Check the given initializer to ensure that the field being initialized is a valid field.
*
* @param node the field initializer being checked
* @param fieldName the field name from the {@link ConstructorFieldInitializer}
* @param staticElement the static element from the name in the
* {@link ConstructorFieldInitializer}
*/
private void checkForInvalidField(ConstructorFieldInitializer node, SimpleIdentifier fieldName,
Element staticElement) {
if (staticElement instanceof FieldElement) {
FieldElement fieldElement = (FieldElement) staticElement;
if (fieldElement.isSynthetic()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INITIALIZER_FOR_NON_EXISTANT_FIELD,
node,
fieldName);
} else if (fieldElement.isStatic()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INITIALIZER_FOR_STATIC_FIELD,
node,
fieldName);
}
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INITIALIZER_FOR_NON_EXISTANT_FIELD,
node,
fieldName);
return;
}
}
/**
* Check to see whether the given function body has a modifier associated with it, and report it
* as an error if it does.
*
* @param body the function body being checked
* @param errorCode the error code to be reported if a modifier is found
* @return {@code true} if an error was reported
*/
private boolean checkForInvalidModifierOnBody(FunctionBody body, CompileTimeErrorCode errorCode) {
Token keyword = body.getKeyword();
if (keyword != null) {
errorReporter.reportErrorForToken(errorCode, keyword, keyword.getLexeme());
return true;
}
return false;
}
/**
* This verifies that the usage of the passed 'this' is valid.
*
* @param node the 'this' expression to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#INVALID_REFERENCE_TO_THIS
*/
private boolean checkForInvalidReferenceToThis(ThisExpression node) {
if (!isThisInValidContext(node)) {
errorReporter.reportErrorForNode(CompileTimeErrorCode.INVALID_REFERENCE_TO_THIS, node);
return true;
}
return false;
}
/**
* Checks to ensure that the passed {@link ListLiteral} or {@link MapLiteral} does not have a type
* parameter as a type argument.
*
* @param arguments a non-{@code null}, non-empty {@link TypeName} node list from the respective
* {@link ListLiteral} or {@link MapLiteral}
* @param errorCode either {@link CompileTimeErrorCode#INVALID_TYPE_ARGUMENT_IN_CONST_LIST} or
* {@link CompileTimeErrorCode#INVALID_TYPE_ARGUMENT_IN_CONST_MAP}
* @return {@code true} if and only if an error code is generated on the passed node
*/
private boolean checkForInvalidTypeArgumentInConstTypedLiteral(NodeList<TypeName> arguments,
ErrorCode errorCode) {
boolean foundError = false;
for (TypeName typeName : arguments) {
if (typeName.getType() instanceof TypeParameterType) {
errorReporter.reportErrorForNode(errorCode, typeName, typeName.getName());
foundError = true;
}
}
return foundError;
}
/**
* This verifies that the elements given {@link ListLiteral} are subtypes of the specified element
* type.
*
* @param node the list literal to evaluate
* @param typeArguments the type arguments, always non-{@code null}
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
* @see StaticWarningCode#LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
*/
private boolean checkForListElementTypeNotAssignable(ListLiteral node,
TypeArgumentList typeArguments) {
NodeList<TypeName> typeNames = typeArguments.getArguments();
if (typeNames.size() < 1) {
return false;
}
Type listElementType = typeNames.get(0).getType();
// Prepare problem to report.
ErrorCode errorCode;
if (node.getConstKeyword() != null) {
errorCode = CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE;
} else {
errorCode = StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE;
}
// Check every list element.
boolean hasProblems = false;
for (Expression element : node.getElements()) {
hasProblems |= checkForArgumentTypeNotAssignableWithExpectedTypes(
element,
listElementType,
errorCode);
}
return hasProblems;
}
/**
* This verifies that the key/value of entries of the given {@link MapLiteral} are subtypes of the
* key/value types specified in the type arguments.
*
* @param node the map literal to evaluate
* @param typeArguments the type arguments, always non-{@code null}
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#MAP_KEY_TYPE_NOT_ASSIGNABLE
* @see CompileTimeErrorCode#MAP_VALUE_TYPE_NOT_ASSIGNABLE
* @see StaticWarningCode#MAP_KEY_TYPE_NOT_ASSIGNABLE
* @see StaticWarningCode#MAP_VALUE_TYPE_NOT_ASSIGNABLE
*/
private boolean checkForMapTypeNotAssignable(MapLiteral node, TypeArgumentList typeArguments) {
// Prepare maps key/value types.
NodeList<TypeName> typeNames = typeArguments.getArguments();
if (typeNames.size() < 2) {
return false;
}
Type keyType = typeNames.get(0).getType();
Type valueType = typeNames.get(1).getType();
// Prepare problem to report.
ErrorCode keyErrorCode;
ErrorCode valueErrorCode;
if (node.getConstKeyword() != null) {
keyErrorCode = CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE;
valueErrorCode = CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE;
} else {
keyErrorCode = StaticWarningCode.MAP_KEY_TYPE_NOT_ASSIGNABLE;
valueErrorCode = StaticWarningCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE;
}
// Check every map entry.
boolean hasProblems = false;
NodeList<MapLiteralEntry> entries = node.getEntries();
for (MapLiteralEntry entry : entries) {
Expression key = entry.getKey();
Expression value = entry.getValue();
hasProblems |= checkForArgumentTypeNotAssignableWithExpectedTypes(key, keyType, keyErrorCode);
hasProblems |= checkForArgumentTypeNotAssignableWithExpectedTypes(
value,
valueType,
valueErrorCode);
}
return hasProblems;
}
/**
* This verifies that the {@link #enclosingClass} does not define members with the same name as
* the enclosing class.
*
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#MEMBER_WITH_CLASS_NAME
*/
private boolean checkForMemberWithClassName() {
if (enclosingClass == null) {
return false;
}
String className = enclosingClass.getName();
if (className == null) {
return false;
}
boolean problemReported = false;
// check accessors
for (PropertyAccessorElement accessor : enclosingClass.getAccessors()) {
if (className.equals(accessor.getName())) {
errorReporter.reportErrorForOffset(
CompileTimeErrorCode.MEMBER_WITH_CLASS_NAME,
accessor.getNameOffset(),
className.length());
problemReported = true;
}
}
// don't check methods, they would be constructors
// done
return problemReported;
}
/**
* Check to make sure that all similarly typed accessors are of the same type (including inherited
* accessors).
*
* @param node the accessor currently being visited
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode.MISMATCHED_GETTER_AND_SETTER_TYPES
* @see StaticWarningCode.MISMATCHED_GETTER_AND_SETTER_TYPES_FROM_SUPERTYPE
*/
// TODO (jwren) In future nit CL, rename this method (and tests) to be consistent with name of error enum
// TODO (jwren) Revisit error code messages, to add more clarity, we may need the pair split into four codes
private boolean checkForMismatchedAccessorTypes(Declaration accessorDeclaration,
String accessorTextName) {
ExecutableElement accessorElement = (ExecutableElement) accessorDeclaration.getElement();
if (!(accessorElement instanceof PropertyAccessorElement)) {
return false;
}
PropertyAccessorElement propertyAccessorElement = (PropertyAccessorElement) accessorElement;
PropertyAccessorElement counterpartAccessor = null;
ClassElement enclosingClassForCounterpart = null;
if (propertyAccessorElement.isGetter()) {
counterpartAccessor = propertyAccessorElement.getCorrespondingSetter();
} else {
counterpartAccessor = propertyAccessorElement.getCorrespondingGetter();
// If the setter and getter are in the same enclosing element, return, this prevents having
// MISMATCHED_GETTER_AND_SETTER_TYPES reported twice.
if (counterpartAccessor != null
&& counterpartAccessor.getEnclosingElement() == propertyAccessorElement.getEnclosingElement()) {
return false;
}
}
if (counterpartAccessor == null) {
// If the accessor is declared in a class, check the superclasses.
if (enclosingClass != null) {
// Figure out the correct identifier to lookup in the inheritance graph, if 'x', then 'x=',
// or if 'x=', then 'x'.
String lookupIdentifier = propertyAccessorElement.getName();
if (StringUtilities.endsWithChar(lookupIdentifier, '=')) {
lookupIdentifier = lookupIdentifier.substring(0, lookupIdentifier.length() - 1);
} else {
lookupIdentifier += "=";
}
// lookup with the identifier.
ExecutableElement elementFromInheritance = inheritanceManager.lookupInheritance(
enclosingClass,
lookupIdentifier);
// Verify that we found something, and that it is an accessor
if (elementFromInheritance != null
&& elementFromInheritance instanceof PropertyAccessorElement) {
enclosingClassForCounterpart = (ClassElement) elementFromInheritance.getEnclosingElement();
counterpartAccessor = (PropertyAccessorElement) elementFromInheritance;
}
}
if (counterpartAccessor == null) {
return false;
}
}
// Default of null == no accessor or no type (dynamic)
Type getterType = null;
Type setterType = null;
// Get an existing counterpart accessor if any.
if (propertyAccessorElement.isGetter()) {
getterType = getGetterType(propertyAccessorElement);
setterType = getSetterType(counterpartAccessor);
} else if (propertyAccessorElement.isSetter()) {
setterType = getSetterType(propertyAccessorElement);
getterType = getGetterType(counterpartAccessor);
}
// If either types are not assignable to each other, report an error (if the getter is null,
// it is dynamic which is assignable to everything).
if (setterType != null && getterType != null && !getterType.isAssignableTo(setterType)) {
if (enclosingClassForCounterpart == null) {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.MISMATCHED_GETTER_AND_SETTER_TYPES,
accessorDeclaration,
accessorTextName,
setterType,
getterType);
return true;
} else {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.MISMATCHED_GETTER_AND_SETTER_TYPES_FROM_SUPERTYPE,
accessorDeclaration,
accessorTextName,
setterType,
getterType,
enclosingClassForCounterpart.getDisplayName());
}
}
return false;
}
/**
* Check to make sure that switch statements whose static type is an enum type either have a
* default case or include all of the enum constants.
*
* @param statement the switch statement to check
* @return {@code true} if and only if an error code is generated on the passed node
*/
private boolean checkForMissingEnumConstantInSwitch(SwitchStatement statement) {
// TODO(brianwilkerson) This needs to be checked after constant values have been computed.
Expression expression = statement.getExpression();
Type expressionType = getStaticType(expression);
if (expressionType == null) {
return false;
}
Element expressionElement = expressionType.getElement();
if (!(expressionElement instanceof ClassElement)) {
return false;
}
ClassElement classElement = (ClassElement) expressionElement;
if (!classElement.isEnum()) {
return false;
}
ArrayList<String> constantNames = new ArrayList<String>();
FieldElement[] fields = classElement.getFields();
int fieldCount = fields.length;
for (int i = 0; i < fieldCount; i++) {
FieldElement field = fields[i];
if (field.isStatic() && !field.isSynthetic()) {
constantNames.add(field.getName());
}
}
NodeList<SwitchMember> members = statement.getMembers();
int memberCount = members.size();
for (int i = 0; i < memberCount; i++) {
SwitchMember member = members.get(i);
if (member instanceof SwitchDefault) {
return false;
}
String constantName = getConstantName(((SwitchCase) member).getExpression());
if (constantName != null) {
constantNames.remove(constantName);
}
}
int nameCount = constantNames.size();
if (nameCount == 0) {
return false;
}
for (int i = 0; i < nameCount; i++) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.MISSING_ENUM_CONSTANT_IN_SWITCH,
statement,
constantNames.get(i));
}
return true;
}
/**
* This verifies that the given function body does not contain return statements that both have
* and do not have return values.
*
* @param node the function body being tested
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#MIXED_RETURN_TYPES
*/
private boolean checkForMixedReturns(BlockFunctionBody node) {
if (hasReturnWithoutValue) {
return false;
}
int withCount = returnsWith.size();
int withoutCount = returnsWithout.size();
if (withCount > 0 && withoutCount > 0) {
for (int i = 0; i < withCount; i++) {
errorReporter.reportErrorForToken(
StaticWarningCode.MIXED_RETURN_TYPES,
returnsWith.get(i).getKeyword());
}
for (int i = 0; i < withoutCount; i++) {
errorReporter.reportErrorForToken(
StaticWarningCode.MIXED_RETURN_TYPES,
returnsWithout.get(i).getKeyword());
}
return true;
}
return false;
}
/**
* This verifies that the passed mixin does not have an explicitly declared constructor.
*
* @param mixinName the node to report problem on
* @param mixinElement the mixing to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#MIXIN_DECLARES_CONSTRUCTOR
*/
private boolean checkForMixinDeclaresConstructor(TypeName mixinName, ClassElement mixinElement) {
for (ConstructorElement constructor : mixinElement.getConstructors()) {
if (!constructor.isSynthetic() && !constructor.isFactory()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.MIXIN_DECLARES_CONSTRUCTOR,
mixinName,
mixinElement.getName());
return true;
}
}
return false;
}
/**
* This verifies that the passed mixin has the 'Object' superclass.
*
* @param mixinName the node to report problem on
* @param mixinElement the mixing to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#MIXIN_INHERITS_FROM_NOT_OBJECT
*/
private boolean checkForMixinInheritsNotFromObject(TypeName mixinName, ClassElement mixinElement) {
InterfaceType mixinSupertype = mixinElement.getSupertype();
if (mixinSupertype != null) {
if (!mixinSupertype.isObject() || !mixinElement.isTypedef()
&& mixinElement.getMixins().length != 0) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.MIXIN_INHERITS_FROM_NOT_OBJECT,
mixinName,
mixinElement.getName());
return true;
}
}
return false;
}
/**
* This verifies that the passed mixin does not reference 'super'.
*
* @param mixinName the node to report problem on
* @param mixinElement the mixing to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#MIXIN_REFERENCES_SUPER
*/
private boolean checkForMixinReferencesSuper(TypeName mixinName, ClassElement mixinElement) {
if (mixinElement.hasReferenceToSuper()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.MIXIN_REFERENCES_SUPER,
mixinName,
mixinElement.getName());
}
return false;
}
/**
* This verifies that the passed constructor has at most one 'super' initializer.
*
* @param node the constructor declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#MULTIPLE_SUPER_INITIALIZERS
*/
private boolean checkForMultipleSuperInitializers(ConstructorDeclaration node) {
int numSuperInitializers = 0;
for (ConstructorInitializer initializer : node.getInitializers()) {
if (initializer instanceof SuperConstructorInvocation) {
numSuperInitializers++;
if (numSuperInitializers > 1) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.MULTIPLE_SUPER_INITIALIZERS,
initializer);
}
}
}
return numSuperInitializers > 0;
}
/**
* Checks to ensure that native function bodies can only in SDK code.
*
* @param node the native function body to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see ParserErrorCode#NATIVE_FUNCTION_BODY_IN_NON_SDK_CODE
*/
private boolean checkForNativeFunctionBodyInNonSDKCode(NativeFunctionBody node) {
if (!isInSystemLibrary && !hasExtUri) {
errorReporter.reportErrorForNode(ParserErrorCode.NATIVE_FUNCTION_BODY_IN_NON_SDK_CODE, node);
return true;
}
return false;
}
/**
* This verifies that the passed 'new' instance creation expression invokes existing constructor.
* <p>
* This method assumes that the instance creation was tested to be 'new' before being called.
*
* @param node the instance creation expression to evaluate
* @param constructorName the constructor name, always non-{@code null}
* @param typeName the name of the type defining the constructor, always non-{@code null}
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#NEW_WITH_UNDEFINED_CONSTRUCTOR
*/
private boolean checkForNewWithUndefinedConstructor(InstanceCreationExpression node,
ConstructorName constructorName, TypeName typeName) {
// OK if resolved
if (node.getStaticElement() != null) {
return false;
}
Type type = typeName.getType();
if (type instanceof InterfaceType) {
ClassElement element = ((InterfaceType) type).getElement();
if (element != null && element.isEnum()) {
// We have already reported the error.
return false;
}
}
// prepare class name
Identifier className = typeName.getName();
// report as named or default constructor absence
SimpleIdentifier name = constructorName.getName();
if (name != null) {
errorReporter.reportErrorForNode(
StaticWarningCode.NEW_WITH_UNDEFINED_CONSTRUCTOR,
name,
className,
name);
} else {
errorReporter.reportErrorForNode(
StaticWarningCode.NEW_WITH_UNDEFINED_CONSTRUCTOR_DEFAULT,
constructorName,
className);
}
return true;
}
/**
* This checks that if the passed class declaration implicitly calls default constructor of its
* superclass, there should be such default constructor - implicit or explicit.
*
* @param node the {@link ClassDeclaration} to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT
*/
private boolean checkForNoDefaultSuperConstructorImplicit(ClassDeclaration node) {
// do nothing if there is explicit constructor
ConstructorElement[] constructors = enclosingClass.getConstructors();
if (!constructors[0].isSynthetic()) {
return false;
}
// prepare super
InterfaceType superType = enclosingClass.getSupertype();
if (superType == null) {
return false;
}
ClassElement superElement = superType.getElement();
// try to find default generative super constructor
ConstructorElement superUnnamedConstructor = superElement.getUnnamedConstructor();
if (superUnnamedConstructor != null) {
if (superUnnamedConstructor.isFactory()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NON_GENERATIVE_CONSTRUCTOR,
node.getName(),
superUnnamedConstructor);
return true;
}
if (superUnnamedConstructor.isDefaultConstructor()) {
return true;
}
}
// report problem
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_IMPLICIT,
node.getName(),
superType.getDisplayName());
return true;
}
/**
* This checks that passed class declaration overrides all members required by its superclasses
* and interfaces.
*
* @param classNameNode the {@link SimpleIdentifier} to be used if there is a violation, this is
* either the named from the {@link ClassDeclaration} or from the {@link ClassTypeAlias}.
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE
* @see StaticWarningCode#NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO
* @see StaticWarningCode#NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE
* @see StaticWarningCode#NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR
* @see StaticWarningCode#NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_PLUS
*/
private boolean checkForNonAbstractClassInheritsAbstractMember(SimpleIdentifier classNameNode) {
if (enclosingClass.isAbstract()) {
return false;
}
//
// Store in local sets the set of all method and accessor names
//
MethodElement[] methods = enclosingClass.getMethods();
for (MethodElement method : methods) {
String methodName = method.getName();
// If the enclosing class declares the method noSuchMethod(), then return.
// From Spec: It is a static warning if a concrete class does not have an implementation for
// a method in any of its superinterfaces unless it declares its own noSuchMethod
// method (7.10).
if (methodName.equals(FunctionElement.NO_SUCH_METHOD_METHOD_NAME)) {
return false;
}
}
HashSet<ExecutableElement> missingOverrides = new HashSet<ExecutableElement>();
//
// Loop through the set of all executable elements declared in the implicit interface.
//
MemberMap membersInheritedFromInterfaces = inheritanceManager.getMapOfMembersInheritedFromInterfaces(enclosingClass);
MemberMap membersInheritedFromSuperclasses = inheritanceManager.getMapOfMembersInheritedFromClasses(enclosingClass);
for (int i = 0; i < membersInheritedFromInterfaces.getSize(); i++) {
String memberName = membersInheritedFromInterfaces.getKey(i);
ExecutableElement executableElt = membersInheritedFromInterfaces.getValue(i);
if (memberName == null) {
break;
}
// If the element is not synthetic and can be determined to be defined in Object, skip it.
if (executableElt.getEnclosingElement() != null
&& ((ClassElement) executableElt.getEnclosingElement()).getType().isObject()) {
continue;
}
// Check to see if some element is in local enclosing class that matches the name of the
// required member.
if (isMemberInClassOrMixin(executableElt, enclosingClass)) {
// We do not have to verify that this implementation of the found method matches the
// required function type: the set of StaticWarningCode.INVALID_METHOD_OVERRIDE_* warnings
// break out the different specific situations.
continue;
}
// First check to see if this element was declared in the superclass chain, in which case
// there is already a concrete implementation.
ExecutableElement elt = membersInheritedFromSuperclasses.get(memberName);
// Check to see if an element was found in the superclass chain with the correct name.
if (elt != null) {
// Reference the types, if any are null then continue.
InterfaceType enclosingType = enclosingClass.getType();
FunctionType concreteType = elt.getType();
FunctionType requiredMemberType = executableElt.getType();
if (enclosingType == null || concreteType == null || requiredMemberType == null) {
continue;
}
// Some element was found in the superclass chain that matches the name of the required
// member.
// If it is not abstract and it is the correct one (types match- the version of this method
// that we have has the correct number of parameters, etc), then this class has a valid
// implementation of this method, so skip it.
if ((elt instanceof MethodElement && !((MethodElement) elt).isAbstract())
|| (elt instanceof PropertyAccessorElement && !((PropertyAccessorElement) elt).isAbstract())) {
// Since we are comparing two function types, we need to do the appropriate type
// substitutions first ().
FunctionType foundConcreteFT = inheritanceManager.substituteTypeArgumentsInMemberFromInheritance(
concreteType,
memberName,
enclosingType);
FunctionType requiredMemberFT = inheritanceManager.substituteTypeArgumentsInMemberFromInheritance(
requiredMemberType,
memberName,
enclosingType);
if (foundConcreteFT.isSubtypeOf(requiredMemberFT)) {
continue;
}
}
}
// The not qualifying concrete executable element was found, add it to the list.
missingOverrides.add(executableElt);
}
// Now that we have the set of missing overrides, generate a warning on this class
int missingOverridesSize = missingOverrides.size();
if (missingOverridesSize == 0) {
return false;
}
ExecutableElement[] missingOverridesArray = missingOverrides.toArray(new ExecutableElement[missingOverridesSize]);
ArrayList<String> stringMembersArrayListSet = new ArrayList<String>(
missingOverridesArray.length);
for (int i = 0; i < missingOverridesArray.length; i++) {
String newStrMember;
Element enclosingElement = missingOverridesArray[i].getEnclosingElement();
String prefix = StringUtilities.EMPTY;
if (missingOverridesArray[i] instanceof PropertyAccessorElement) {
PropertyAccessorElement propertyAccessorElement = (PropertyAccessorElement) missingOverridesArray[i];
if (propertyAccessorElement.isGetter()) {
prefix = GETTER_SPACE; // "getter "
} else {
prefix = SETTER_SPACE; // "setter "
}
}
if (enclosingElement != null) {
newStrMember = prefix + "'" + enclosingElement.getDisplayName() + '.'
+ missingOverridesArray[i].getDisplayName() + "'";
} else {
newStrMember = prefix + "'" + missingOverridesArray[i].getDisplayName() + "'";
}
stringMembersArrayListSet.add(newStrMember);
}
String[] stringMembersArray = stringMembersArrayListSet.toArray(new String[stringMembersArrayListSet.size()]);
AnalysisErrorWithProperties analysisError;
if (stringMembersArray.length == 1) {
analysisError = errorReporter.newErrorWithProperties(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_ONE,
classNameNode,
stringMembersArray[0]);
} else if (stringMembersArray.length == 2) {
analysisError = errorReporter.newErrorWithProperties(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_TWO,
classNameNode,
stringMembersArray[0],
stringMembersArray[1]);
} else if (stringMembersArray.length == 3) {
analysisError = errorReporter.newErrorWithProperties(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_THREE,
classNameNode,
stringMembersArray[0],
stringMembersArray[1],
stringMembersArray[2]);
} else if (stringMembersArray.length == 4) {
analysisError = errorReporter.newErrorWithProperties(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FOUR,
classNameNode,
stringMembersArray[0],
stringMembersArray[1],
stringMembersArray[2],
stringMembersArray[3]);
} else {
analysisError = errorReporter.newErrorWithProperties(
StaticWarningCode.NON_ABSTRACT_CLASS_INHERITS_ABSTRACT_MEMBER_FIVE_PLUS,
classNameNode,
stringMembersArray[0],
stringMembersArray[1],
stringMembersArray[2],
stringMembersArray[3],
stringMembersArray.length - 4);
}
analysisError.setProperty(ErrorProperty.UNIMPLEMENTED_METHODS, missingOverridesArray);
errorReporter.reportError(analysisError);
return true;
}
/**
* Checks to ensure that the expressions that need to be of type bool, are. Otherwise an error is
* reported on the expression.
*
* @param condition the conditional expression to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#NON_BOOL_CONDITION
*/
private boolean checkForNonBoolCondition(Expression condition) {
Type conditionType = getStaticType(condition);
if (conditionType != null && !conditionType.isAssignableTo(boolType)) {
errorReporter.reportErrorForNode(StaticTypeWarningCode.NON_BOOL_CONDITION, condition);
return true;
}
return false;
}
/**
* This verifies that the passed assert statement has either a 'bool' or '() -> bool' input.
*
* @param node the assert statement to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#NON_BOOL_EXPRESSION
*/
private boolean checkForNonBoolExpression(AssertStatement node) {
Expression expression = node.getCondition();
Type type = getStaticType(expression);
if (type instanceof InterfaceType) {
if (!type.isAssignableTo(boolType)) {
errorReporter.reportErrorForNode(StaticTypeWarningCode.NON_BOOL_EXPRESSION, expression);
return true;
}
} else if (type instanceof FunctionType) {
FunctionType functionType = (FunctionType) type;
if (functionType.getTypeArguments().length == 0
&& !functionType.getReturnType().isAssignableTo(boolType)) {
errorReporter.reportErrorForNode(StaticTypeWarningCode.NON_BOOL_EXPRESSION, expression);
return true;
}
}
return false;
}
/**
* Checks to ensure that the given expression is assignable to bool.
*
* @param expression the expression expression to test
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#NON_BOOL_NEGATION_EXPRESSION
*/
private boolean checkForNonBoolNegationExpression(Expression expression) {
Type conditionType = getStaticType(expression);
if (conditionType != null && !conditionType.isAssignableTo(boolType)) {
errorReporter.reportErrorForNode(
StaticTypeWarningCode.NON_BOOL_NEGATION_EXPRESSION,
expression);
return true;
}
return false;
}
/**
* This verifies the passed map literal either:
* <ul>
* <li>has {@code const modifier}</li>
* <li>has explicit type arguments</li>
* <li>is not start of the statement</li>
* <ul>
*
* @param node the map literal to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#NON_CONST_MAP_AS_EXPRESSION_STATEMENT
*/
private boolean checkForNonConstMapAsExpressionStatement(MapLiteral node) {
// "const"
if (node.getConstKeyword() != null) {
return false;
}
// has type arguments
if (node.getTypeArguments() != null) {
return false;
}
// prepare statement
Statement statement = node.getAncestor(ExpressionStatement.class);
if (statement == null) {
return false;
}
// OK, statement does not start with map
if (statement.getBeginToken() != node.getBeginToken()) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NON_CONST_MAP_AS_EXPRESSION_STATEMENT,
node);
return true;
}
/**
* This verifies the passed method declaration of operator {@code []=}, has {@code void} return
* type.
*
* @param node the method declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#NON_VOID_RETURN_FOR_OPERATOR
*/
private boolean checkForNonVoidReturnTypeForOperator(MethodDeclaration node) {
// check that []= operator
SimpleIdentifier name = node.getName();
if (!name.getName().equals("[]=")) {
return false;
}
// check return type
TypeName typeName = node.getReturnType();
if (typeName != null) {
Type type = typeName.getType();
if (type != null && !type.isVoid()) {
errorReporter.reportErrorForNode(StaticWarningCode.NON_VOID_RETURN_FOR_OPERATOR, typeName);
}
}
// no warning
return false;
}
/**
* This verifies the passed setter has no return type or the {@code void} return type.
*
* @param typeName the type name to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#NON_VOID_RETURN_FOR_SETTER
*/
private boolean checkForNonVoidReturnTypeForSetter(TypeName typeName) {
if (typeName != null) {
Type type = typeName.getType();
if (type != null && !type.isVoid()) {
errorReporter.reportErrorForNode(StaticWarningCode.NON_VOID_RETURN_FOR_SETTER, typeName);
}
}
return false;
}
/**
* This verifies the passed operator-method declaration, does not have an optional parameter.
* <p>
* This method assumes that the method declaration was tested to be an operator declaration before
* being called.
*
* @param node the method declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#OPTIONAL_PARAMETER_IN_OPERATOR
*/
private boolean checkForOptionalParameterInOperator(MethodDeclaration node) {
FormalParameterList parameterList = node.getParameters();
if (parameterList == null) {
return false;
}
boolean foundError = false;
NodeList<FormalParameter> formalParameters = parameterList.getParameters();
for (FormalParameter formalParameter : formalParameters) {
if (formalParameter.getKind().isOptional()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.OPTIONAL_PARAMETER_IN_OPERATOR,
formalParameter);
foundError = true;
}
}
return foundError;
}
/**
* This checks for named optional parameters that begin with '_'.
*
* @param node the default formal parameter to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#PRIVATE_OPTIONAL_PARAMETER
*/
private boolean checkForPrivateOptionalParameter(FormalParameter node) {
// should be named parameter
if (node.getKind() != ParameterKind.NAMED) {
return false;
}
// name should start with '_'
SimpleIdentifier name = node.getIdentifier();
if (name.isSynthetic() || !StringUtilities.startsWithChar(name.getName(), '_')) {
return false;
}
// report problem
errorReporter.reportErrorForNode(CompileTimeErrorCode.PRIVATE_OPTIONAL_PARAMETER, node);
return true;
}
/**
* This checks if the passed constructor declaration is the redirecting generative constructor and
* references itself directly or indirectly.
*
* @param node the constructor declaration to evaluate
* @param constructorElement the constructor element
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#RECURSIVE_CONSTRUCTOR_REDIRECT
*/
private boolean checkForRecursiveConstructorRedirect(ConstructorDeclaration node,
ConstructorElement constructorElement) {
// we check generative constructor here
if (node.getFactoryKeyword() != null) {
return false;
}
// try to find redirecting constructor invocation and analyzer it for recursion
for (ConstructorInitializer initializer : node.getInitializers()) {
if (initializer instanceof RedirectingConstructorInvocation) {
// OK if no cycle
if (!hasRedirectingFactoryConstructorCycle(constructorElement)) {
return false;
}
// report error
errorReporter.reportErrorForNode(
CompileTimeErrorCode.RECURSIVE_CONSTRUCTOR_REDIRECT,
initializer);
return true;
}
}
// OK, no redirecting constructor invocation
return false;
}
/**
* This checks if the passed constructor declaration has redirected constructor and references
* itself directly or indirectly.
*
* @param node the constructor declaration to evaluate
* @param constructorElement the constructor element
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#RECURSIVE_FACTORY_REDIRECT
*/
private boolean checkForRecursiveFactoryRedirect(ConstructorDeclaration node,
ConstructorElement constructorElement) {
// prepare redirected constructor
ConstructorName redirectedConstructorNode = node.getRedirectedConstructor();
if (redirectedConstructorNode == null) {
return false;
}
// OK if no cycle
if (!hasRedirectingFactoryConstructorCycle(constructorElement)) {
return false;
}
// report error
errorReporter.reportErrorForNode(
CompileTimeErrorCode.RECURSIVE_FACTORY_REDIRECT,
redirectedConstructorNode);
return true;
}
/**
* This checks the class declaration is not a superinterface to itself.
*
* @param classElt the class element to test
* @return {@code true} if and only if an error code is generated on the passed element
* @see CompileTimeErrorCode#RECURSIVE_INTERFACE_INHERITANCE
* @see CompileTimeErrorCode#RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_EXTENDS
* @see CompileTimeErrorCode#RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_IMPLEMENTS
*/
private boolean checkForRecursiveInterfaceInheritance(ClassElement classElt) {
if (classElt == null) {
return false;
}
return safeCheckForRecursiveInterfaceInheritance(classElt, new ArrayList<ClassElement>());
}
/**
* This checks the passed constructor declaration has a valid combination of redirected
* constructor invocation(s), super constructor invocations and field initializers.
*
* @param node the constructor declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#DEFAULT_VALUE_IN_REDIRECTING_FACTORY_CONSTRUCTOR
* @see CompileTimeErrorCode#FIELD_INITIALIZER_REDIRECTING_CONSTRUCTOR
* @see CompileTimeErrorCode#MULTIPLE_REDIRECTING_CONSTRUCTOR_INVOCATIONS
* @see CompileTimeErrorCode#SUPER_IN_REDIRECTING_CONSTRUCTOR
* @see CompileTimeErrorCode#REDIRECT_GENERATIVE_TO_NON_GENERATIVE_CONSTRUCTOR
*/
private boolean checkForRedirectingConstructorErrorCodes(ConstructorDeclaration node) {
boolean errorReported = false;
//
// Check for default values in the parameters
//
ConstructorName redirectedConstructor = node.getRedirectedConstructor();
if (redirectedConstructor != null) {
for (FormalParameter parameter : node.getParameters().getParameters()) {
if (parameter instanceof DefaultFormalParameter
&& ((DefaultFormalParameter) parameter).getDefaultValue() != null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.DEFAULT_VALUE_IN_REDIRECTING_FACTORY_CONSTRUCTOR,
parameter.getIdentifier());
errorReported = true;
}
}
}
// check if there are redirected invocations
int numRedirections = 0;
for (ConstructorInitializer initializer : node.getInitializers()) {
if (initializer instanceof RedirectingConstructorInvocation) {
if (numRedirections > 0) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.MULTIPLE_REDIRECTING_CONSTRUCTOR_INVOCATIONS,
initializer);
errorReported = true;
}
if (node.getFactoryKeyword() == null) {
RedirectingConstructorInvocation invocation = (RedirectingConstructorInvocation) initializer;
ConstructorElement redirectingElement = invocation.getStaticElement();
if (redirectingElement == null) {
String enclosingTypeName = enclosingClass.getDisplayName();
String constructorStrName = enclosingTypeName;
if (invocation.getConstructorName() != null) {
constructorStrName += '.' + invocation.getConstructorName().getName();
}
errorReporter.reportErrorForNode(
CompileTimeErrorCode.REDIRECT_GENERATIVE_TO_MISSING_CONSTRUCTOR,
invocation,
constructorStrName,
enclosingTypeName);
} else {
if (redirectingElement.isFactory()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.REDIRECT_GENERATIVE_TO_NON_GENERATIVE_CONSTRUCTOR,
initializer);
}
}
}
numRedirections++;
}
}
// check for other initializers
if (numRedirections > 0) {
for (ConstructorInitializer initializer : node.getInitializers()) {
if (initializer instanceof SuperConstructorInvocation) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.SUPER_IN_REDIRECTING_CONSTRUCTOR,
initializer);
errorReported = true;
}
if (initializer instanceof ConstructorFieldInitializer) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.FIELD_INITIALIZER_REDIRECTING_CONSTRUCTOR,
initializer);
errorReported = true;
}
}
}
// done
return errorReported;
}
/**
* This checks if the passed constructor declaration has redirected constructor and references
* itself directly or indirectly.
*
* @param node the constructor declaration to evaluate
* @param constructorElement the constructor element
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#REDIRECT_TO_NON_CONST_CONSTRUCTOR
*/
private boolean checkForRedirectToNonConstConstructor(ConstructorDeclaration node,
ConstructorElement constructorElement) {
// prepare redirected constructor
ConstructorName redirectedConstructorNode = node.getRedirectedConstructor();
if (redirectedConstructorNode == null) {
return false;
}
// prepare element
if (constructorElement == null) {
return false;
}
// OK, it is not 'const'
if (!constructorElement.isConst()) {
return false;
}
// prepare redirected constructor
ConstructorElement redirectedConstructor = constructorElement.getRedirectedConstructor();
if (redirectedConstructor == null) {
return false;
}
// OK, it is also 'const'
if (redirectedConstructor.isConst()) {
return false;
}
// report error
errorReporter.reportErrorForNode(
CompileTimeErrorCode.REDIRECT_TO_NON_CONST_CONSTRUCTOR,
redirectedConstructorNode);
return true;
}
/**
* This checks that the rethrow is inside of a catch clause.
*
* @param node the rethrow expression to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#RETHROW_OUTSIDE_CATCH
*/
private boolean checkForRethrowOutsideCatch(RethrowExpression node) {
if (!isInCatchClause) {
errorReporter.reportErrorForNode(CompileTimeErrorCode.RETHROW_OUTSIDE_CATCH, node);
return true;
}
return false;
}
/**
* This checks that if the the given constructor declaration is generative, then it does not have
* an expression function body.
*
* @param node the constructor to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#RETURN_IN_GENERATIVE_CONSTRUCTOR
*/
private boolean checkForReturnInGenerativeConstructor(ConstructorDeclaration node) {
// ignore factory
if (node.getFactoryKeyword() != null) {
return false;
}
// block body (with possible return statement) is checked elsewhere
FunctionBody body = node.getBody();
if (!(body instanceof ExpressionFunctionBody)) {
return false;
}
// report error
errorReporter.reportErrorForNode(CompileTimeErrorCode.RETURN_IN_GENERATIVE_CONSTRUCTOR, body);
return true;
}
/**
* This checks that a type mis-match between the return type and the expressed return type by the
* enclosing method or function.
* <p>
* This method is called both by {@link #checkForAllReturnStatementErrorCodes(ReturnStatement)}
* and {@link #visitExpressionFunctionBody(ExpressionFunctionBody)}.
*
* @param returnExpression the returned expression to evaluate
* @param expectedReturnType the expressed return type by the enclosing method or function
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#RETURN_OF_INVALID_TYPE
*/
private boolean checkForReturnOfInvalidType(Expression returnExpression, Type expectedReturnType) {
if (enclosingFunction == null) {
return false;
}
Type staticReturnType = getStaticType(returnExpression);
if (expectedReturnType.isVoid()) {
if (staticReturnType.isVoid() || staticReturnType.isDynamic() || staticReturnType.isBottom()) {
return false;
}
errorReporter.reportTypeErrorForNode(
StaticTypeWarningCode.RETURN_OF_INVALID_TYPE,
returnExpression,
staticReturnType,
expectedReturnType,
enclosingFunction.getDisplayName());
return true;
}
if (enclosingFunction.isAsynchronous() && !enclosingFunction.isGenerator()) {
// TODO(brianwilkerson) Figure out how to get the type "Future" so that we can build the type
// we need to test against.
// InterfaceType impliedType = "Future<" + flatten(staticReturnType) + ">"
// if (impliedType.isAssignableTo(expectedReturnType)) {
// return false;
// }
// errorReporter.reportTypeErrorForNode(
// StaticTypeWarningCode.RETURN_OF_INVALID_TYPE,
// returnExpression,
// impliedType,
// expectedReturnType.getDisplayName(),
// enclosingFunction.getDisplayName());
// return true;
return false;
}
if (staticReturnType.isAssignableTo(expectedReturnType)) {
return false;
}
errorReporter.reportTypeErrorForNode(
StaticTypeWarningCode.RETURN_OF_INVALID_TYPE,
returnExpression,
staticReturnType,
expectedReturnType,
enclosingFunction.getDisplayName());
return true;
// TODO(brianwilkerson) Define a hint corresponding to the warning and report it if appropriate.
// Type propagatedReturnType = returnExpression.getPropagatedType();
// boolean isPropagatedAssignable = propagatedReturnType.isAssignableTo(expectedReturnType);
// if (isStaticAssignable || isPropagatedAssignable) {
// return false;
// }
// errorReporter.reportTypeErrorForNode(
// StaticTypeWarningCode.RETURN_OF_INVALID_TYPE,
// returnExpression,
// staticReturnType,
// expectedReturnType,
// enclosingFunction.getDisplayName());
// return true;
}
/**
* This checks the given "typeReference" and that the "name" is not the reference to an instance
* member.
*
* @param typeReference the resolved {@link ClassElement} of the left hand side of the expression,
* or {@code null}, aka, the class element of 'C' in 'C.x', see
* {@link #getTypeReference(Expression)}
* @param name the accessed name to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#STATIC_ACCESS_TO_INSTANCE_MEMBER
*/
private boolean checkForStaticAccessToInstanceMember(ClassElement typeReference,
SimpleIdentifier name) {
// OK, target is not a type
if (typeReference == null) {
return false;
}
// prepare member Element
Element element = name.getStaticElement();
if (!(element instanceof ExecutableElement)) {
return false;
}
ExecutableElement memberElement = (ExecutableElement) element;
// OK, static
if (memberElement.isStatic()) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
StaticWarningCode.STATIC_ACCESS_TO_INSTANCE_MEMBER,
name,
name.getName());
return true;
}
/**
* This checks that the type of the passed 'switch' expression is assignable to the type of the
* 'case' members.
*
* @param node the 'switch' statement to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#SWITCH_EXPRESSION_NOT_ASSIGNABLE
*/
private boolean checkForSwitchExpressionNotAssignable(SwitchStatement node) {
// prepare 'switch' expression type
Expression expression = node.getExpression();
Type expressionType = getStaticType(expression);
if (expressionType == null) {
return false;
}
// compare with type of the first 'case'
NodeList<SwitchMember> members = node.getMembers();
for (SwitchMember switchMember : members) {
if (!(switchMember instanceof SwitchCase)) {
continue;
}
SwitchCase switchCase = (SwitchCase) switchMember;
// prepare 'case' type
Expression caseExpression = switchCase.getExpression();
Type caseType = getStaticType(caseExpression);
// check types
if (expressionType.isAssignableTo(caseType)) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
StaticWarningCode.SWITCH_EXPRESSION_NOT_ASSIGNABLE,
expression,
expressionType,
caseType);
return true;
}
return false;
}
/**
* This verifies that the passed function type alias does not reference itself directly.
*
* @param node the function type alias to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#TYPE_ALIAS_CANNOT_REFERENCE_ITSELF
*/
private boolean checkForTypeAliasCannotReferenceItself_function(FunctionTypeAlias node) {
FunctionTypeAliasElement element = node.getElement();
if (!hasTypedefSelfReference(element)) {
return false;
}
errorReporter.reportErrorForNode(CompileTimeErrorCode.TYPE_ALIAS_CANNOT_REFERENCE_ITSELF, node);
return true;
}
/**
* This verifies that the passed type name is not a deferred type.
*
* @param expression the expression to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#TYPE_ANNOTATION_DEFERRED_CLASS
*/
private boolean checkForTypeAnnotationDeferredClass(TypeName node) {
if (node != null && node.isDeferred()) {
errorReporter.reportErrorForNode(
StaticWarningCode.TYPE_ANNOTATION_DEFERRED_CLASS,
node,
node.getName());
}
return false;
}
/**
* This verifies that the type arguments in the passed type name are all within their bounds.
*
* @param node the {@link TypeName} to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#TYPE_ARGUMENT_NOT_MATCHING_BOUNDS
*/
private boolean checkForTypeArgumentNotMatchingBounds(TypeName node) {
if (node.getTypeArguments() == null) {
return false;
}
// prepare Type
Type type = node.getType();
if (type == null) {
return false;
}
// prepare ClassElement
Element element = type.getElement();
if (!(element instanceof ClassElement)) {
return false;
}
ClassElement classElement = (ClassElement) element;
// prepare type parameters
Type[] typeParameters = classElement.getType().getTypeArguments();
TypeParameterElement[] boundingElts = classElement.getTypeParameters();
// iterate over each bounded type parameter and corresponding argument
NodeList<TypeName> typeNameArgList = node.getTypeArguments().getArguments();
Type[] typeArguments = ((InterfaceType) type).getTypeArguments();
int loopThroughIndex = Math.min(typeNameArgList.size(), boundingElts.length);
boolean foundError = false;
for (int i = 0; i < loopThroughIndex; i++) {
TypeName argTypeName = typeNameArgList.get(i);
Type argType = argTypeName.getType();
Type boundType = boundingElts[i].getBound();
if (argType != null && boundType != null) {
if (typeArguments.length != 0 && typeArguments.length == typeParameters.length) {
boundType = boundType.substitute(typeArguments, typeParameters);
}
if (!argType.isSubtypeOf(boundType)) {
ErrorCode errorCode;
if (isInConstInstanceCreation) {
errorCode = CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS;
} else {
errorCode = StaticTypeWarningCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS;
}
errorReporter.reportTypeErrorForNode(errorCode, argTypeName, argType, boundType);
foundError = true;
}
}
}
return foundError;
}
/**
* This checks that if the passed type name is a type parameter being used to define a static
* member.
*
* @param node the type name to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#TYPE_PARAMETER_REFERENCED_BY_STATIC
*/
private boolean checkForTypeParameterReferencedByStatic(TypeName node) {
if (isInStaticMethod || isInStaticVariableDeclaration) {
Type type = node.getType();
if (type instanceof TypeParameterType) {
errorReporter.reportErrorForNode(
StaticWarningCode.TYPE_PARAMETER_REFERENCED_BY_STATIC,
node);
return true;
}
}
return false;
}
/**
* This checks that if the passed type parameter is a supertype of its bound.
*
* @param node the type parameter to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#TYPE_PARAMETER_SUPERTYPE_OF_ITS_BOUND
*/
private boolean checkForTypeParameterSupertypeOfItsBound(TypeParameter node) {
TypeParameterElement element = node.getElement();
// prepare bound
Type bound = element.getBound();
if (bound == null) {
return false;
}
// OK, type parameter is not supertype of its bound
if (!bound.isMoreSpecificThan(element.getType())) {
return false;
}
// report problem
errorReporter.reportErrorForNode(
StaticTypeWarningCode.TYPE_PARAMETER_SUPERTYPE_OF_ITS_BOUND,
node,
element.getDisplayName());
return true;
}
/**
* This checks that if the passed generative constructor has neither an explicit super constructor
* invocation nor a redirecting constructor invocation, that the superclass has a default
* generative constructor.
*
* @param node the constructor declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT
* @see CompileTimeErrorCode#NON_GENERATIVE_CONSTRUCTOR
* @see StaticWarningCode#NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT
*/
private boolean checkForUndefinedConstructorInInitializerImplicit(ConstructorDeclaration node) {
//
// Ignore if the constructor is not generative.
//
if (node.getFactoryKeyword() != null) {
return false;
}
//
// Ignore if the constructor has either an implicit super constructor invocation or a
// redirecting constructor invocation.
//
for (ConstructorInitializer constructorInitializer : node.getInitializers()) {
if (constructorInitializer instanceof SuperConstructorInvocation
|| constructorInitializer instanceof RedirectingConstructorInvocation) {
return false;
}
}
//
// Check to see whether the superclass has a non-factory unnamed constructor.
//
if (enclosingClass == null) {
return false;
}
InterfaceType superType = enclosingClass.getSupertype();
if (superType == null) {
return false;
}
ClassElement superElement = superType.getElement();
ConstructorElement superUnnamedConstructor = superElement.getUnnamedConstructor();
if (superUnnamedConstructor != null) {
if (superUnnamedConstructor.isFactory()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.NON_GENERATIVE_CONSTRUCTOR,
node.getReturnType(),
superUnnamedConstructor);
return true;
}
if (!superUnnamedConstructor.isDefaultConstructor()) {
int offset;
int length;
{
Identifier returnType = node.getReturnType();
SimpleIdentifier name = node.getName();
offset = returnType.getOffset();
length = (name != null ? name.getEnd() : returnType.getEnd()) - offset;
}
errorReporter.reportErrorForOffset(
CompileTimeErrorCode.NO_DEFAULT_SUPER_CONSTRUCTOR_EXPLICIT,
offset,
length,
superType.getDisplayName());
}
return false;
}
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_CONSTRUCTOR_IN_INITIALIZER_DEFAULT,
node.getReturnType(),
superElement.getName());
return true;
}
/**
* This checks that if the given name is a reference to a static member it is defined in the
* enclosing class rather than in a superclass.
*
* @param name the name to be evaluated
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticTypeWarningCode#UNQUALIFIED_REFERENCE_TO_NON_LOCAL_STATIC_MEMBER
*/
private boolean checkForUnqualifiedReferenceToNonLocalStaticMember(SimpleIdentifier name) {
Element element = name.getStaticElement();
if (element == null || element instanceof TypeParameterElement) {
return false;
}
Element enclosingElement = element.getEnclosingElement();
if (!(enclosingElement instanceof ClassElement)) {
return false;
}
if ((element instanceof MethodElement && !((MethodElement) element).isStatic())
|| (element instanceof PropertyAccessorElement && !((PropertyAccessorElement) element).isStatic())) {
return false;
}
if (enclosingElement == enclosingClass) {
return false;
}
errorReporter.reportErrorForNode(
StaticTypeWarningCode.UNQUALIFIED_REFERENCE_TO_NON_LOCAL_STATIC_MEMBER,
name,
name.getName());
return true;
}
private void checkForValidField(FieldFormalParameter node) {
ParameterElement element = node.getElement();
if (element instanceof FieldFormalParameterElement) {
FieldElement fieldElement = ((FieldFormalParameterElement) element).getField();
if (fieldElement == null || fieldElement.isSynthetic()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTANT_FIELD,
node,
node.getIdentifier().getName());
} else {
ParameterElement parameterElement = node.getElement();
if (parameterElement instanceof FieldFormalParameterElementImpl) {
FieldFormalParameterElementImpl fieldFormal = (FieldFormalParameterElementImpl) parameterElement;
Type declaredType = fieldFormal.getType();
Type fieldType = fieldElement.getType();
if (fieldElement.isSynthetic()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTANT_FIELD,
node,
node.getIdentifier().getName());
} else if (fieldElement.isStatic()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_STATIC_FIELD,
node,
node.getIdentifier().getName());
} else if (declaredType != null && fieldType != null
&& !declaredType.isAssignableTo(fieldType)) {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.FIELD_INITIALIZING_FORMAL_NOT_ASSIGNABLE,
node,
declaredType,
fieldType);
}
} else {
if (fieldElement.isSynthetic()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_NON_EXISTANT_FIELD,
node,
node.getIdentifier().getName());
} else if (fieldElement.isStatic()) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.INITIALIZING_FORMAL_FOR_STATIC_FIELD,
node,
node.getIdentifier().getName());
}
}
}
}
// else {
// // TODO(jwren) Report error, constructor initializer variable is a top level element
// // (Either here or in ErrorVerifier#checkForAllFinalInitializedErrorCodes)
// }
}
/**
* This verifies that the given getter does not have a return type of 'void'.
*
* @param node the method declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#VOID_RETURN_FOR_GETTER
*/
private boolean checkForVoidReturnType(MethodDeclaration node) {
TypeName returnType = node.getReturnType();
if (returnType == null || !returnType.getName().getName().equals("void")) {
return false;
}
errorReporter.reportErrorForNode(StaticWarningCode.VOID_RETURN_FOR_GETTER, returnType);
return true;
}
/**
* This verifies the passed operator-method declaration, has correct number of parameters.
* <p>
* This method assumes that the method declaration was tested to be an operator declaration before
* being called.
*
* @param node the method declaration to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#WRONG_NUMBER_OF_PARAMETERS_FOR_OPERATOR
*/
private boolean checkForWrongNumberOfParametersForOperator(MethodDeclaration node) {
// prepare number of parameters
FormalParameterList parameterList = node.getParameters();
if (parameterList == null) {
return false;
}
int numParameters = parameterList.getParameters().size();
// prepare operator name
SimpleIdentifier nameNode = node.getName();
if (nameNode == null) {
return false;
}
String name = nameNode.getName();
// check for exact number of parameters
int expected = -1;
if ("[]=".equals(name)) {
expected = 2;
} else if ("<".equals(name) || ">".equals(name) || "<=".equals(name) || ">=".equals(name)
|| "==".equals(name) || "+".equals(name) || "/".equals(name) || "~/".equals(name)
|| "*".equals(name) || "%".equals(name) || "|".equals(name) || "^".equals(name)
|| "&".equals(name) || "<<".equals(name) || ">>".equals(name) || "[]".equals(name)) {
expected = 1;
} else if ("~".equals(name)) {
expected = 0;
}
if (expected != -1 && numParameters != expected) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.WRONG_NUMBER_OF_PARAMETERS_FOR_OPERATOR,
nameNode,
name,
expected,
numParameters);
return true;
}
// check for operator "-"
if ("-".equals(name) && numParameters > 1) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.WRONG_NUMBER_OF_PARAMETERS_FOR_OPERATOR_MINUS,
nameNode,
numParameters);
return true;
}
// OK
return false;
}
/**
* This verifies if the passed setter parameter list have only one required parameter.
* <p>
* This method assumes that the method declaration was tested to be a setter before being called.
*
* @param setterName the name of the setter to report problems on
* @param parameterList the parameter list to evaluate
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#WRONG_NUMBER_OF_PARAMETERS_FOR_SETTER
*/
private boolean checkForWrongNumberOfParametersForSetter(SimpleIdentifier setterName,
FormalParameterList parameterList) {
if (setterName == null) {
return false;
}
if (parameterList == null) {
return false;
}
NodeList<FormalParameter> parameters = parameterList.getParameters();
if (parameters.size() != 1 || parameters.get(0).getKind() != ParameterKind.REQUIRED) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.WRONG_NUMBER_OF_PARAMETERS_FOR_SETTER,
setterName);
return true;
}
return false;
}
/**
* This verifies that if the given class declaration implements the class Function that it has a
* concrete implementation of the call method.
*
* @return {@code true} if and only if an error code is generated on the passed node
* @see StaticWarningCode#FUNCTION_WITHOUT_CALL
*/
private boolean checkImplementsFunctionWithoutCall(ClassDeclaration node) {
if (node.isAbstract()) {
return false;
}
ClassElement classElement = node.getElement();
if (classElement == null) {
return false;
}
if (!classElement.getType().isSubtypeOf(typeProvider.getFunctionType())) {
return false;
}
// If there is a noSuchMethod method, then don't report the warning, see dartbug.com/16078
if (classElement.getMethod(FunctionElement.NO_SUCH_METHOD_METHOD_NAME) != null) {
return false;
}
ExecutableElement callMethod = inheritanceManager.lookupMember(classElement, "call");
if (callMethod == null || !(callMethod instanceof MethodElement)
|| ((MethodElement) callMethod).isAbstract()) {
errorReporter.reportErrorForNode(StaticWarningCode.FUNCTION_WITHOUT_CALL, node.getName());
return true;
}
return false;
}
/**
* This verifies that the given class declaration does not have the same class in the 'extends'
* and 'implements' clauses.
*
* @return {@code true} if and only if an error code is generated on the passed node
* @see CompileTimeErrorCode#IMPLEMENTS_SUPER_CLASS
*/
private boolean checkImplementsSuperClass(ClassDeclaration node) {
// prepare super type
InterfaceType superType = enclosingClass.getSupertype();
if (superType == null) {
return false;
}
// prepare interfaces
ImplementsClause implementsClause = node.getImplementsClause();
if (implementsClause == null) {
return false;
}
// check interfaces
boolean hasProblem = false;
for (TypeName interfaceNode : implementsClause.getInterfaces()) {
if (interfaceNode.getType().equals(superType)) {
hasProblem = true;
errorReporter.reportErrorForNode(
CompileTimeErrorCode.IMPLEMENTS_SUPER_CLASS,
interfaceNode,
superType.getDisplayName());
}
}
// done
return hasProblem;
}
/**
* Return the flattened version of the given type, as defined by the specification: <blockquote>
* Let <i>flatten(T) = flatten(S)</i> if <i>T = Future<S></i>, and <i>T</i> otherwise.
* </blockquote>
*
* @param type the type to be flattened
* @return the flattened version of the given type
*/
private Type flatten(Type type) {
while (isFuture(type)) {
Type[] arguments = ((InterfaceType) type).getTypeArguments();
if (arguments.length != 1) {
return type;
}
type = arguments[0];
}
return type;
}
/**
* Return the error code that should be used when the given class references itself directly.
*
* @param classElt the class that references itself
* @return the error code that should be used
*/
private ErrorCode getBaseCaseErrorCode(ClassElement classElt) {
InterfaceType supertype = classElt.getSupertype();
if (supertype != null && enclosingClass.equals(supertype.getElement())) {
return CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_EXTENDS;
}
InterfaceType[] mixins = classElt.getMixins();
for (int i = 0; i < mixins.length; i++) {
if (enclosingClass.equals(mixins[i].getElement())) {
return CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_WITH;
}
}
return CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_IMPLEMENTS;
}
/**
* Given an expression in a switch case whose value is expected to be an enum constant, return the
* name of the constant.
*
* @param expression the expression from the switch case
* @return the name of the constant referenced by the expression
*/
private String getConstantName(Expression expression) {
// TODO(brianwilkerson) Convert this to return the element representing the constant.
if (expression instanceof SimpleIdentifier) {
return ((SimpleIdentifier) expression).getName();
} else if (expression instanceof PrefixedIdentifier) {
return ((PrefixedIdentifier) expression).getIdentifier().getName();
} else if (expression instanceof PropertyAccess) {
return ((PropertyAccess) expression).getPropertyName().getName();
}
return null;
}
/**
* Returns the Type (return type) for a given getter.
*
* @param propertyAccessorElement
* @return The type of the given getter.
*/
private Type getGetterType(PropertyAccessorElement propertyAccessorElement) {
FunctionType functionType = propertyAccessorElement.getType();
if (functionType != null) {
return functionType.getReturnType();
} else {
return null;
}
}
/**
* Returns the Type (first and only parameter) for a given setter.
*
* @param propertyAccessorElement
* @return The type of the given setter.
*/
private Type getSetterType(PropertyAccessorElement propertyAccessorElement) {
// Get the parameters for MethodDeclaration or FunctionDeclaration
ParameterElement[] setterParameters = propertyAccessorElement.getParameters();
// If there are no setter parameters, return no type.
if (setterParameters.length == 0) {
return null;
}
return setterParameters[0].getType();
}
/**
* Given a list of directives that have the same prefix, generate an error if there is more than
* one import and any of those imports is deferred.
*
* @param directives the list of directives that have the same prefix
* @return {@code true} if an error was generated
* @see CompileTimeErrorCode#SHARED_DEFERRED_PREFIX
*/
private boolean hasDeferredPrefixCollision(ArrayList<ImportDirective> directives) {
boolean foundError = false;
int count = directives.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
Token deferredToken = directives.get(i).getDeferredToken();
if (deferredToken != null) {
errorReporter.reportErrorForToken(
CompileTimeErrorCode.SHARED_DEFERRED_PREFIX,
deferredToken);
foundError = true;
}
}
}
return foundError;
}
/**
* @return {@code true} if the given constructor redirects to itself, directly or indirectly
*/
private boolean hasRedirectingFactoryConstructorCycle(ConstructorElement element) {
Set<ConstructorElement> constructors = new HashSet<ConstructorElement>();
ConstructorElement current = element;
while (current != null) {
if (constructors.contains(current)) {
return current == element;
}
constructors.add(current);
current = current.getRedirectedConstructor();
if (current instanceof ConstructorMember) {
current = ((ConstructorMember) current).getBaseElement();
}
}
return false;
}
/**
* @return <code>true</code> if given {@link Element} has direct or indirect reference to itself
* from anywhere except {@link ClassElement} or type parameter bounds.
*/
private boolean hasTypedefSelfReference(final Element target) {
final Set<Element> checked = new HashSet<Element>();
final List<Element> toCheck = new ArrayList<Element>();
toCheck.add(target);
boolean firstIteration = true;
while (true) {
Element current;
// get next element
while (true) {
// may be no more elements to check
if (toCheck.isEmpty()) {
return false;
}
// try to get next element
current = toCheck.remove(toCheck.size() - 1);
if (target.equals(current)) {
if (firstIteration) {
firstIteration = false;
break;
} else {
return true;
}
}
if (current != null && !checked.contains(current)) {
break;
}
}
// check current element
current.accept(new GeneralizingElementVisitor<Void>() {
private boolean inClass;
@Override
public Void visitClassElement(ClassElement element) {
addTypeToCheck(element.getSupertype());
for (InterfaceType mixin : element.getMixins()) {
addTypeToCheck(mixin);
}
inClass = !element.isTypedef();
try {
return super.visitClassElement(element);
} finally {
inClass = false;
}
}
@Override
public Void visitExecutableElement(ExecutableElement element) {
if (element.isSynthetic()) {
return null;
}
addTypeToCheck(element.getReturnType());
return super.visitExecutableElement(element);
}
@Override
public Void visitFunctionTypeAliasElement(FunctionTypeAliasElement element) {
addTypeToCheck(element.getReturnType());
return super.visitFunctionTypeAliasElement(element);
}
@Override
public Void visitParameterElement(ParameterElement element) {
addTypeToCheck(element.getType());
return super.visitParameterElement(element);
}
@Override
public Void visitTypeParameterElement(TypeParameterElement element) {
addTypeToCheck(element.getBound());
return super.visitTypeParameterElement(element);
}
@Override
public Void visitVariableElement(VariableElement element) {
addTypeToCheck(element.getType());
return super.visitVariableElement(element);
}
private void addTypeToCheck(Type type) {
if (type == null) {
return;
}
Element element = type.getElement();
// it is OK to reference target from class
if (inClass && target.equals(element)) {
return;
}
// schedule for checking
toCheck.add(element);
// type arguments
if (type instanceof InterfaceType) {
InterfaceType interfaceType = (InterfaceType) type;
for (Type typeArgument : interfaceType.getTypeArguments()) {
addTypeToCheck(typeArgument);
}
}
}
});
checked.add(current);
}
}
private boolean isFunctionType(Type type) {
if (type.isDynamic() || type.isBottom()) {
return true;
} else if (type instanceof FunctionType || type.isDartCoreFunction()) {
return true;
} else if (type instanceof InterfaceType) {
MethodElement callMethod = ((InterfaceType) type).lookUpMethod(
FunctionElement.CALL_METHOD_NAME,
currentLibrary);
return callMethod != null;
}
return false;
}
/**
* Return {@code true} if the given type represents the class {@code Future} from the
* {@code dart:async} library.
*
* @param type the type to be tested
* @return {@code true} if the given type represents the class {@code Future} from the
* {@code dart:async} library
*/
private boolean isFuture(Type type) {
if (type instanceof InterfaceType) {
InterfaceType interfaceType = (InterfaceType) type;
if (interfaceType.getName().equals("Future")) {
ClassElement element = interfaceType.getElement();
if (element != null) {
LibraryElement library = element.getLibrary();
if (library.getName().equals("dart.async")) {
return true;
}
}
}
}
return false;
}
/**
* Return {@code true} iff the passed {@link ClassElement} has a method, getter or setter that
* matches the name of the passed {@link ExecutableElement} in either the class itself, or one of
* its' mixins that is concrete.
* <p>
* By "match", only the name of the member is tested to match, it does not have to equal or be a
* subtype of the passed executable element, this is due to the specific use where this method is
* used in {@link #checkForNonAbstractClassInheritsAbstractMember(ClassDeclaration)}.
*
* @param executableElt the executable to search for in the passed class element
* @param classElt the class method to search through the members of
* @return {@code true} iff the passed member is found in the passed class element
*/
private boolean isMemberInClassOrMixin(ExecutableElement executableElt, ClassElement classElt) {
ExecutableElement foundElt = null;
String executableName = executableElt.getName();
if (executableElt instanceof MethodElement) {
foundElt = classElt.getMethod(executableName);
if (foundElt != null && !((MethodElement) foundElt).isAbstract()) {
return true;
}
InterfaceType[] mixins = classElt.getMixins();
for (int i = 0; i < mixins.length && foundElt == null; i++) {
foundElt = mixins[i].getMethod(executableName);
}
if (foundElt != null && !((MethodElement) foundElt).isAbstract()) {
return true;
}
} else if (executableElt instanceof PropertyAccessorElement) {
PropertyAccessorElement propertyAccessorElement = (PropertyAccessorElement) executableElt;
if (propertyAccessorElement.isGetter()) {
foundElt = classElt.getGetter(executableName);
}
if (foundElt == null && propertyAccessorElement.isSetter()) {
foundElt = classElt.getSetter(executableName);
}
if (foundElt != null && !((PropertyAccessorElement) foundElt).isAbstract()) {
return true;
}
InterfaceType[] mixins = classElt.getMixins();
for (int i = 0; i < mixins.length && foundElt == null; i++) {
foundElt = mixins[i].getGetter(executableName);
if (foundElt == null) {
foundElt = mixins[i].getSetter(executableName);
}
}
if (foundElt != null && !((PropertyAccessorElement) foundElt).isAbstract()) {
return true;
}
}
return false;
}
/**
* @param node the 'this' expression to analyze
* @return {@code true} if the given 'this' expression is in the valid context
*/
private boolean isThisInValidContext(ThisExpression node) {
for (AstNode n = node; n != null; n = n.getParent()) {
if (n instanceof CompilationUnit) {
return false;
}
if (n instanceof ConstructorDeclaration) {
ConstructorDeclaration constructor = (ConstructorDeclaration) n;
return constructor.getFactoryKeyword() == null;
}
if (n instanceof ConstructorInitializer) {
return false;
}
if (n instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) n;
return !method.isStatic();
}
}
return false;
}
/**
* Return {@code true} if the given identifier is in a location where it is allowed to resolve to
* a static member of a supertype.
*
* @param node the node being tested
* @return {@code true} if the given identifier is in a location where it is allowed to resolve to
* a static member of a supertype
*/
private boolean isUnqualifiedReferenceToNonLocalStaticMemberAllowed(SimpleIdentifier node) {
if (node.inDeclarationContext()) {
return true;
}
AstNode parent = node.getParent();
if (parent instanceof ConstructorName || parent instanceof MethodInvocation
|| parent instanceof PropertyAccess || parent instanceof SuperConstructorInvocation) {
return true;
}
if (parent instanceof PrefixedIdentifier
&& ((PrefixedIdentifier) parent).getIdentifier() == node) {
return true;
}
if (parent instanceof Annotation && ((Annotation) parent).getConstructorName() == node) {
return true;
}
if (parent instanceof CommentReference) {
CommentReference commentReference = (CommentReference) parent;
if (commentReference.getNewKeyword() != null) {
return true;
}
}
return false;
}
//
// /**
// * Return {@code true} iff the passed {@link ClassElement} has a concrete implementation of the
// * passed accessor name in the superclass chain.
// */
// private boolean memberHasConcreteAccessorImplementationInSuperclassChain(
// ClassElement classElement, String accessorName, ArrayList<ClassElement> superclassChain) {
// if (superclassChain.contains(classElement)) {
// return false;
// } else {
// superclassChain.add(classElement);
// }
// for (PropertyAccessorElement accessor : classElement.getAccessors()) {
// if (accessor.getName().equals(accessorName)) {
// if (!accessor.isAbstract()) {
// return true;
// }
// }
// }
// for (InterfaceType mixinType : classElement.getMixins()) {
// if (mixinType != null) {
// ClassElement mixinElement = mixinType.getElement();
// if (mixinElement != null) {
// for (PropertyAccessorElement accessor : mixinElement.getAccessors()) {
// if (accessor.getName().equals(accessorName)) {
// if (!accessor.isAbstract()) {
// return true;
// }
// }
// }
// }
// }
// }
// InterfaceType superType = classElement.getSupertype();
// if (superType != null) {
// ClassElement superClassElt = superType.getElement();
// if (superClassElt != null) {
// return memberHasConcreteAccessorImplementationInSuperclassChain(
// superClassElt,
// accessorName,
// superclassChain);
// }
// }
// return false;
// }
//
// /**
// * Return {@code true} iff the passed {@link ClassElement} has a concrete implementation of the
// * passed method name in the superclass chain.
// */
// private boolean memberHasConcreteMethodImplementationInSuperclassChain(ClassElement classElement,
// String methodName, ArrayList<ClassElement> superclassChain) {
// if (superclassChain.contains(classElement)) {
// return false;
// } else {
// superclassChain.add(classElement);
// }
// for (MethodElement method : classElement.getMethods()) {
// if (method.getName().equals(methodName)) {
// if (!method.isAbstract()) {
// return true;
// }
// }
// }
// for (InterfaceType mixinType : classElement.getMixins()) {
// if (mixinType != null) {
// ClassElement mixinElement = mixinType.getElement();
// if (mixinElement != null) {
// for (MethodElement method : mixinElement.getMethods()) {
// if (method.getName().equals(methodName)) {
// if (!method.isAbstract()) {
// return true;
// }
// }
// }
// }
// }
// }
// InterfaceType superType = classElement.getSupertype();
// if (superType != null) {
// ClassElement superClassElt = superType.getElement();
// if (superClassElt != null) {
// return memberHasConcreteMethodImplementationInSuperclassChain(
// superClassElt,
// methodName,
// superclassChain);
// }
// }
// return false;
// }
private boolean isUserDefinedObject(EvaluationResultImpl result) {
return result == null
|| (result instanceof ValidResult && ((ValidResult) result).isUserDefinedObject());
}
/**
* This checks the class declaration is not a superinterface to itself.
*
* @param classElt the class element to test
* @param path a list containing the potentially cyclic implements path
* @return {@code true} if and only if an error code is generated on the passed element
* @see CompileTimeErrorCode#RECURSIVE_INTERFACE_INHERITANCE
* @see CompileTimeErrorCode#RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_EXTENDS
* @see CompileTimeErrorCode#RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_IMPLEMENTS
* @see CompileTimeErrorCode#RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_WITH
*/
private boolean safeCheckForRecursiveInterfaceInheritance(ClassElement classElt,
ArrayList<ClassElement> path) {
// Detect error condition.
int size = path.size();
// If this is not the base case (size > 0), and the enclosing class is the passed class
// element then an error an error.
if (size > 0 && enclosingClass.equals(classElt)) {
String enclosingClassName = enclosingClass.getDisplayName();
if (size > 1) {
// Construct a string showing the cyclic implements path: "A, B, C, D, A"
String separator = ", ";
StringBuilder builder = new StringBuilder();
for (int i = 0; i < size; i++) {
builder.append(path.get(i).getDisplayName());
builder.append(separator);
}
builder.append(classElt.getDisplayName());
errorReporter.reportErrorForOffset(
CompileTimeErrorCode.RECURSIVE_INTERFACE_INHERITANCE,
enclosingClass.getNameOffset(),
enclosingClassName.length(),
enclosingClassName,
builder.toString());
return true;
} else { // size == 1
// RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_EXTENDS or
// RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_IMPLEMENTS or
// RECURSIVE_INTERFACE_INHERITANCE_BASE_CASE_WITH
errorReporter.reportErrorForOffset(
getBaseCaseErrorCode(classElt),
enclosingClass.getNameOffset(),
enclosingClassName.length(),
enclosingClassName);
return true;
}
}
if (path.indexOf(classElt) > 0) {
return false;
}
path.add(classElt);
// n-case
InterfaceType supertype = classElt.getSupertype();
if (supertype != null
&& safeCheckForRecursiveInterfaceInheritance(supertype.getElement(), path)) {
return true;
}
InterfaceType[] interfaceTypes = classElt.getInterfaces();
for (InterfaceType interfaceType : interfaceTypes) {
if (safeCheckForRecursiveInterfaceInheritance(interfaceType.getElement(), path)) {
return true;
}
}
InterfaceType[] mixinTypes = classElt.getMixins();
for (InterfaceType mixinType : mixinTypes) {
if (safeCheckForRecursiveInterfaceInheritance(mixinType.getElement(), path)) {
return true;
}
}
path.remove(path.size() - 1);
return false;
}
}