diff options
author | Stephan Herrmann | 2015-11-17 13:26:53 +0000 |
---|---|---|
committer | Stephan Herrmann | 2015-12-03 14:50:32 +0000 |
commit | 6dbe2813dd7277bd2252f3441ba09237b6b43790 (patch) | |
tree | 76c61d1c66ac33d85a9b274b7fcfd65613c4af35 | |
parent | c6083a2d48fa11ffa1e9554a2f00be6af36feafe (diff) | |
download | eclipse.jdt.core-6dbe2813dd7277bd2252f3441ba09237b6b43790.tar.gz eclipse.jdt.core-6dbe2813dd7277bd2252f3441ba09237b6b43790.tar.xz eclipse.jdt.core-6dbe2813dd7277bd2252f3441ba09237b6b43790.zip |
Bug 369079: [null] Allow multiple null annotations I20151203-1230
Change-Id: I11accf3663e4c14865aef574bc0735c228979f87
19 files changed, 466 insertions, 133 deletions
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java index f3cdee896c..45d9147eef 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java @@ -1959,8 +1959,11 @@ public void test012b(){ " <option key=\"org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations\" value=\"disabled\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation\" value=\"ignore\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.annotation.nonnull\" value=\"org.eclipse.jdt.annotation.NonNull\"/>\n" + + " <option key=\"org.eclipse.jdt.core.compiler.annotation.nonnull.secondary\" value=\"\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.annotation.nonnullbydefault\" value=\"org.eclipse.jdt.annotation.NonNullByDefault\"/>\n" + + " <option key=\"org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary\" value=\"\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.annotation.nullable\" value=\"org.eclipse.jdt.annotation.Nullable\"/>\n" + + " <option key=\"org.eclipse.jdt.core.compiler.annotation.nullable.secondary\" value=\"\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.annotation.nullanalysis\" value=\"disabled\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode\" value=\"disabled\"/>\n" + " <option key=\"org.eclipse.jdt.core.compiler.codegen.lambda.genericSignature\" value=\"do not generate\"/>\n" + diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java index 66c9b0ad53..42f263bc91 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java @@ -16,6 +16,7 @@ package org.eclipse.jdt.core.tests.compiler.regression; import java.io.File; +import java.util.HashMap; import java.util.Map; import junit.framework.Test; @@ -8660,4 +8661,152 @@ public void testBug482075() { "" ); } +public void testMultipleAnnotations() { + Map options1 = new HashMap<>(getCompilerOptions()); + options1.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "org.foo1.NonNull"); + options1.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "org.foo1.Nullable"); + runConformTest( + new String[] { + "org/foo1/Nullable.java", + "package org.foo1;\n" + + "import java.lang.annotation.*;\n" + + "@Retention(RetentionPolicy.CLASS)\n" + + "@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})\n" + + "public @interface Nullable {}\n", + "org/foo1/NonNull.java", + "package org.foo1;\n" + + "import java.lang.annotation.*;\n" + + "@Retention(RetentionPolicy.CLASS)\n" + + "@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})\n" + + "public @interface NonNull {}\n", + "p1/TestNulls.java", + "package p1;\n" + + "import org.foo1.*;\n" + + "\n" + + "public class TestNulls {\n" + + " public @Nullable String weaken(@NonNull String theValue) {\n" + + " return theValue;\n" + + " }\n" + + "\n" + + "}" + }, + options1); + Map options2 = new HashMap<>(getCompilerOptions()); + options2.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "org.foo2.NonNull2"); + options2.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "org.foo2.Nullable2"); + options2.put(JavaCore.COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME, "org.foo2.NoNulls2"); + runConformTest( + false, // flush + new String[] { + "org/foo2/Nullable2.java", + "package org.foo2;\n" + + "import java.lang.annotation.*;\n" + + "@Retention(RetentionPolicy.CLASS)\n" + + "@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})\n" + + "public @interface Nullable2 {}\n", + "org/foo2/NonNull2.java", + "package org.foo2;\n" + + "import java.lang.annotation.*;\n" + + "@Retention(RetentionPolicy.CLASS)\n" + + "@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})\n" + + "public @interface NonNull2 {}\n", + "org/foo2/NoNulls2.java", + "package org.foo2;\n" + + "import java.lang.annotation.*;\n" + + "@Retention(RetentionPolicy.CLASS)\n" + + "@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})\n" + + "public @interface NoNulls2 {}\n", + "p2/TestNulls2.java", + "package p2;\n" + + "import org.foo2.*;\n" + + "\n" + + "public class TestNulls2 {\n" + + " public @Nullable2 String weaken(@NonNull2 String theValue) {\n" + + " return theValue;\n" + + " }\n" + + " @NoNulls2\n" + + " public String strong(String theValue) {\n" + + " return theValue;\n" + + " }\n" + + "\n" + + "}", + "p2/TestNulls2a.java", + "package p2;\n" + + "import org.foo2.*;\n" + + "\n" + + "@NoNulls2\n" + + "public class TestNulls2a {\n" + + " public String strong(String theValue) {\n" + + " return theValue;\n" + + " }\n" + + "\n" + + "}" + }, + null, //libs + options1, + "", + "", + "", + JavacTestOptions.DEFAULT); + Map options3 = getCompilerOptions(); + options3.put(JavaCore.COMPILER_NONNULL_ANNOTATION_SECONDARY_NAMES, "org.foo1.NonNull,org.foo2.NonNull2"); + options3.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_SECONDARY_NAMES, " org.foo1.Nullable , org.foo2.Nullable2 "); // some spaces to test trimming + options3.put(JavaCore.COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_SECONDARY_NAMES, "org.foo2.NoNulls2"); + String specifiedOrInferred = (this.complianceLevel < ClassFileConstants.JDK1_8 ? "specified" : "inferred"); + runNegativeTestWithLibs( + new String[] { + "p3/Test.java", + "package p3;\n" + + "import p1.TestNulls;\n" + + "import p2.TestNulls2;\n" + + "import p2.TestNulls2a;\n" + + "import org.eclipse.jdt.annotation.*;\n" + + "public class Test {\n" + + " @NonNull String test1(TestNulls test, @Nullable String input) {\n" + + " return test.weaken(input);\n" + + " }\n" + + " @NonNull String test2(TestNulls2 test, @Nullable String input) {\n" + + " return test.weaken(input);\n" + + " }\n" + + " @NonNull String test3(TestNulls2 test, @Nullable String input) {\n" + + " return test.strong(input); // requires nonnull due to method-level default\n" + + " }\n" + + " @NonNull String test4(TestNulls2a test, @Nullable String input) {\n" + + " return test.strong(input); // requires nonnull due to type-level default\n" + + " }\n" + + "}\n" + }, + options3, + "----------\n" + + "1. ERROR in p3\\Test.java (at line 8)\n" + + " return test.weaken(input);\n" + + " ^^^^^^^^^^^^^^^^^^\n" + + "Null type mismatch: required \'@NonNull String\' but the provided value is "+specifiedOrInferred+" as @Nullable\n" + + "----------\n" + + "2. ERROR in p3\\Test.java (at line 8)\n" + + " return test.weaken(input);\n" + + " ^^^^^\n" + + mismatch_NonNull_Nullable("String") + + "----------\n" + + "3. ERROR in p3\\Test.java (at line 11)\n" + + " return test.weaken(input);\n" + + " ^^^^^^^^^^^^^^^^^^\n" + + "Null type mismatch: required \'@NonNull String\' but the provided value is "+specifiedOrInferred+" as @Nullable\n" + + "----------\n" + + "4. ERROR in p3\\Test.java (at line 11)\n" + + " return test.weaken(input);\n" + + " ^^^^^\n" + + mismatch_NonNull_Nullable("String") + + "----------\n" + + "5. ERROR in p3\\Test.java (at line 14)\n" + + " return test.strong(input); // requires nonnull due to method-level default\n" + + " ^^^^^\n" + + mismatch_NonNull_Nullable("String") + + "----------\n" + + "6. ERROR in p3\\Test.java (at line 17)\n" + + " return test.strong(input); // requires nonnull due to type-level default\n" + + " ^^^^^\n" + + mismatch_NonNull_Nullable("String") + + "----------\n"); +} } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java index 690aeb4f24..53ba667238 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullTypeAnnotationTest.java @@ -15,12 +15,14 @@ package org.eclipse.jdt.core.tests.compiler.regression;
import java.io.File;
+import java.util.HashMap;
import java.util.Map;
import junit.framework.Test;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.tests.util.Util;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
@SuppressWarnings({ "unchecked", "rawtypes" })
public class NullTypeAnnotationTest extends AbstractNullAnnotationTest {
@@ -8853,5 +8855,63 @@ public void testBug483527() { },
compilerOptions,
"");
+} +public void testMultipleAnnotations1() {
+ Map options1 = new HashMap<>(getCompilerOptions());
+ options1.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "org.foo.NonNull");
+ options1.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "org.foo.Nullable");
+ runConformTest(
+ new String[] {
+ "org/foo/Nullable.java",
+ "package org.foo;\n" +
+ "import java.lang.annotation.*;\n" +
+ "@Retention(RetentionPolicy.CLASS)\n" +
+ "@Target({ElementType.TYPE_USE})\n" +
+ "public @interface Nullable {}\n",
+ "org/foo/NonNull.java",
+ "package org.foo;\n" +
+ "import java.lang.annotation.*;\n" +
+ "@Retention(RetentionPolicy.CLASS)\n" +
+ "@Target({ElementType.TYPE_USE})\n" +
+ "public @interface NonNull {}\n",
+ "p1/TestNulls.java",
+ "package p1;\n" +
+ "import org.foo.*;\n" +
+ "\n" +
+ "public class TestNulls {\n" +
+ " public @Nullable String weaken(@NonNull String theValue) {\n" +
+ " return theValue;\n" +
+ " }\n" +
+ "\n" +
+ "}"
+ },
+ options1);
+ Map options2 = getCompilerOptions();
+ options2.put(CompilerOptions.OPTION_NonNullAnnotationSecondaryNames, "org.foo.NonNull");
+ options2.put(CompilerOptions.OPTION_NullableAnnotationSecondaryNames, "org.foo.Nullable");
+ runNegativeTestWithLibs(
+ new String[] {
+ "p2/Test.java",
+ "package p2;\n" +
+ "import p1.TestNulls;\n" +
+ "import org.eclipse.jdt.annotation.*;\n" +
+ "public class Test {\n" +
+ " @NonNull String test(TestNulls test, @Nullable String input) {\n" +
+ " return test.weaken(input);\n" +
+ " }\n" +
+ "}\n"
+ },
+ options2,
+ "----------\n" +
+ "1. ERROR in p2\\Test.java (at line 6)\n" +
+ " return test.weaken(input);\n" +
+ " ^^^^^^^^^^^^^^^^^^\n" +
+ "Null type mismatch (type annotations): required \'@NonNull String\' but this expression has type \'@Nullable String\'\n" +
+ "----------\n" +
+ "2. ERROR in p2\\Test.java (at line 6)\n" +
+ " return test.weaken(input);\n" +
+ " ^^^^^\n" +
+ "Null type mismatch (type annotations): required \'@NonNull String\' but this expression has type \'@Nullable String\'\n" +
+ "----------\n");
}
}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java index 4651364ad6..5d489477c7 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java @@ -972,10 +972,10 @@ public abstract class ASTNode implements TypeConstants, TypeIds { System.arraycopy(se8Annotations, 0, se8Annotations = new AnnotationBinding[se8count + 1], 0, se8count); se8Annotations[se8count++] = annotation; } - if (annotationType.id == TypeIds.T_ConfiguredAnnotationNonNull) { + if (annotationType.hasNullBit(TypeIds.BitNonNullAnnotation)) { se8nullBits |= TagBits.AnnotationNonNull; se8NullAnnotation = annotations[i]; - } else if (annotationType.id == TypeIds.T_ConfiguredAnnotationNullable) { + } else if (annotationType.hasNullBit(TypeIds.BitNullableAnnotation)) { se8nullBits |= TagBits.AnnotationNullable; se8NullAnnotation = annotations[i]; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java index 94b8b08caa..0222db4be2 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java @@ -388,34 +388,32 @@ public abstract class Annotation extends Expression { case TypeIds.T_JavaLangInvokeMethodHandlePolymorphicSignature : tagBits |= TagBits.AnnotationPolymorphicSignature; break; - case TypeIds.T_ConfiguredAnnotationNullable : - tagBits |= TagBits.AnnotationNullable; - break; - case TypeIds.T_ConfiguredAnnotationNonNull : - tagBits |= TagBits.AnnotationNonNull; - break; - case TypeIds.T_ConfiguredAnnotationNonNullByDefault : - // seeing this id implies that null annotation analysis is enabled - Object value = null; - if (valueAttribute != null) { - if (valueAttribute.compilerElementPair != null) - value = valueAttribute.compilerElementPair.value; - } else { // fetch default value - TODO: cache it? - MethodBinding[] methods = annotationType.methods(); - if (methods != null && methods.length == 1) - value = methods[0].getDefaultValue(); - else - tagBits |= TagBits.AnnotationNonNullByDefault; // custom unconfigurable NNBD - } - if (value instanceof BooleanConstant) { - // boolean value is used for declaration annotations, signal using the annotation tag bit: - tagBits |= ((BooleanConstant)value).booleanValue() ? TagBits.AnnotationNonNullByDefault : TagBits.AnnotationNullUnspecifiedByDefault; - } else if (value != null) { - // non-boolean value signals type annotations, evaluate from DefaultLocation[] to bitvector a la Binding#NullnessDefaultMASK: - tagBits |= nullLocationBitsFromAnnotationValue(value); - } - break; } + if (annotationType.hasNullBit(TypeIds.BitNullableAnnotation)) { + tagBits |= TagBits.AnnotationNullable; + } else if (annotationType.hasNullBit(TypeIds.BitNonNullAnnotation)) { + tagBits |= TagBits.AnnotationNonNull; + } else if (annotationType.hasNullBit(TypeIds.BitNonNullByDefaultAnnotation)) { + Object value = null; + if (valueAttribute != null) { + if (valueAttribute.compilerElementPair != null) + value = valueAttribute.compilerElementPair.value; + } else { // fetch default value - TODO: cache it? + MethodBinding[] methods = annotationType.methods(); + if (methods != null && methods.length == 1) + value = methods[0].getDefaultValue(); + else + tagBits |= TagBits.AnnotationNonNullByDefault; // custom unconfigurable NNBD + } + if (value instanceof BooleanConstant) { + // boolean value is used for declaration annotations, signal using the annotation tag bit: + tagBits |= ((BooleanConstant)value).booleanValue() ? TagBits.AnnotationNonNullByDefault : TagBits.AnnotationNullUnspecifiedByDefault; + } else if (value != null) { + // non-boolean value signals type annotations, evaluate from DefaultLocation[] to bitvector a la Binding#NullnessDefaultMASK: + tagBits |= nullLocationBitsFromAnnotationValue(value); + } + } + return tagBits; } @@ -1160,8 +1158,7 @@ public abstract class Annotation extends Expression { QualifiedTypeReference.rejectAnnotationsOnStaticMemberQualififer(scope, currentType, new Annotation [] { annotation }); continue nextAnnotation; } else { - int id = annotation.resolvedType.id; - if (id == TypeIds.T_ConfiguredAnnotationNonNull || id == TypeIds.T_ConfiguredAnnotationNullable) { + if (annotation.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { scope.problemReporter().nullAnnotationUnsupportedLocation(annotation); continue nextAnnotation; } @@ -1172,6 +1169,10 @@ public abstract class Annotation extends Expression { } } + public boolean hasNullBit(int bit) { + return this.resolvedType instanceof ReferenceBinding && ((ReferenceBinding) this.resolvedType).hasNullBit(bit); + } + public abstract void traverse(ASTVisitor visitor, BlockScope scope); public abstract void traverse(ASTVisitor visitor, ClassScope scope); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java index 08515e553c..0a89f0384c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java @@ -581,13 +581,8 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre AnnotationBinding [] annotations = descParameters[i].getTypeAnnotations(); for (int j = 0, length = annotations.length; j < length; j++) { AnnotationBinding annotation = annotations[j]; - if (annotation != null) { - switch (annotation.getAnnotationType().id) { - case TypeIds.T_ConfiguredAnnotationNullable : - case TypeIds.T_ConfiguredAnnotationNonNull : - ourParameters[i] = env.createAnnotatedType(ourParameters[i], new AnnotationBinding [] { annotation }); - break; - } + if (annotation != null && annotation.getAnnotationType().hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { + ourParameters[i] = env.createAnnotatedType(ourParameters[i], new AnnotationBinding [] { annotation }); } } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java index d9e0cec29e..f1c4feef85 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java @@ -192,6 +192,11 @@ void internalAnalyseOneArgument18(BlockScope currentScope, FlowContext flowConte // immediate reporting: currentScope.problemReporter().nullityMismatchingTypeAnnotation(argument, argument.resolvedType, expectedType, annotationStatus); } else if (annotationStatus.isAnyMismatch() || (statusFromAnnotatedNull & FlowInfo.POTENTIALLY_NULL) != 0) { + if (!expectedType.hasNullTypeAnnotations() && expectedNonNullness == Boolean.TRUE) { + // improve problem rendering when using a declaration annotation in a 1.8 setting + LookupEnvironment env = currentScope.environment(); + expectedType = env.createAnnotatedType(expectedType, new AnnotationBinding[] {env.getNonNullAnnotation()}); + } flowContext.recordNullityMismatch(currentScope, argument, argument.resolvedType, expectedType, flowInfo, nullStatus, annotationStatus); } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeReference.java index ce7b8fa081..fad1c0a224 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeReference.java @@ -699,11 +699,9 @@ public Annotation findAnnotation(long nullTagBits) { if (this.annotations != null) { Annotation[] innerAnnotations = this.annotations[this.annotations.length-1]; if (innerAnnotations != null) { - int annId = nullTagBits == TagBits.AnnotationNonNull ? TypeIds.T_ConfiguredAnnotationNonNull : TypeIds.T_ConfiguredAnnotationNullable; + int annBit = nullTagBits == TagBits.AnnotationNonNull ? TypeIds.BitNonNullAnnotation : TypeIds.BitNullableAnnotation; for (int i = 0; i < innerAnnotations.length; i++) { - if (innerAnnotations[i] != null - && innerAnnotations[i].resolvedType != null - && innerAnnotations[i].resolvedType.id == annId) + if (innerAnnotations[i] != null && innerAnnotations[i].hasNullBit(annBit)) return innerAnnotations[i]; } } @@ -727,10 +725,7 @@ public boolean hasNullTypeAnnotation(AnnotationPosition position) { public static boolean containsNullAnnotation(Annotation[] annotations) { if (annotations != null) { for (int i = 0; i < annotations.length; i++) { - if (annotations[i] != null - && annotations[i].resolvedType != null - && (annotations[i].resolvedType.id == TypeIds.T_ConfiguredAnnotationNonNull - || annotations[i].resolvedType.id == TypeIds.T_ConfiguredAnnotationNullable)) + if (annotations[i] != null && (annotations[i].hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation))) return true; } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java index 1b006ee54a..bae98724c5 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java @@ -172,6 +172,9 @@ public class CompilerOptions { public static final String OPTION_NullableAnnotationName = "org.eclipse.jdt.core.compiler.annotation.nullable"; //$NON-NLS-1$ public static final String OPTION_NonNullAnnotationName = "org.eclipse.jdt.core.compiler.annotation.nonnull"; //$NON-NLS-1$ public static final String OPTION_NonNullByDefaultAnnotationName = "org.eclipse.jdt.core.compiler.annotation.nonnullbydefault"; //$NON-NLS-1$ + public static final String OPTION_NullableAnnotationSecondaryNames = "org.eclipse.jdt.core.compiler.annotation.nullable.secondary"; //$NON-NLS-1$ + public static final String OPTION_NonNullAnnotationSecondaryNames = "org.eclipse.jdt.core.compiler.annotation.nonnull.secondary"; //$NON-NLS-1$ + public static final String OPTION_NonNullByDefaultAnnotationSecondaryNames = "org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary"; //$NON-NLS-1$ public static final String OPTION_ReportUninternedIdentityComparison = "org.eclipse.jdt.core.compiler.problem.uninternedIdentityComparison"; //$NON-NLS-1$ // defaults for the above: static final char[][] DEFAULT_NULLABLE_ANNOTATION_NAME = CharOperation.splitOn('.', "org.eclipse.jdt.annotation.Nullable".toCharArray()); //$NON-NLS-1$ @@ -213,6 +216,8 @@ public class CompilerOptions { public static final String NO_TAG = "no_tag"; //$NON-NLS-1$ public static final String ALL_STANDARD_TAGS = "all_standard_tags"; //$NON-NLS-1$ + private static final String[] NO_STRINGS = new String[0]; + /** * Bit mask for configurable problems (error/warning threshold) * Note: bitmask assumes 3 highest bits to denote irritant group (to allow storing 8 groups of 29 bits each @@ -445,6 +450,12 @@ public class CompilerOptions { public char[][] nonNullAnnotationName; /** Fully qualified name of annotation to use as marker for default nonnull. */ public char[][] nonNullByDefaultAnnotationName; + /** Fully qualified names of secondary annotations to use as marker for nullable types. */ + public String[] nullableAnnotationSecondaryNames = NO_STRINGS; + /** Fully qualified names of secondary annotations to use as marker for nonnull types. */ + public String[] nonNullAnnotationSecondaryNames = NO_STRINGS; + /** Fully qualified names of secondary annotations to use as marker for default nonnull. */ + public String[] nonNullByDefaultAnnotationSecondaryNames = NO_STRINGS; /** TagBits-encoded default for non-annotated types. */ public long intendedDefaultNonNullness; // 0 or TagBits#AnnotationNonNull /** Should resources (objects of type Closeable) be analysed for matching calls to close()? */ @@ -1182,6 +1193,9 @@ public class CompilerOptions { optionsMap.put(OPTION_NullableAnnotationName, String.valueOf(CharOperation.concatWith(this.nullableAnnotationName, '.'))); optionsMap.put(OPTION_NonNullAnnotationName, String.valueOf(CharOperation.concatWith(this.nonNullAnnotationName, '.'))); optionsMap.put(OPTION_NonNullByDefaultAnnotationName, String.valueOf(CharOperation.concatWith(this.nonNullByDefaultAnnotationName, '.'))); + optionsMap.put(OPTION_NullableAnnotationSecondaryNames, nameListToString(this.nullableAnnotationSecondaryNames)); + optionsMap.put(OPTION_NonNullAnnotationSecondaryNames, nameListToString(this.nonNullAnnotationSecondaryNames)); + optionsMap.put(OPTION_NonNullByDefaultAnnotationSecondaryNames, nameListToString(this.nonNullByDefaultAnnotationSecondaryNames)); optionsMap.put(OPTION_ReportMissingNonNullByDefaultAnnotation, getSeverityString(MissingNonNullByDefaultAnnotation)); optionsMap.put(OPTION_ReportUnusedTypeParameter, getSeverityString(UnusedTypeParameter)); optionsMap.put(OPTION_SyntacticNullAnalysisForFields, this.enableSyntacticNullAnalysisForFields ? ENABLED : DISABLED); @@ -1705,6 +1719,15 @@ public class CompilerOptions { if ((optionValue = optionsMap.get(OPTION_NonNullByDefaultAnnotationName)) != null) { this.nonNullByDefaultAnnotationName = CharOperation.splitAndTrimOn('.', optionValue.toCharArray()); } + if ((optionValue = optionsMap.get(OPTION_NullableAnnotationSecondaryNames)) != null) { + this.nullableAnnotationSecondaryNames = stringToNameList(optionValue); + } + if ((optionValue = optionsMap.get(OPTION_NonNullAnnotationSecondaryNames)) != null) { + this.nonNullAnnotationSecondaryNames = stringToNameList(optionValue); + } + if ((optionValue = optionsMap.get(OPTION_NonNullByDefaultAnnotationSecondaryNames)) != null) { + this.nonNullByDefaultAnnotationSecondaryNames = stringToNameList(optionValue); + } if ((optionValue = optionsMap.get(OPTION_ReportMissingNonNullByDefaultAnnotation)) != null) updateSeverity(MissingNonNullByDefaultAnnotation, optionValue); if ((optionValue = optionsMap.get(OPTION_SyntacticNullAnalysisForFields)) != null) { this.enableSyntacticNullAnalysisForFields = ENABLED.equals(optionValue); @@ -1850,6 +1873,26 @@ public class CompilerOptions { } } } + + private String[] stringToNameList(String optionValue) { + String[] result = optionValue.split(","); //$NON-NLS-1$ + if (result == null) + return NO_STRINGS; + for (int i = 0; i < result.length; i++) + result[i] = result[i].trim(); + return result; + } + + String nameListToString(String[] names) { + if (names == null) return ""; //$NON-NLS-1$ + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < names.length; i++) { + if (i > 0) buf.append(','); + buf.append(names[i]); + } + return buf.toString(); + } + public String toString() { StringBuffer buf = new StringBuffer("CompilerOptions:"); //$NON-NLS-1$ buf.append("\n\t- local variables debug attributes: ").append((this.produceDebugAttributes & ClassFileConstants.ATTR_VARS) != 0 ? "ON" : " OFF"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java index 7a08e9c14f..e36c9bc874 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java @@ -400,15 +400,12 @@ public void setTypeAnnotations(AnnotationBinding[] annotations, boolean evalNull for (int i = 0, length = annotations.length; i < length; i++) { AnnotationBinding annotation = annotations[i]; if (annotation != null) { - switch (annotation.type.id) { - case TypeIds.T_ConfiguredAnnotationNullable : - nullTagBits |= TagBits.AnnotationNullable; - this.tagBits |= TagBits.HasNullTypeAnnotation; - break; - case TypeIds.T_ConfiguredAnnotationNonNull : - nullTagBits |= TagBits.AnnotationNonNull; - this.tagBits |= TagBits.HasNullTypeAnnotation; - break; + if (annotation.type.hasNullBit(TypeIds.BitNullableAnnotation)) { + nullTagBits |= TagBits.AnnotationNullable; + this.tagBits |= TagBits.HasNullTypeAnnotation; + } else if (annotation.type.hasNullBit(TypeIds.BitNonNullAnnotation)) { + nullTagBits |= TagBits.AnnotationNonNull; + this.tagBits |= TagBits.HasNullTypeAnnotation; } } else { // null signals end of annotations for the current dimension in the serialized form. diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java index 5330998195..a68bba5194 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java @@ -554,10 +554,9 @@ private ITypeAnnotationWalker getTypeAnnotationWalker(IBinaryTypeAnnotation[] an private int getNullDefaultFrom(IBinaryAnnotation[] declAnnotations) { if (declAnnotations != null) { - char[][] nonNullByDefaultAnnotationName = this.environment.getNonNullByDefaultAnnotationName(); for (IBinaryAnnotation annotation : declAnnotations) { char[][] typeName = signature2qualifiedTypeName(annotation.getTypeName()); - if (CharOperation.equals(typeName, nonNullByDefaultAnnotationName)) + if (this.environment.getNullAnnotationBit(typeName) == TypeIds.BitNonNullByDefaultAnnotation) return getNonNullByDefaultValue(annotation); } } @@ -1531,10 +1530,6 @@ private void scanFieldForNullAnnotation(IBinaryField field, FieldBinding fieldBi } // global option is checked by caller - char[][] nullableAnnotationName = this.environment.getNullableAnnotationName(); - char[][] nonNullAnnotationName = this.environment.getNonNullAnnotationName(); - if (nullableAnnotationName == null || nonNullAnnotationName == null) - return; // not well-configured to use null annotations if (fieldBinding.type == null || fieldBinding.type.isBaseType()) return; // null annotations are only applied to reference types @@ -1548,13 +1543,13 @@ private void scanFieldForNullAnnotation(IBinaryField field, FieldBinding fieldBi char[] annotationTypeName = annotations[i].getTypeName(); if (annotationTypeName[0] != Util.C_RESOLVED) continue; - char[][] typeName = signature2qualifiedTypeName(annotationTypeName); - if (CharOperation.equals(typeName, nonNullAnnotationName)) { + int typeBit = this.environment.getNullAnnotationBit(signature2qualifiedTypeName(annotationTypeName)); + if (typeBit == TypeIds.BitNonNullAnnotation) { fieldBinding.tagBits |= TagBits.AnnotationNonNull; explicitNullness = true; break; } - if (CharOperation.equals(typeName, nullableAnnotationName)) { + if (typeBit == TypeIds.BitNullableAnnotation) { fieldBinding.tagBits |= TagBits.AnnotationNullable; explicitNullness = true; break; @@ -1588,11 +1583,6 @@ private void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding met return; } } - char[][] nullableAnnotationName = this.environment.getNullableAnnotationName(); - char[][] nonNullAnnotationName = this.environment.getNonNullAnnotationName(); - char[][] nonNullByDefaultAnnotationName = this.environment.getNonNullByDefaultAnnotationName(); - if (nullableAnnotationName == null || nonNullAnnotationName == null || nonNullByDefaultAnnotationName == null) - return; // not well-configured to use null annotations // return: ITypeAnnotationWalker returnWalker = externalAnnotationWalker.toMethodReturn(); @@ -1604,16 +1594,21 @@ private void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding met char[] annotationTypeName = annotations[i].getTypeName(); if (annotationTypeName[0] != Util.C_RESOLVED) continue; - char[][] typeName = signature2qualifiedTypeName(annotationTypeName); - if (CharOperation.equals(typeName, nonNullByDefaultAnnotationName)) { + int typeBit = this.environment.getNullAnnotationBit(signature2qualifiedTypeName(annotationTypeName)); + if (typeBit == TypeIds.BitNonNullByDefaultAnnotation) { methodBinding.defaultNullness = getNonNullByDefaultValue(annotations[i]); - if (methodBinding.defaultNullness == Binding.NULL_UNSPECIFIED_BY_DEFAULT) + if (methodBinding.defaultNullness == Binding.NULL_UNSPECIFIED_BY_DEFAULT) { methodBinding.tagBits |= TagBits.AnnotationNullUnspecifiedByDefault; - else if (methodBinding.defaultNullness != 0) + } else if (methodBinding.defaultNullness != 0) { methodBinding.tagBits |= TagBits.AnnotationNonNullByDefault; - } else if (CharOperation.equals(typeName, nonNullAnnotationName)) { + if (methodBinding.defaultNullness == Binding.NONNULL_BY_DEFAULT && this.environment.usesNullTypeAnnotations()) { + // reading a decl-nnbd in a project using type annotations, mimic corresponding semantics by enumerating: + methodBinding.defaultNullness |= Binding.DefaultLocationParameter | Binding.DefaultLocationReturnType; + } + } + } else if (typeBit == TypeIds.BitNonNullAnnotation) { methodBinding.tagBits |= TagBits.AnnotationNonNull; - } else if (CharOperation.equals(typeName, nullableAnnotationName)) { + } else if (typeBit == TypeIds.BitNullableAnnotation) { methodBinding.tagBits |= TagBits.AnnotationNullable; } } @@ -1638,13 +1633,13 @@ private void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding met char[] annotationTypeName = paramAnnotations[i].getTypeName(); if (annotationTypeName[0] != Util.C_RESOLVED) continue; - char[][] typeName = signature2qualifiedTypeName(annotationTypeName); - if (CharOperation.equals(typeName, nonNullAnnotationName)) { + int typeBit = this.environment.getNullAnnotationBit(signature2qualifiedTypeName(annotationTypeName)); + if (typeBit == TypeIds.BitNonNullAnnotation) { if (methodBinding.parameterNonNullness == null) methodBinding.parameterNonNullness = new Boolean[numVisibleParams]; methodBinding.parameterNonNullness[j] = Boolean.TRUE; break; - } else if (CharOperation.equals(typeName, nullableAnnotationName)) { + } else if (typeBit == TypeIds.BitNullableAnnotation) { if (methodBinding.parameterNonNullness == null) methodBinding.parameterNonNullness = new Boolean[numVisibleParams]; methodBinding.parameterNonNullness[j] = Boolean.FALSE; @@ -1676,14 +1671,18 @@ private void scanTypeForNullDefaultAnnotation(IBinaryType binaryType, PackageBin char[] annotationTypeName = annotations[i].getTypeName(); if (annotationTypeName[0] != Util.C_RESOLVED) continue; - char[][] typeName = signature2qualifiedTypeName(annotationTypeName); - if (CharOperation.equals(typeName, nonNullByDefaultAnnotationName)) { + int typeBit = this.environment.getNullAnnotationBit(signature2qualifiedTypeName(annotationTypeName)); + if (typeBit == TypeIds.BitNonNullByDefaultAnnotation) { // using NonNullByDefault we need to inspect the details of the value() attribute: nullness = getNonNullByDefaultValue(annotations[i]); if (nullness == NULL_UNSPECIFIED_BY_DEFAULT) { annotationBit = TagBits.AnnotationNullUnspecifiedByDefault; } else if (nullness != 0) { annotationBit = TagBits.AnnotationNonNullByDefault; + if (nullness == Binding.NONNULL_BY_DEFAULT && this.environment.usesNullTypeAnnotations()) { + // reading a decl-nnbd in a project using type annotations, mimic corresponding semantics by enumerating: + nullness |= Binding.DefaultLocationParameter | Binding.DefaultLocationReturnType | Binding.DefaultLocationField; + } } this.defaultNullness = nullness; break; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java index 8882848fd2..1d5950dea8 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java @@ -109,6 +109,8 @@ public class LookupEnvironment implements ProblemReasons, TypeConstants { AnnotationBinding nonNullAnnotation; AnnotationBinding nullableAnnotation; + Map<String,Integer> allNullAnnotations = null; + final List<MethodBinding> deferredEnumMethods = new ArrayList<>(); // during early initialization we cannot mark Enum-methods as nonnull. /** Global access to the outermost active inference context as the universe for inference variable interning. */ @@ -1003,9 +1005,10 @@ public TypeBinding createAnnotatedType(TypeBinding type, AnnotationBinding[] new continue; } long tagBits = 0; - switch (newbies[i].type.id) { - case TypeIds.T_ConfiguredAnnotationNonNull : tagBits = TagBits.AnnotationNonNull; break; - case TypeIds.T_ConfiguredAnnotationNullable : tagBits = TagBits.AnnotationNullable; break; + if (newbies[i].type.hasNullBit(TypeIds.BitNonNullAnnotation)) { + tagBits = TagBits.AnnotationNonNull; + } else if (newbies[i].type.hasNullBit(TypeIds.BitNullableAnnotation)) { + tagBits = TagBits.AnnotationNullable; } if ((tagBitsSeen & tagBits) == 0) { tagBitsSeen |= tagBits; @@ -1109,6 +1112,27 @@ public char[][] getNonNullByDefaultAnnotationName() { return this.globalOptions.nonNullByDefaultAnnotationName; } +int getNullAnnotationBit(char[][] qualifiedTypeName) { + if (this.allNullAnnotations == null) { + this.allNullAnnotations = new HashMap<>(); + this.allNullAnnotations.put(CharOperation.toString(this.globalOptions.nonNullAnnotationName), TypeIds.BitNonNullAnnotation); + this.allNullAnnotations.put(CharOperation.toString(this.globalOptions.nullableAnnotationName), TypeIds.BitNullableAnnotation); + this.allNullAnnotations.put(CharOperation.toString(this.globalOptions.nonNullByDefaultAnnotationName), TypeIds.BitNonNullByDefaultAnnotation); + for (String name : this.globalOptions.nullableAnnotationSecondaryNames) + this.allNullAnnotations.put(name, TypeIds.BitNullableAnnotation); + for (String name : this.globalOptions.nonNullAnnotationSecondaryNames) + this.allNullAnnotations.put(name, TypeIds.BitNonNullAnnotation); + for (String name : this.globalOptions.nonNullByDefaultAnnotationSecondaryNames) + this.allNullAnnotations.put(name, TypeIds.BitNonNullByDefaultAnnotation); + } + String qualifiedTypeString = CharOperation.toString(qualifiedTypeName); + Integer typeBit = this.allNullAnnotations.get(qualifiedTypeString); + return typeBit == null ? 0 : typeBit; +} +public boolean isNullnessAnnotationPackage(PackageBinding pkg) { + return this.nonnullAnnotationPackage == pkg || this.nullableAnnotationPackage == pkg || this.nonnullByDefaultAnnotationPackage == pkg; +} + public boolean usesNullTypeAnnotations() { if (this.globalOptions.useNullTypeAnnotations != null) return this.globalOptions.useNullTypeAnnotations; @@ -1695,8 +1719,7 @@ public AnnotationBinding[] filterNullTypeAnnotations(AnnotationBinding[] typeAnn if (typeAnnotation == null) { count++; // sentinel in annotation sequence for array dimensions } else { - int id = typeAnnotation.type.id; - if (id != TypeIds.T_ConfiguredAnnotationNonNull && id != TypeIds.T_ConfiguredAnnotationNullable) + if (!typeAnnotation.type.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) filtered[count++] = typeAnnotation; } } @@ -1711,15 +1734,13 @@ public AnnotationBinding[] filterNullTypeAnnotations(AnnotationBinding[] typeAnn public boolean containsNullTypeAnnotation(IBinaryAnnotation[] typeAnnotations) { if (typeAnnotations.length == 0) return false; - char[][] nonNullAnnotationName = this.getNonNullAnnotationName(); - char[][] nullableAnnotationName = this.getNullableAnnotationName(); for (int i = 0; i < typeAnnotations.length; i++) { IBinaryAnnotation typeAnnotation = typeAnnotations[i]; char[] typeName = typeAnnotation.getTypeName(); // typeName must be "Lfoo/X;" if (typeName == null || typeName.length < 3 || typeName[0] != 'L') continue; char[][] name = CharOperation.splitOn('/', typeName, 1, typeName.length-1); - if (CharOperation.equals(name, nonNullAnnotationName) || CharOperation.equals(name, nullableAnnotationName)) + if (getNullAnnotationBit(name) != 0) return true; } return false; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java index a182e1fa29..78325ab451 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2013 IBM Corporation and others. + * Copyright (c) 2000, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -273,22 +273,24 @@ private boolean isPackageOfQualifiedTypeName(char[][] packageName, char[][] type void checkIfNullAnnotationType(ReferenceBinding type) { // check if type is one of the configured null annotation types - // if so mark as a well known type using the corresponding typeID: + // if so mark as a well known type using the corresponding typeBit: if (this.environment.nullableAnnotationPackage == this && CharOperation.equals(type.compoundName, this.environment.getNullableAnnotationName())) { - type.id = TypeIds.T_ConfiguredAnnotationNullable; + type.typeBits |= TypeIds.BitNullableAnnotation; if (!(type instanceof UnresolvedReferenceBinding)) // unresolved will need to check back for the resolved type this.environment.nullableAnnotationPackage = null; // don't check again } else if (this.environment.nonnullAnnotationPackage == this && CharOperation.equals(type.compoundName, this.environment.getNonNullAnnotationName())) { - type.id = TypeIds.T_ConfiguredAnnotationNonNull; + type.typeBits |= TypeIds.BitNonNullAnnotation; if (!(type instanceof UnresolvedReferenceBinding)) // unresolved will need to check back for the resolved type this.environment.nonnullAnnotationPackage = null; // don't check again } else if (this.environment.nonnullByDefaultAnnotationPackage == this && CharOperation.equals(type.compoundName, this.environment.getNonNullByDefaultAnnotationName())) { - type.id = TypeIds.T_ConfiguredAnnotationNonNullByDefault; + type.typeBits |= TypeIds.BitNonNullByDefaultAnnotation; if (!(type instanceof UnresolvedReferenceBinding)) // unresolved will need to check back for the resolved type this.environment.nonnullByDefaultAnnotationPackage = null; // don't check again + } else { + type.typeBits |= this.environment.getNullAnnotationBit(type.compoundName); } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java index 6684dba973..569bc996e2 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java @@ -1154,6 +1154,11 @@ public final boolean hasRestrictedAccess() { return (this.modifiers & ExtraCompilerModifiers.AccRestrictedAccess) != 0; } +/** Query typeBits without triggering supertype lookup. */ +public boolean hasNullBit(int mask) { + return (this.typeBits & mask) != 0; +} + /** Answer true if the receiver implements anInterface or is identical to anInterface. * If searchHierarchy is true, then also search the receiver's superclasses. * @@ -1649,8 +1654,8 @@ protected void appendNullAnnotation(StringBuffer nameBuffer, CompilerOptions opt if (options.isAnnotationBasedNullAnalysisEnabled) { if (options.usesNullTypeAnnotations()) { for (AnnotationBinding annotation : this.typeAnnotations) { - TypeBinding annotationType = annotation.getAnnotationType(); - if (annotationType.id == TypeIds.T_ConfiguredAnnotationNonNull || annotation.type.id == TypeIds.T_ConfiguredAnnotationNullable) { + ReferenceBinding annotationType = annotation.getAnnotationType(); + if (annotationType.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { nameBuffer.append('@').append(annotationType.shortReadableName()).append(' '); } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java index 480cdd6780..75535967a1 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java @@ -2033,8 +2033,7 @@ public void evaluateNullAnnotations() { for (int i = 0; i < annotations.length; i++) {
ReferenceBinding annotationType = annotations[i].getCompilerAnnotation().getAnnotationType();
if (annotationType != null) {
- if (annotationType.id == TypeIds.T_ConfiguredAnnotationNonNull
- || annotationType.id == TypeIds.T_ConfiguredAnnotationNullable) {
+ if (annotationType.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) {
this.scope.problemReporter().nullAnnotationUnsupportedLocation(annotations[i]);
this.tagBits &= ~TagBits.AnnotationNullMASK;
}
@@ -2046,10 +2045,7 @@ public void evaluateNullAnnotations() { PackageBinding pkg = getPackage();
boolean isInDefaultPkg = (pkg.compoundName == CharOperation.NO_CHAR_CHAR);
if (!isPackageInfo) {
- boolean isInNullnessAnnotationPackage =
- pkg == this.scope.environment().nonnullAnnotationPackage
- || pkg == this.scope.environment().nullableAnnotationPackage
- || pkg == this.scope.environment().nonnullByDefaultAnnotationPackage;
+ boolean isInNullnessAnnotationPackage = this.scope.environment().isNullnessAnnotationPackage(pkg);
if (pkg.defaultNullness == NO_NULL_DEFAULT && !isInDefaultPkg && !isInNullnessAnnotationPackage && !(this instanceof NestedTypeBinding)) {
ReferenceBinding packageInfo = pkg.getType(TypeConstants.PACKAGE_INFO_NAME);
if (packageInfo == null) {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java index 52c8148b61..29fe3dfa57 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java @@ -1526,14 +1526,10 @@ public void setTypeAnnotations(AnnotationBinding[] annotations, boolean evalNull for (int i = 0, length = annotations.length; i < length; i++) { AnnotationBinding annotation = annotations[i]; if (annotation != null) { - switch (annotation.type.id) { - case TypeIds.T_ConfiguredAnnotationNullable : - this.tagBits |= TagBits.AnnotationNullable | TagBits.HasNullTypeAnnotation; - break; - case TypeIds.T_ConfiguredAnnotationNonNull : - this.tagBits |= TagBits.AnnotationNonNull | TagBits.HasNullTypeAnnotation; - break; - } + if (annotation.type.hasNullBit(TypeIds.BitNullableAnnotation)) + this.tagBits |= TagBits.AnnotationNullable | TagBits.HasNullTypeAnnotation; + else if (annotation.type.hasNullBit(TypeIds.BitNonNullAnnotation)) + this.tagBits |= TagBits.AnnotationNonNull | TagBits.HasNullTypeAnnotation; } } // we do accept contradictory tagBits here, to support detecting contradictions caused by type substitution diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java index 1693a65875..af32c78013 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java @@ -108,10 +108,7 @@ public interface TypeIds { // java 7 java.lang.AutoCloseable final int T_JavaLangAutoCloseable = 62; - // new in 3.8 for null annotations: - final int T_ConfiguredAnnotationNullable = 65; - final int T_ConfiguredAnnotationNonNull = 66; - final int T_ConfiguredAnnotationNonNullByDefault = 67; + // new in 3.8 for null annotations, removed in 4.6 (ids 65-67) // new in 3.8 to identify org.eclipse.core.runtime.Assert final int T_OrgEclipseCoreRuntimeAssert = 68; @@ -247,6 +244,14 @@ public interface TypeIds { final int BitResourceFreeCloseable = 8; final int BitUninternedType = 16; + + /** Bit for a type configured as a @NonNull annotation. */ + final int BitNonNullAnnotation = 32; + /** Bit for a type configured as a @Nullable annotation. */ + final int BitNullableAnnotation = 64; + /** Bit for a type configured as a @NonNullByDefault annotation. */ + final int BitNonNullByDefaultAnnotation = 128; + /** * Set of type bits that should be inherited by any sub types. */ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java index 7094b485f4..f3f50e2757 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java @@ -8776,9 +8776,6 @@ private boolean excludeDueToAnnotation(Annotation[] annotations, int problemId) case TypeIds.T_JavaLangSuppressWarnings: case TypeIds.T_JavaLangDeprecated: case TypeIds.T_JavaLangSafeVarargs: - case TypeIds.T_ConfiguredAnnotationNonNull: - case TypeIds.T_ConfiguredAnnotationNullable: - case TypeIds.T_ConfiguredAnnotationNonNullByDefault: break; case TypeIds.T_JavaxInjectInject: case TypeIds.T_ComGoogleInjectInject: @@ -8787,8 +8784,10 @@ private boolean excludeDueToAnnotation(Annotation[] annotations, int problemId) return true; // @Inject on method/ctor does constitute a relevant use, just on fields it doesn't break; default: - // non-standard annotation found, don't warn - return true; + if (resolvedType instanceof ReferenceBinding) + if (((ReferenceBinding) resolvedType).hasNullBit(TypeIds.BitNullableAnnotation|TypeIds.BitNonNullAnnotation|TypeIds.BitNonNullByDefaultAnnotation)) + break; + return true; // non-standard annotation found, don't warn } } } @@ -9289,9 +9288,7 @@ public void illegalRedefinitionToNonNullParameter(Argument argument, ReferenceBi if (argument.annotations != null) { for (int i=0; i<argument.annotations.length; i++) { Annotation annotation = argument.annotations[i]; - if ( annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNullable - || annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNonNull) - { + if (annotation.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { sourceStart = annotation.sourceStart; break; } @@ -9342,9 +9339,7 @@ public void illegalParameterRedefinition(Argument argument, ReferenceBinding dec if (argument.annotations != null) { for (int i=0; i<argument.annotations.length; i++) { Annotation annotation = argument.annotations[i]; - if ( annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNullable - || annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNonNull) - { + if (annotation.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { sourceStart = annotation.sourceStart; break; } @@ -9373,7 +9368,7 @@ public void illegalReturnRedefinition(AbstractMethodDeclaration abstractMethodDe .append(inheritedMethod.shortReadableName()); int sourceStart = methodDecl.returnType.sourceStart; Annotation[] annotations = methodDecl.annotations; - Annotation annotation = findAnnotation(annotations, TypeIds.T_ConfiguredAnnotationNullable); + Annotation annotation = findAnnotation(annotations, TypeIds.BitNullableAnnotation); if (annotation != null) { sourceStart = annotation.sourceStart; } @@ -9528,7 +9523,7 @@ public void nullAnnotationIsRedundant(AbstractMethodDeclaration sourceMethod, in int sourceStart, sourceEnd; if (i == -1) { MethodDeclaration methodDecl = (MethodDeclaration) sourceMethod; - Annotation annotation = findAnnotation(methodDecl.annotations, TypeIds.T_ConfiguredAnnotationNonNull); + Annotation annotation = findAnnotation(methodDecl.annotations, TypeIds.BitNonNullAnnotation); sourceStart = annotation != null ? annotation.sourceStart : methodDecl.returnType.sourceStart; sourceEnd = methodDecl.returnType.sourceEnd; } else { @@ -9540,14 +9535,14 @@ public void nullAnnotationIsRedundant(AbstractMethodDeclaration sourceMethod, in } public void nullAnnotationIsRedundant(FieldDeclaration sourceField) { - Annotation annotation = findAnnotation(sourceField.annotations, TypeIds.T_ConfiguredAnnotationNonNull); + Annotation annotation = findAnnotation(sourceField.annotations, TypeIds.BitNonNullAnnotation); int sourceStart = annotation != null ? annotation.sourceStart : sourceField.type.sourceStart; int sourceEnd = sourceField.type.sourceEnd; this.handle(IProblem.RedundantNullAnnotation, ProblemHandler.NoArgument, ProblemHandler.NoArgument, sourceStart, sourceEnd); } public void nullDefaultAnnotationIsRedundant(ASTNode location, Annotation[] annotations, Binding outer) { - Annotation annotation = findAnnotation(annotations, TypeIds.T_ConfiguredAnnotationNonNullByDefault); + Annotation annotation = findAnnotation(annotations, TypeIds.BitNonNullByDefaultAnnotation); int start = annotation != null ? annotation.sourceStart : location.sourceStart; int end = annotation != null ? annotation.sourceEnd : location.sourceStart; String[] args = NoArgument; @@ -9667,13 +9662,13 @@ public void conflictingInheritedNullAnnotations(ASTNode location, boolean previo public void illegalAnnotationForBaseType(TypeReference type, Annotation[] annotations, long nullAnnotationTagBit) { - int typeId = (nullAnnotationTagBit == TagBits.AnnotationNullable) - ? TypeIds.T_ConfiguredAnnotationNullable : TypeIds.T_ConfiguredAnnotationNonNull; + int typeBit = (nullAnnotationTagBit == TagBits.AnnotationNullable) + ? TypeIds.BitNullableAnnotation : TypeIds.BitNonNullAnnotation; char[][] annotationNames = (nullAnnotationTagBit == TagBits.AnnotationNonNull) ? this.options.nonNullAnnotationName : this.options.nullableAnnotationName; String[] args = new String[] { new String(annotationNames[annotationNames.length-1]), new String(type.getLastToken()) }; - Annotation annotation = findAnnotation(annotations, typeId); + Annotation annotation = findAnnotation(annotations, typeBit); int start = annotation != null ? annotation.sourceStart : type.sourceStart; int end = annotation != null ? annotation.sourceEnd : type.sourceEnd; this.handle(IProblem.IllegalAnnotationForBaseType, @@ -9735,12 +9730,12 @@ String internalAnnotatedTypeName(char[] annotationName, char[] typeName, int dim } return String.valueOf(fullName); } -private Annotation findAnnotation(Annotation[] annotations, int typeId) { +private Annotation findAnnotation(Annotation[] annotations, int typeBit) { if (annotations != null) { // should have a @NonNull/@Nullable annotation, search for it: int length = annotations.length; for (int j=0; j<length; j++) { - if (annotations[j].resolvedType != null && annotations[j].resolvedType.id == typeId) { + if (annotations[j].hasNullBit(typeBit)) { return annotations[j]; } } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java index 2d390392fd..59ed127ffc 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java @@ -1545,6 +1545,29 @@ public final class JavaCore extends Plugin { */ public static final String COMPILER_NULLABLE_ANNOTATION_NAME = PLUGIN_ID + ".compiler.annotation.nullable"; //$NON-NLS-1$ /** + * Compiler option ID: Names of Secondary Annotation Types for Nullable Types. + * <p>This option defines a comma-separated list of fully qualified Java type names + * that the compiler may use to perform special null analysis.</p> + * <p>The annotation types identified by the names in this list are interpreted in the same way + * as the annotation identified by {@link #COMPILER_NULLABLE_ANNOTATION_NAME}. + * The intention is to support libraries using different sets of null annotations, + * in addition to those used by the current project. Secondary null annotations should not be + * used in the project's own source code.</p> + * <p>JDT will never actively use any secondary annotation names from this list, + * i.e., inferred null annotations and content assist proposals mentioning null annotations + * are always rendered using the primary name from {@link #COMPILER_NULLABLE_ANNOTATION_NAME}.</p> + * <p>This option only has an effect if the option {@link #COMPILER_ANNOTATION_NULL_ANALYSIS} is enabled.</p> + * <dl> + * <dt>Option id:</dt><dd><code>"org.eclipse.jdt.core.compiler.annotation.nullable.secondary"</code></dd> + * <dt>Possible values:</dt><dd>a comma-separated list of legal, fully qualified Java type names; + * each name in the list must resolve to an annotation type.</dd> + * <dt>Default:</dt><dd><code>""</code></dd> + * </dl> + * @since 3.12 + * @category CompilerOptionID + */ + public static final String COMPILER_NULLABLE_ANNOTATION_SECONDARY_NAMES = PLUGIN_ID + ".compiler.annotation.nullable.secondary"; //$NON-NLS-1$ + /** * Compiler option ID: Name of Annotation Type for Non-Null Types. * <p>This option defines a fully qualified Java type name that the compiler may use * to perform special null analysis.</p> @@ -1571,6 +1594,29 @@ public final class JavaCore extends Plugin { */ public static final String COMPILER_NONNULL_ANNOTATION_NAME = PLUGIN_ID + ".compiler.annotation.nonnull"; //$NON-NLS-1$ /** + * Compiler option ID: Names of Secondary Annotation Types for Non-Null Types. + * <p>This option defines a comma-separated list of fully qualified Java type names + * that the compiler may use to perform special null analysis.</p> + * <p>The annotation types identified by the names in this list are interpreted in the same way + * as the annotation identified by {@link #COMPILER_NONNULL_ANNOTATION_NAME}. + * The intention is to support libraries using different sets of null annotations, + * in addition to those used by the current project. Secondary null annotations should not be + * used in the project's own source code.</p> + * <p>JDT will never actively use any secondary annotation names from this list, + * i.e., inferred null annotations and content assist proposals mentioning null annotations + * are always rendered using the primary name from {@link #COMPILER_NONNULL_ANNOTATION_NAME}.</p> + * <p>This option only has an effect if the option {@link #COMPILER_ANNOTATION_NULL_ANALYSIS} is enabled.</p> + * <dl> + * <dt>Option id:</dt><dd><code>"org.eclipse.jdt.core.compiler.annotation.nonnull.secondary"</code></dd> + * <dt>Possible values:</dt><dd>a comma-separated list of legal, fully qualified Java type names; + * each name in the list must resolve to an annotation type.</dd> + * <dt>Default:</dt><dd><code>""</code></dd> + * </dl> + * @since 3.12 + * @category CompilerOptionID + */ + public static final String COMPILER_NONNULL_ANNOTATION_SECONDARY_NAMES = PLUGIN_ID + ".compiler.annotation.nonnull.secondary"; //$NON-NLS-1$ + /** * Compiler option ID: Name of Annotation Type to specify a nullness default for unannotated types. * <p>This option defines a fully qualified Java type name that the compiler may use * to perform special null analysis.</p> @@ -1591,6 +1637,26 @@ public final class JavaCore extends Plugin { */ public static final String COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME = PLUGIN_ID + ".compiler.annotation.nonnullbydefault"; //$NON-NLS-1$ /** + * Compiler option ID: Names of Secondary Annotation Types to specify a nullness default for unannotated types. + * <p>This option defines a comma-separated list of fully qualified Java type names + * that the compiler may use to perform special null analysis.</p> + * <p>The annotation types identified by the names in this list are interpreted in the same way + * as the annotation identified by {@link #COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME}. + * The intention is to support libraries using different sets of null annotations, + * in addition to those used by the current project. Secondary null annotations should not be + * used in the project's own source code.</p> + * <p>This option only has an effect if the option {@link #COMPILER_ANNOTATION_NULL_ANALYSIS} is enabled.</p> + * <dl> + * <dt>Option id:</dt><dd><code>"org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary"</code></dd> + * <dt>Possible values:</dt><dd>a comma-separated list of legal, fully qualified Java type names; + * each name in the list must resolve to an annotation type.</dd> + * <dt>Default:</dt><dd><code>""</code></dd> + * </dl> + * @since 3.12 + * @category CompilerOptionID + */ + public static final String COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_SECONDARY_NAMES = PLUGIN_ID + ".compiler.annotation.nonnullbydefault.secondary"; //$NON-NLS-1$ + /** * Compiler option ID: Reporting missing default nullness annotation. * <p>When enabled, the compiler will issue an error or a warning in the following cases:</p> * <ul> |