IDEA-125281 Support @TypeQualifierDefault for nullable/notnull warnings

This commit is contained in:
peter
2014-07-28 10:15:17 +02:00
parent 51a92208c2
commit 6f8c9cd291
7 changed files with 256 additions and 123 deletions
@@ -187,12 +187,12 @@ public final class FieldFromParameterUtils {
modifierList.setModifierProperty(PsiModifier.FINAL, isFinal);
final NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
final String nullable = manager.getNullable(parameter);
final PsiAnnotation nullable = manager.getNullableAnnotation(parameter, false);
if (nullable != null && !manager.isContainerAnnotation(nullable)) {
modifierList.addAfter(factory.createAnnotationFromText("@" + nullable, field), null);
}
else if (isFinal) {
final String notNull = manager.getNotNull(parameter);
final PsiAnnotation notNull = manager.getNotNullAnnotation(parameter, false);
if (notNull != null && !manager.isContainerAnnotation(notNull)) {
modifierList.addAfter(factory.createAnnotationFromText("@" + notNull, field), null);
}
@@ -0,0 +1,136 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.intellij.codeInsight;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Set;
/**
* @author peter
*/
public class AnnotationTargetUtil {
public static final Set<PsiAnnotation.TargetType> DEFAULT_TARGETS = Collections.unmodifiableSet(ContainerUtil.newHashSet(
PsiAnnotation.TargetType.PACKAGE, PsiAnnotation.TargetType.TYPE, PsiAnnotation.TargetType.ANNOTATION_TYPE,
PsiAnnotation.TargetType.FIELD, PsiAnnotation.TargetType.METHOD, PsiAnnotation.TargetType.CONSTRUCTOR,
PsiAnnotation.TargetType.PARAMETER, PsiAnnotation.TargetType.LOCAL_VARIABLE));
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.AnnotationUtil");
private static final PsiAnnotation.TargetType[] PACKAGE_TARGETS = {PsiAnnotation.TargetType.PACKAGE};
private static final PsiAnnotation.TargetType[] TYPE_USE_TARGETS = {PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] ANNOTATION_TARGETS = {PsiAnnotation.TargetType.ANNOTATION_TYPE, PsiAnnotation.TargetType.TYPE, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] TYPE_TARGETS = {PsiAnnotation.TargetType.TYPE, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] TYPE_PARAMETER_TARGETS = {
PsiAnnotation.TargetType.TYPE_PARAMETER, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] CONSTRUCTOR_TARGETS = {PsiAnnotation.TargetType.CONSTRUCTOR, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] METHOD_TARGETS = {PsiAnnotation.TargetType.METHOD, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] FIELD_TARGETS = {PsiAnnotation.TargetType.FIELD, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] PARAMETER_TARGETS = {PsiAnnotation.TargetType.PARAMETER, PsiAnnotation.TargetType.TYPE_USE};
private static final PsiAnnotation.TargetType[] LOCAL_VARIABLE_TARGETS ={
PsiAnnotation.TargetType.LOCAL_VARIABLE, PsiAnnotation.TargetType.TYPE_USE};
@NotNull
public static PsiAnnotation.TargetType[] getTargetsForLocation(@Nullable PsiAnnotationOwner owner) {
if (owner == null) {
return PsiAnnotation.TargetType.EMPTY_ARRAY;
}
if (owner instanceof PsiType || owner instanceof PsiTypeElement) {
return TYPE_USE_TARGETS;
}
if (owner instanceof PsiTypeParameter) {
return TYPE_PARAMETER_TARGETS;
}
if (owner instanceof PsiModifierList) {
PsiElement element = ((PsiModifierList)owner).getParent();
if (element instanceof PsiPackageStatement) {
return PACKAGE_TARGETS;
}
if (element instanceof PsiClass) {
if (((PsiClass)element).isAnnotationType()) {
return ANNOTATION_TARGETS;
}
else {
return TYPE_TARGETS;
}
}
if (element instanceof PsiMethod) {
if (((PsiMethod)element).isConstructor()) {
return CONSTRUCTOR_TARGETS;
}
else {
return METHOD_TARGETS;
}
}
if (element instanceof PsiField) {
return FIELD_TARGETS;
}
if (element instanceof PsiParameter) {
return PARAMETER_TARGETS;
}
if (element instanceof PsiLocalVariable) {
return LOCAL_VARIABLE_TARGETS;
}
}
return PsiAnnotation.TargetType.EMPTY_ARRAY;
}
@Nullable
public static Set<PsiAnnotation.TargetType> extractRequiredAnnotationTargets(@Nullable PsiAnnotationMemberValue value) {
if (value instanceof PsiReference) {
PsiAnnotation.TargetType targetType = translateTargetRef((PsiReference)value);
if (targetType != null) {
return Collections.singleton(targetType);
}
}
else if (value instanceof PsiArrayInitializerMemberValue) {
Set <PsiAnnotation.TargetType> targets = ContainerUtil.newHashSet();
for (PsiAnnotationMemberValue initializer : ((PsiArrayInitializerMemberValue)value).getInitializers()) {
if (initializer instanceof PsiReference) {
PsiAnnotation.TargetType targetType = translateTargetRef((PsiReference)initializer);
if (targetType != null) {
targets.add(targetType);
}
}
}
return targets;
}
return null;
}
@Nullable
private static PsiAnnotation.TargetType translateTargetRef(@NotNull PsiReference reference) {
PsiElement field = reference.resolve();
if (field instanceof PsiEnumConstant) {
String name = ((PsiEnumConstant)field).getName();
try {
return PsiAnnotation.TargetType.valueOf(name);
}
catch (IllegalArgumentException e) {
LOG.warn("Unknown target: " + name);
}
}
return null;
}
}
@@ -379,14 +379,14 @@ public class AnnotationUtil {
@NotNull
public static PsiAnnotation[] getAllAnnotations(@NotNull PsiModifierListOwner owner,
boolean inHierarchy,
Set<PsiModifierListOwner> visited) {
@Nullable Set<PsiModifierListOwner> visited) {
return getAllAnnotations(owner, inHierarchy, visited, true);
}
@NotNull
public static PsiAnnotation[] getAllAnnotations(@NotNull PsiModifierListOwner owner,
boolean inHierarchy,
Set<PsiModifierListOwner> visited, boolean withInferred) {
@Nullable Set<PsiModifierListOwner> visited, boolean withInferred) {
final PsiModifierList list = owner.getModifierList();
PsiAnnotation[] annotations = PsiAnnotation.EMPTY_ARRAY;
if (list != null) {
@@ -27,7 +27,6 @@ import com.intellij.psi.*;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -39,18 +38,19 @@ import java.util.*;
*/
public class NullableNotNullManager implements PersistentStateComponent<Element> {
private static final Logger LOG = Logger.getInstance("#" + NullableNotNullManager.class.getName());
@NonNls private static final String NULLABLE_CONTAINER_ANNOTATION = "javax.annotation.ParametersAreNullableByDefault";
@NonNls private static final String NONNULL_CONTAINER_ANNOTATION = "javax.annotation.ParametersAreNonnullByDefault";
public String myDefaultNullable = AnnotationUtil.NULLABLE;
public String myDefaultNotNull = AnnotationUtil.NOT_NULL;
public final JDOMExternalizableStringList myNullables = new JDOMExternalizableStringList();
public final JDOMExternalizableStringList myNotNulls = new JDOMExternalizableStringList();
public static final String[] DEFAULT_NULLABLES = {AnnotationUtil.NULLABLE, "javax.annotation.Nullable",
private static final String JAVAX_ANNOTATION_NULLABLE = "javax.annotation.Nullable";
private static final String JAVAX_ANNOTATION_NONNULL = "javax.annotation.Nonnull";
public static final String[] DEFAULT_NULLABLES = {AnnotationUtil.NULLABLE, JAVAX_ANNOTATION_NULLABLE,
"edu.umd.cs.findbugs.annotations.Nullable", "android.support.annotation.Nullable"
};
public static final String[] DEFAULT_NOT_NULLS = {AnnotationUtil.NOT_NULL, "javax.annotation.Nonnull",
public static final String[] DEFAULT_NOT_NULLS = {AnnotationUtil.NOT_NULL, JAVAX_ANNOTATION_NONNULL,
"edu.umd.cs.findbugs.annotations.NonNull", "android.support.annotation.NonNull"
};
@@ -106,8 +106,9 @@ public class NullableNotNullManager implements PersistentStateComponent<Element>
return findNullabilityAnnotation(owner, checkBases, true);
}
public boolean isContainerAnnotation(String anno) {
return NULLABLE_CONTAINER_ANNOTATION.equals(anno) || NONNULL_CONTAINER_ANNOTATION.equals(anno);
public boolean isContainerAnnotation(@NotNull PsiAnnotation anno) {
PsiAnnotation.TargetType[] acceptAnyTarget = PsiAnnotation.TargetType.values();
return isNullabilityDefault(anno, true, acceptAnyTarget) || isNullabilityDefault(anno, false, acceptAnyTarget);
}
public void setDefaultNullable(@NotNull String defaultNullable) {
@@ -145,15 +146,20 @@ public class NullableNotNullManager implements PersistentStateComponent<Element>
return annotation;
}
if (owner instanceof PsiParameter && !TypeConversionUtil.isPrimitiveAndNotNull(((PsiParameter)owner).getType())) {
// even if javax.annotation.Nullable is not configured, it should still take precedence over ByDefault annotations
if (AnnotationUtil.isAnnotated(owner, nullable ? Arrays.asList(DEFAULT_NOT_NULLS) : Arrays.asList(DEFAULT_NULLABLES), checkBases, false)) {
return null;
}
return findContainerAnnotation(owner, nullable
? NULLABLE_CONTAINER_ANNOTATION
: NONNULL_CONTAINER_ANNOTATION);
PsiType type = getOwnerType(owner);
if (type == null || TypeConversionUtil.isPrimitiveAndNotNull(type)) return null;
// even if javax.annotation.Nullable is not configured, it should still take precedence over ByDefault annotations
if (AnnotationUtil.isAnnotated(owner, nullable ? Arrays.asList(DEFAULT_NOT_NULLS) : Arrays.asList(DEFAULT_NULLABLES), checkBases, false)) {
return null;
}
return findNullabilityDefaultInHierarchy(owner, nullable);
}
@Nullable
private static PsiType getOwnerType(PsiModifierListOwner owner) {
if (owner instanceof PsiVariable) return ((PsiVariable)owner).getType();
if (owner instanceof PsiMethod) return ((PsiMethod)owner).getReturnType();
return null;
}
@@ -166,11 +172,13 @@ public class NullableNotNullManager implements PersistentStateComponent<Element>
}
@Nullable
private static PsiAnnotation findContainerAnnotation(PsiModifierListOwner owner, String annotationFQN) {
private static PsiAnnotation findNullabilityDefaultInHierarchy(PsiModifierListOwner owner, boolean nullable) {
PsiAnnotation.TargetType[] placeTargetTypes = AnnotationTargetUtil.getTargetsForLocation(owner.getModifierList());
PsiElement element = owner.getParent();
while (element != null) {
if (element instanceof PsiModifierListOwner) {
PsiAnnotation annotation = AnnotationUtil.findAnnotation((PsiModifierListOwner)element, annotationFQN);
PsiAnnotation annotation = getNullabilityDefault((PsiModifierListOwner)element, nullable, placeTargetTypes);
if (annotation != null) {
return annotation;
}
@@ -179,7 +187,7 @@ public class NullableNotNullManager implements PersistentStateComponent<Element>
if (element instanceof PsiClassOwner) {
String packageName = ((PsiClassOwner)element).getPackageName();
PsiPackage psiPackage = JavaPsiFacade.getInstance(element.getProject()).findPackage(packageName);
return AnnotationUtil.findAnnotation(psiPackage, annotationFQN);
return psiPackage == null ? null : getNullabilityDefault(psiPackage, nullable, placeTargetTypes);
}
element = element.getContext();
@@ -187,6 +195,37 @@ public class NullableNotNullManager implements PersistentStateComponent<Element>
return null;
}
private static PsiAnnotation getNullabilityDefault(@NotNull PsiModifierListOwner container, boolean nullable, PsiAnnotation.TargetType[] placeTargetTypes) {
PsiModifierList modifierList = container.getModifierList();
if (modifierList == null) return null;
for (PsiAnnotation annotation : modifierList.getAnnotations()) {
if (isNullabilityDefault(annotation, nullable, placeTargetTypes)) {
return annotation;
}
}
return null;
}
private static boolean isNullabilityDefault(@NotNull PsiAnnotation annotation, boolean nullable, PsiAnnotation.TargetType[] placeTargetTypes) {
PsiJavaCodeReferenceElement element = annotation.getNameReferenceElement();
PsiElement declaration = element == null ? null : element.resolve();
if (!(declaration instanceof PsiClass)) return false;
if (!AnnotationUtil.isAnnotated((PsiClass)declaration,
nullable ? JAVAX_ANNOTATION_NULLABLE : JAVAX_ANNOTATION_NONNULL,
false,
true)) {
return false;
}
PsiAnnotation tqDefault = AnnotationUtil.findAnnotation((PsiClass)declaration, true, "javax.annotation.meta.TypeQualifierDefault");
if (tqDefault == null) return false;
Set<PsiAnnotation.TargetType> required = AnnotationTargetUtil.extractRequiredAnnotationTargets(tqDefault.findAttributeValue(null));
if (required == null) return false;
return required.isEmpty() || ContainerUtil.intersects(required, Arrays.asList(placeTargetTypes));
}
public List<String> getNullables() {
return myNullables;
}
@@ -15,6 +15,7 @@
*/
package com.intellij.psi.impl;
import com.intellij.codeInsight.AnnotationTargetUtil;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -51,7 +52,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -60,22 +60,6 @@ import static com.intellij.psi.PsiAnnotation.TargetType;
public class PsiImplUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiImplUtil");
private static final Set<TargetType> DEFAULT_TARGETS = Collections.unmodifiableSet(ContainerUtil.newHashSet(
TargetType.PACKAGE, TargetType.TYPE, TargetType.ANNOTATION_TYPE,
TargetType.FIELD, TargetType.METHOD, TargetType.CONSTRUCTOR,
TargetType.PARAMETER, TargetType.LOCAL_VARIABLE));
private static final TargetType[] PACKAGE_TARGETS = {TargetType.PACKAGE};
private static final TargetType[] TYPE_USE_TARGETS = {TargetType.TYPE_USE};
private static final TargetType[] ANNOTATION_TARGETS = {TargetType.ANNOTATION_TYPE, TargetType.TYPE, TargetType.TYPE_USE};
private static final TargetType[] TYPE_TARGETS = {TargetType.TYPE, TargetType.TYPE_USE};
private static final TargetType[] TYPE_PARAMETER_TARGETS = {TargetType.TYPE_PARAMETER, TargetType.TYPE_USE};
private static final TargetType[] CONSTRUCTOR_TARGETS = {TargetType.CONSTRUCTOR, TargetType.TYPE_USE};
private static final TargetType[] METHOD_TARGETS = {TargetType.METHOD, TargetType.TYPE_USE};
private static final TargetType[] FIELD_TARGETS = {TargetType.FIELD, TargetType.TYPE_USE};
private static final TargetType[] PARAMETER_TARGETS = {TargetType.PARAMETER, TargetType.TYPE_USE};
private static final TargetType[] LOCAL_VARIABLE_TARGETS ={TargetType.LOCAL_VARIABLE, TargetType.TYPE_USE};
private PsiImplUtil() { }
@NotNull
@@ -376,93 +360,14 @@ public class PsiImplUtil {
PsiModifierList modifierList = annotationType.getModifierList();
if (modifierList == null) return null;
PsiAnnotation target = modifierList.findAnnotation(CommonClassNames.JAVA_LANG_ANNOTATION_TARGET);
if (target == null) return DEFAULT_TARGETS; // if omitted it is applicable to all but Java 8 TYPE_USE/TYPE_PARAMETERS targets
if (target == null) return AnnotationTargetUtil.DEFAULT_TARGETS; // if omitted it is applicable to all but Java 8 TYPE_USE/TYPE_PARAMETERS targets
PsiAnnotationMemberValue value = target.findAttributeValue(null);
if (value instanceof PsiReference) {
TargetType targetType = translateTargetRef((PsiReference)value);
if (targetType != null) {
return Collections.singleton(targetType);
}
}
else if (value instanceof PsiArrayInitializerMemberValue) {
Set <TargetType> targets = ContainerUtil.newHashSet();
for (PsiAnnotationMemberValue initializer : ((PsiArrayInitializerMemberValue)value).getInitializers()) {
if (initializer instanceof PsiReference) {
TargetType targetType = translateTargetRef((PsiReference)initializer);
if (targetType != null) {
targets.add(targetType);
}
}
}
return targets;
}
return null;
}
@Nullable
private static TargetType translateTargetRef(PsiReference reference) {
PsiElement field = reference.resolve();
if (field instanceof PsiEnumConstant) {
String name = ((PsiEnumConstant)field).getName();
try {
return TargetType.valueOf(name);
}
catch (IllegalArgumentException e) {
LOG.warn("Unknown target: " + name);
}
}
return null;
return AnnotationTargetUtil.extractRequiredAnnotationTargets(target.findAttributeValue(null));
}
@NotNull
public static TargetType[] getTargetsForLocation(@Nullable PsiAnnotationOwner owner) {
if (owner == null) {
return TargetType.EMPTY_ARRAY;
}
if (owner instanceof PsiType || owner instanceof PsiTypeElement) {
return TYPE_USE_TARGETS;
}
if (owner instanceof PsiTypeParameter) {
return TYPE_PARAMETER_TARGETS;
}
if (owner instanceof PsiModifierList) {
PsiElement element = ((PsiModifierList)owner).getParent();
if (element instanceof PsiPackageStatement) {
return PACKAGE_TARGETS;
}
if (element instanceof PsiClass) {
if (((PsiClass)element).isAnnotationType()) {
return ANNOTATION_TARGETS;
}
else {
return TYPE_TARGETS;
}
}
if (element instanceof PsiMethod) {
if (((PsiMethod)element).isConstructor()) {
return CONSTRUCTOR_TARGETS;
}
else {
return METHOD_TARGETS;
}
}
if (element instanceof PsiField) {
return FIELD_TARGETS;
}
if (element instanceof PsiParameter) {
return PARAMETER_TARGETS;
}
if (element instanceof PsiLocalVariable) {
return LOCAL_VARIABLE_TARGETS;
}
}
return TargetType.EMPTY_ARRAY;
return AnnotationTargetUtil.getTargetsForLocation(owner);
}
@Nullable
@@ -0,0 +1,25 @@
import foo.*;
import org.jetbrains.annotations.NotNull;
class Some {
void foo(@NotNull String s) {
NotNullClass.foo(null);
if (<warning descr="Condition 'NotNullClass.foo(s) == null' is always 'false'">NotNullClass.foo(s) == null</warning>) {}
NullableClass.foo(null);
if (NullableClass.foo("a") == null) {}
AnotherPackageNotNull.foo(null);
if (<warning descr="Condition 'AnotherPackageNotNull.foo(s) == null' is always 'false'">AnotherPackageNotNull.foo(s) == null</warning>) {}
}
}
@bar.MethodsAreNotNullByDefault
class NotNullClass {
static native Object foo(String s);
}
class NullableClass {
static native Object foo(String s);
}
@@ -258,8 +258,14 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase {
public void testAccessingSameArrayElements() { doTest(); }
public void testParametersAreNonnullByDefault() {
myFixture.addClass("package javax.annotation; public @interface ParametersAreNonnullByDefault {}");
myFixture.addClass("package javax.annotation; public @interface ParametersAreNullableByDefault {}");
addJavaxNullabilityAnnotations();
myFixture.addClass("package javax.annotation;" +
"@javax.annotation.meta.TypeQualifierDefault(java.lang.annotation.ElementType.PARAMETER) @javax.annotation.Nonnull " +
"public @interface ParametersAreNonnullByDefault {}");
myFixture.addClass("package javax.annotation;" +
"@javax.annotation.meta.TypeQualifierDefault(java.lang.annotation.ElementType.PARAMETER) @javax.annotation.Nullable " +
"public @interface ParametersAreNullableByDefault {}");
myFixture.addClass("package foo; public class AnotherPackageNotNull { public static void foo(String s) {}}");
myFixture.addFileToProject("foo/package-info.java", "@javax.annotation.ParametersAreNonnullByDefault package foo;");
@@ -267,6 +273,28 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase {
doTest();
}
private void addJavaxNullabilityAnnotations() {
myFixture.addClass("package javax.annotation;" +
"public @interface Nonnull {}");
myFixture.addClass("package javax.annotation.meta;" +
"public @interface TypeQualifier {}");
myFixture.addClass("package javax.annotation.meta;" +
"public @interface TypeQualifierDefault { java.lang.annotation.ElementType[] value() default {};}");
}
public void testCustomTypeQualifierDefault() {
addJavaxNullabilityAnnotations();
myFixture.addClass("package bar;" +
"@javax.annotation.meta.TypeQualifierDefault(java.lang.annotation.ElementType.METHOD) @javax.annotation.Nonnull " +
"public @interface MethodsAreNotNullByDefault {}");
myFixture.addClass("package foo; public class AnotherPackageNotNull { public static native Object foo(String s); }");
myFixture.addFileToProject("foo/package-info.java", "@bar.MethodsAreNotNullByDefault package foo;");
myFixture.enableInspections(new DataFlowInspection());
myFixture.testHighlighting(true, false, true, getTestName(false) + ".java");
}
public void testTrueOrEqualsSomething() {
doTest();
myFixture.launchAction(myFixture.findSingleIntention("Remove redundant assignment"));