/* * 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.CheckedModeCompileTimeErrorCode; 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.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); checkForIllegalReturnType(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); checkForIllegalReturnType(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) { checkForYieldOfInvalidType(node.getExpression(), node.getStar() != null); } else { 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 (inGenerator || computeReturnTypeForMethod(null).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) { // TODO(paulberry): this error should be based on the actual type of the constant, not the // static type. See dartbug.com/21119. errorReporter.reportTypeErrorForNode( CheckedModeCompileTimeErrorCode.CONST_FIELD_INITIALIZER_NOT_ASSIGNABLE, expression, staticType, fieldType); } 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; } /** * If the current function is async, async*, or sync*, verify that its declared return type is * assignable to Future, Stream, or Iterable, respectively. If not, report the error using [node]. */ private void checkForIllegalReturnType(TypeName node) { if (node == null) { // No declared return type, so the return type must be dynamic, which is assignable to // everything. return; } if (enclosingFunction.isAsynchronous()) { if (enclosingFunction.isGenerator()) { // TODO(paulberry): We should report an error if enclosingFunction.getReturnType() isn't // assignable to Stream<dynamic>. But we can't because the Stream type isn't available in // the type provider. So to avoid bogus warnings, don't do any check. } else { // TODO(paulberry): We should report an error if enclosingFunction.getReturnType() isn't // assignable to Future<dynamic>. But we can't because the Future type isn't available in // the type provider. So to avoid bogus warnings, don't do any check. } } else if (enclosingFunction.isGenerator()) { if (!enclosingFunction.getReturnType().isAssignableTo(typeProvider.getIterableDynamicType())) { errorReporter.reportErrorForNode( StaticTypeWarningCode.ILLEGAL_SYNC_GENERATOR_RETURN_TYPE, node); } } } /** * 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(); // Check every list element. boolean hasProblems = false; for (Expression element : node.getElements()) { if (node.getConstKeyword() != null) { // TODO(paulberry): this error should be based on the actual type of the list element, not // the static type. See dartbug.com/21119. hasProblems |= checkForArgumentTypeNotAssignableWithExpectedTypes( element, listElementType, CheckedModeCompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE); } hasProblems |= checkForArgumentTypeNotAssignableWithExpectedTypes( element, listElementType, StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE); } 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(); // Check every map entry. boolean hasProblems = false; NodeList<MapLiteralEntry> entries = node.getEntries(); for (MapLiteralEntry entry : entries) { Expression key = entry.getKey(); Expression value = entry.getValue(); if (node.getConstKeyword() != null) { // TODO(paulberry): this error should be based on the actual type of the list element, not // the static type. See dartbug.com/21119. hasProblems |= checkForArgumentTypeNotAssignableWithExpectedTypes( key, keyType, CheckedModeCompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE); hasProblems |= checkForArgumentTypeNotAssignableWithExpectedTypes( value, valueType, CheckedModeCompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE); } hasProblems |= checkForArgumentTypeNotAssignableWithExpectedTypes( key, keyType, StaticWarningCode.MAP_KEY_TYPE_NOT_ASSIGNABLE); hasProblems |= checkForArgumentTypeNotAssignableWithExpectedTypes( value, valueType, StaticWarningCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE); } 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; } if (inGenerator) { // "return expression;" is disallowed in generators, but this is checked elsewhere. Bare // "return" is always allowed in generators regardless of the return type. So no need to do // any further checking. return false; } Type staticReturnType = computeReturnTypeForMethod(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 (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; } /** * Check for a type mis-match between the yielded type and the declared return type of a generator * function. This method should only be called in generator functions. */ private boolean checkForYieldOfInvalidType(Expression yieldExpression, boolean isYieldEach) { if (enclosingFunction == null) { return false; } Type declaredReturnType = enclosingFunction.getReturnType(); Type staticYieldedType = getStaticType(yieldExpression); Type impliedReturnType; if (isYieldEach) { impliedReturnType = staticYieldedType; } else if (enclosingFunction.isAsynchronous()) { // TODO(paulberry): We should set impliedReturnType to Stream<staticYieldedType>. But we // can't because the Stream type isn't available in the type provider. So to avoid bogus // warnings, use dynamic. impliedReturnType = typeProvider.getDynamicType(); } else { impliedReturnType = typeProvider.getIterableType().substitute(new Type[] {staticYieldedType}); } if (!impliedReturnType.isAssignableTo(declaredReturnType)) { errorReporter.reportTypeErrorForNode( StaticTypeWarningCode.YIELD_OF_INVALID_TYPE, yieldExpression, impliedReturnType, declaredReturnType); return true; } if (isYieldEach) { // Since the declared return type might have been "dynamic", we need to also check that the // implied return type is assignable to generic Stream/Iterable. Type requiredReturnType; if (enclosingFunction.isAsynchronous()) { // TODO(paulberry): We should set requiredReturnType to Stream<dynamic>. But we can't // because the Stream type isn't available in the type provider. So to avoid bogus // warnings, use dynamic. requiredReturnType = typeProvider.getDynamicType(); } else { requiredReturnType = typeProvider.getIterableDynamicType(); } if (!impliedReturnType.isAssignableTo(requiredReturnType)) { errorReporter.reportTypeErrorForNode( StaticTypeWarningCode.YIELD_OF_INVALID_TYPE, yieldExpression, impliedReturnType, requiredReturnType); 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; } private Type computeReturnTypeForMethod(Expression returnExpression) { // This method should never be called for generators, since generators are never allowed to // contain return statements with expressions. if (returnExpression == null) { if (enclosingFunction.isAsynchronous()) { // TODO(paulberry): We should return Future<Null>. But we can't because the Future type // isn't available in the type provider. So to avoid bogus warnings, return dynamic. return typeProvider.getDynamicType(); } else { return VoidTypeImpl.getInstance(); } } Type staticReturnType = getStaticType(returnExpression); if (staticReturnType != null && enclosingFunction.isAsynchronous()) { // TODO(paulberry): We should return Future<flatten(staticReturnType)>. But we can't // implement the flatten function, and even if we could, we wouldn't be able to return the // proper type, because the Future type isn't available in the type provider. So to avoid // bogus warnings, return dynamic. return typeProvider.getDynamicType(); } return staticReturnType; } /** * 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.getValue() != null && result.getValue().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; } }