Bug 499716: Quick fix "change return type to @NonNull" generates
exception
- tests & implementation
Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de>
Change-Id: Id0910af7c35b51e31e159ecdcda631af93acbb0f
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest.java
index ba3d9b3..b386314 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest.java
@@ -1210,17 +1210,16 @@
proposal= (CUCorrectionProposal)proposals.get(1);
- assertEqualString(proposal.getDisplayString(), "Change parameter 'o' to '@NonNull'");
+ assertEqualString(proposal.getDisplayString(), "Change parameter in overridden 'foo(..)' to '@NonNull'");
preview= getPreviewContent(proposal);
buf= new StringBuffer();
buf.append("package test1;\n");
buf.append("import org.eclipse.jdt.annotation.*;\n");
- buf.append("@NonNullByDefault\n");
- buf.append("public class E2 extends E {\n");
+ buf.append("public class E {\n");
buf.append(" void foo(@NonNull Object o) {\n");
- buf.append(" System.out.print(\"E2\");\n");
+ buf.append(" // nop\n");
buf.append(" }\n");
buf.append("}\n");
assertEqualString(preview, buf.toString());
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest18.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest18.java
new file mode 100644
index 0000000..794f6c1
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/NullAnnotationsQuickFixTest18.java
@@ -0,0 +1,456 @@
+/*******************************************************************************
+ * Copyright (c) 2017 GK Software AG 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stephan Herrmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.ui.tests.quickfix;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+import org.osgi.framework.Bundle;
+
+import org.eclipse.jdt.testplugin.JavaProjectHelper;
+import org.eclipse.jdt.testplugin.TestOptions;
+
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
+
+import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
+import org.eclipse.jdt.internal.corext.template.java.CodeTemplateContextType;
+
+import org.eclipse.jdt.ui.PreferenceConstants;
+import org.eclipse.jdt.ui.tests.core.Java18ProjectTestSetup;
+import org.eclipse.jdt.ui.tests.core.ProjectTestSetup;
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+import org.eclipse.jdt.ui.text.java.correction.CUCorrectionProposal;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class NullAnnotationsQuickFixTest18 extends QuickFixTest {
+
+ private static final Class<NullAnnotationsQuickFixTest18> THIS= NullAnnotationsQuickFixTest18.class;
+ private IJavaProject fJProject1;
+ private IPackageFragmentRoot fSourceFolder;
+ private String ANNOTATION_JAR_PATH;
+
+ public NullAnnotationsQuickFixTest18(String name) {
+ super(name);
+ }
+
+ public static Test suite() {
+ return new Java18ProjectTestSetup(new TestSuite(THIS));
+ }
+
+ public static Test setUpTest(Test test) {
+ return new Java18ProjectTestSetup(test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ Hashtable<String, String> options= TestOptions.getDefaultOptions();
+ options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE);
+ options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "4");
+ options.put(DefaultCodeFormatterConstants.FORMATTER_NUMBER_OF_EMPTY_LINES_TO_PRESERVE, String.valueOf(99));
+ options.put(JavaCore.COMPILER_PB_STATIC_ACCESS_RECEIVER, JavaCore.ERROR);
+ options.put(JavaCore.COMPILER_PB_UNCHECKED_TYPE_OPERATION, JavaCore.IGNORE);
+ options.put(JavaCore.COMPILER_PB_MISSING_HASHCODE_METHOD, JavaCore.WARNING);
+ options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
+ options.put(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION, JavaCore.ERROR);
+ options.put(JavaCore.COMPILER_PB_NULL_REFERENCE, JavaCore.ERROR);
+ options.put(JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE, JavaCore.WARNING);
+ options.put(JavaCore.COMPILER_PB_NULL_ANNOTATION_INFERENCE_CONFLICT, JavaCore.WARNING);
+ options.put(JavaCore.COMPILER_PB_NULL_UNCHECKED_CONVERSION, JavaCore.WARNING);
+ options.put(JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK, JavaCore.WARNING);
+ options.put(JavaCore.COMPILER_PB_NULL_UNCHECKED_CONVERSION, JavaCore.WARNING);
+
+ JavaCore.setOptions(options);
+
+ IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore();
+ store.setValue(PreferenceConstants.CODEGEN_ADD_COMMENTS, false);
+
+ StubUtility.setCodeTemplate(CodeTemplateContextType.CATCHBLOCK_ID, "", null);
+ StubUtility.setCodeTemplate(CodeTemplateContextType.CONSTRUCTORSTUB_ID, "", null);
+ StubUtility.setCodeTemplate(CodeTemplateContextType.METHODSTUB_ID, "", null);
+
+ fJProject1= Java18ProjectTestSetup.getProject();
+
+ if (this.ANNOTATION_JAR_PATH == null) {
+ String version= "[2.0.0,3.0.0)"; // tests run at 1.8, need the "new" null annotations
+ Bundle[] bundles= Platform.getBundles("org.eclipse.jdt.annotation", version);
+ File bundleFile= FileLocator.getBundleFile(bundles[0]);
+ if (bundleFile.isDirectory())
+ this.ANNOTATION_JAR_PATH= bundleFile.getPath() + "/bin";
+ else
+ this.ANNOTATION_JAR_PATH= bundleFile.getPath();
+ }
+ JavaProjectHelper.addLibrary(fJProject1, new Path(ANNOTATION_JAR_PATH));
+
+ fSourceFolder= JavaProjectHelper.addSourceContainer(fJProject1, "src");
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ JavaProjectHelper.clear(fJProject1, ProjectTestSetup.getDefaultClasspath());
+ }
+
+ /*
+ * Problem: package default nonnull conflicts with inherited nullable (via generic substitution) (implicit vs. implicit)
+ * Location: method return
+ * Fixes:
+ * - change local to nullable (equal)
+ * - change local to nonnull (covariant return)
+ * - change super to nonnull (equal)
+ */
+ public void testBug499716_a() throws Exception {
+ fJProject1.setOption(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED);
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("@org.eclipse.jdt.annotation.NonNullByDefault\n");
+ buf.append("package test1;\n");
+ pack1.createCompilationUnit("package-info.java", buf.toString(), false, null);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type<@Nullable K> {\n");
+ buf.append(" @NonNullByDefault(DefaultLocation.RETURN_TYPE)\n"); // no effect
+ buf.append(" K get();\n");
+ buf.append("\n");
+ buf.append(" class U implements Type<@Nullable String> {\n");
+ buf.append(" @Override\n");
+ buf.append(" public String get() { // <-- error \"The default '@NonNull' conflicts...\"\n");
+ buf.append(" return \"\";\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("Test.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList<IJavaCompletionProposal> proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 4);
+ CUCorrectionProposal proposal= (CUCorrectionProposal)proposals.get(0);
+
+ assertEqualString(proposal.getDisplayString(), "Change return type of 'get(..)' to '@Nullable'");
+
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type<@Nullable K> {\n");
+ buf.append(" @NonNullByDefault(DefaultLocation.RETURN_TYPE)\n");
+ buf.append(" K get();\n");
+ buf.append("\n");
+ buf.append(" class U implements Type<@Nullable String> {\n");
+ buf.append(" @Override\n");
+ buf.append(" public @Nullable String get() { // <-- error \"The default '@NonNull' conflicts...\"\n");
+ buf.append(" return \"\";\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+
+ proposal= (CUCorrectionProposal)proposals.get(1);
+
+ assertEqualString(proposal.getDisplayString(), "Change return type of 'get(..)' to '@NonNull'");
+
+ preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type<@Nullable K> {\n");
+ buf.append(" @NonNullByDefault(DefaultLocation.RETURN_TYPE)\n");
+ buf.append(" K get();\n");
+ buf.append("\n");
+ buf.append(" class U implements Type<@Nullable String> {\n");
+ buf.append(" @Override\n");
+ buf.append(" public @NonNull String get() { // <-- error \"The default '@NonNull' conflicts...\"\n");
+ buf.append(" return \"\";\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+
+ proposal= (CUCorrectionProposal)proposals.get(2);
+
+ assertEqualString(proposal.getDisplayString(), "Change return type of overridden 'get(..)' to '@NonNull'");
+
+ preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type<@Nullable K> {\n");
+ buf.append(" @NonNullByDefault(DefaultLocation.RETURN_TYPE)\n");
+ buf.append(" @NonNull\n");
+ buf.append(" K get();\n");
+ buf.append("\n");
+ buf.append(" class U implements Type<@Nullable String> {\n");
+ buf.append(" @Override\n");
+ buf.append(" public String get() { // <-- error \"The default '@NonNull' conflicts...\"\n");
+ buf.append(" return \"\";\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ /*
+ * Problem: package default nonnull conflicts with inherited nullable (via generic substitution) (implicit vs. implicit)
+ * Location: parameter
+ * Fixes:
+ * - change local to nullable
+ * - change super to nonnull
+ * No covariant parameter!
+ */
+ public void testBug499716_b() throws Exception {
+ fJProject1.setOption(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED);
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("@org.eclipse.jdt.annotation.NonNullByDefault\n");
+ buf.append("package test1;\n");
+ pack1.createCompilationUnit("package-info.java", buf.toString(), false, null);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type<@Nullable K> {\n");
+ buf.append(" void set(int i, K arg);\n");
+ buf.append("\n");
+ buf.append(" class U implements Type<@Nullable String> {\n");
+ buf.append(" @Override\n");
+ buf.append(" public void set(int i, String arg) {\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("Test.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList<IJavaCompletionProposal> proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 3);
+ CUCorrectionProposal proposal= (CUCorrectionProposal)proposals.get(0);
+
+ assertEqualString(proposal.getDisplayString(), "Change parameter 'arg' to '@Nullable'");
+
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type<@Nullable K> {\n");
+ buf.append(" void set(int i, K arg);\n");
+ buf.append("\n");
+ buf.append(" class U implements Type<@Nullable String> {\n");
+ buf.append(" @Override\n");
+ buf.append(" public void set(int i, @Nullable String arg) {\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+
+ proposal= (CUCorrectionProposal)proposals.get(1);
+
+ assertEqualString(proposal.getDisplayString(), "Change parameter in overridden 'set(..)' to '@NonNull'");
+
+ preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type<@Nullable K> {\n");
+ buf.append(" void set(int i, @NonNull K arg);\n");
+ buf.append("\n");
+ buf.append(" class U implements Type<@Nullable String> {\n");
+ buf.append(" @Override\n");
+ buf.append(" public void set(int i, String arg) {\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ /*
+ * Problem: explicit nullable conflicts with inherited nonnull (from type default) (illegal override)
+ * Location: return type
+ * Fixes:
+ * - change local to nonnull
+ * - change super to nullable
+ * No contravariant return!
+ */
+ public void testBug499716_c() throws Exception {
+ fJProject1.setOption(JavaCore.COMPILER_INHERIT_NULL_ANNOTATIONS, JavaCore.ENABLED);
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+
+ StringBuffer buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("@NonNullByDefault(DefaultLocation.RETURN_TYPE)\n");
+ buf.append("interface Type {\n");
+ buf.append(" String get();\n");
+ buf.append("\n");
+ buf.append(" class U implements Type {\n");
+ buf.append(" @Override\n");
+ buf.append(" public @Nullable String get() {\n");
+ buf.append(" return \"\";\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("Test.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList<IJavaCompletionProposal> proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 3);
+ CUCorrectionProposal proposal= (CUCorrectionProposal)proposals.get(0);
+
+ assertEqualString(proposal.getDisplayString(), "Change return type of 'get(..)' to '@NonNull'");
+
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("@NonNullByDefault(DefaultLocation.RETURN_TYPE)\n");
+ buf.append("interface Type {\n");
+ buf.append(" String get();\n");
+ buf.append("\n");
+ buf.append(" class U implements Type {\n");
+ buf.append(" @Override\n");
+ buf.append(" public String get() {\n");
+ buf.append(" return \"\";\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+
+ proposal= (CUCorrectionProposal)proposals.get(1);
+
+ assertEqualString(proposal.getDisplayString(), "Change return type of overridden 'get(..)' to '@Nullable'");
+
+ preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("@NonNullByDefault(DefaultLocation.RETURN_TYPE)\n");
+ buf.append("interface Type {\n");
+ buf.append(" @Nullable\n");
+ buf.append(" String get();\n");
+ buf.append("\n");
+ buf.append(" class U implements Type {\n");
+ buf.append(" @Override\n");
+ buf.append(" public @Nullable String get() {\n");
+ buf.append(" return \"\";\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+
+ /*
+ * Problem: package default nonnull conflicts with declared nullable in super (illegal override)
+ * Location: method parameter
+ * Fixes:
+ * - change local to nullable
+ * - change super to implicit nonnull (by removing to make default apply)
+ * No covariant parameter!
+ */
+ public void testBug499716_d() throws Exception {
+ IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+ StringBuffer buf= new StringBuffer();
+ buf.append("@org.eclipse.jdt.annotation.NonNullByDefault\n");
+ buf.append("package test1;\n");
+ pack1.createCompilationUnit("package-info.java", buf.toString(), true, null);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type {\n");
+ buf.append(" void set(@Nullable String s);\n");
+ buf.append("\n");
+ buf.append(" class U implements Type {\n");
+ buf.append(" @Override\n");
+ buf.append(" public void set(String t) {\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ ICompilationUnit cu= pack1.createCompilationUnit("Test.java", buf.toString(), false, null);
+
+ CompilationUnit astRoot= getASTRoot(cu);
+ ArrayList<IJavaCompletionProposal> proposals= collectCorrections(cu, astRoot);
+ assertNumberOfProposals(proposals, 3);
+ CUCorrectionProposal proposal= (CUCorrectionProposal)proposals.get(0);
+
+ assertEqualString(proposal.getDisplayString(), "Change parameter 't' to '@Nullable'");
+
+ String preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type {\n");
+ buf.append(" void set(@Nullable String s);\n");
+ buf.append("\n");
+ buf.append(" class U implements Type {\n");
+ buf.append(" @Override\n");
+ buf.append(" public void set(@Nullable String t) {\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+
+ proposal= (CUCorrectionProposal)proposals.get(1);
+
+ assertEqualString(proposal.getDisplayString(), "Change parameter in overridden 'set(..)' to '@NonNull'");
+
+ preview= getPreviewContent(proposal);
+
+ buf= new StringBuffer();
+ buf.append("package test1;\n");
+ buf.append("import org.eclipse.jdt.annotation.*;\n");
+ buf.append("\n");
+ buf.append("interface Type {\n");
+ buf.append(" void set(String s);\n");
+ buf.append("\n");
+ buf.append(" class U implements Type {\n");
+ buf.append(" @Override\n");
+ buf.append(" public void set(String t) {\n");
+ buf.append(" }\n");
+ buf.append(" }\n");
+ buf.append("}\n");
+ assertEqualString(preview, buf.toString());
+ }
+}
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsFix.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsFix.java
index 9c9f3d0..883d5ec 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsFix.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsFix.java
@@ -88,7 +88,8 @@
IJavaElement javaElement= compilationUnit.getJavaElement();
String nullableAnnotationName= getNullableAnnotationName(javaElement, false);
String nonNullAnnotationName= getNonNullAnnotationName(javaElement, false);
- Builder builder= new Builder(problem, compilationUnit, nullableAnnotationName, nonNullAnnotationName, /*allowRemove*/true, isArgumentProblem);
+ Builder builder= new Builder(problem, compilationUnit, nullableAnnotationName, nonNullAnnotationName,
+ /*allowRemove*/true, isArgumentProblem, changeKind);
boolean addNonNull= false;
switch (problem.getProblemId()) {
@@ -117,8 +118,12 @@
}
break;
case IProblem.ConflictingNullAnnotations:
+ if (isArgumentProblem && changeKind == ChangeKind.INVERSE) {
+ return null; // cannot redefine @Nullable param to @NonNull
+ }
+ //$FALL-THROUGH$
case IProblem.ConflictingInheritedNullAnnotations:
- if (changeKind == ChangeKind.INVERSE) {
+ if (changeKind == ChangeKind.INVERSE || changeKind == ChangeKind.OVERRIDDEN) {
addNonNull= true;
builder.swapAnnotations();
}
@@ -198,7 +203,7 @@
continue; // problem was filtered out by createCleanUp()
boolean isArgumentProblem= isComplainingAboutArgument(problem.getCoveredNode(compilationUnit));
Builder builder= new Builder(problem, compilationUnit, nullableAnnotationName, nonNullAnnotationName,
- /*allowRemove*/false, isArgumentProblem);
+ /*allowRemove*/false, isArgumentProblem, ChangeKind.LOCAL);
boolean addNonNull= false;
// cf. createNullAnnotationInSignatureFix() but changeKind is constantly LOCAL
switch (problem.getProblemId()) {
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsRewriteOperations.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsRewriteOperations.java
index 8661c75..f7f02bd 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsRewriteOperations.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/NullAnnotationsRewriteOperations.java
@@ -11,6 +11,8 @@
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -19,7 +21,12 @@
import org.eclipse.text.edits.TextEditGroup;
+import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IMemberValuePair;
+import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
@@ -57,8 +64,18 @@
import org.eclipse.jdt.ui.text.java.IProblemLocation;
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+
public class NullAnnotationsRewriteOperations {
+ static final String TYPE_USE_NAME= ElementType.class.getName()+'.'+ElementType.TYPE_USE.name();
+
+ // reduced copy of org.eclipse.jdt.annotation.DefaultLocation:
+ enum DefaultLocation {
+ PARAMETER,
+ RETURN_TYPE
+ }
+
public enum ChangeKind {
LOCAL, // do the normal thing locally in the current method
INVERSE, // insert the inverse annotation in the local method
@@ -68,10 +85,12 @@
static abstract class SignatureAnnotationRewriteOperation extends CompilationUnitRewriteOperation {
// initialized from the Builder:
- CompilationUnit fUnit;
- String fAnnotationToAdd;
- String fAnnotationToRemove;
- boolean fAllowRemove;
+ protected CompilationUnit fUnit;
+ protected String fAnnotationToAdd;
+ protected String fAnnotationToRemove;
+ protected boolean fAllowRemove;
+ protected boolean fUseNullTypeAnnotations;
+ protected boolean fRequireExplicitAnnotation;
// assigned within constructors:
protected String fKey;
protected String fMessage;
@@ -84,6 +103,8 @@
fAnnotationToAdd= builder.fAnnotationToAdd;
fAnnotationToRemove= builder.fAnnotationToRemove;
fAllowRemove= builder.fAllowRemove;
+ fUseNullTypeAnnotations= builder.fUseNullTypeAnnotations;
+ fRequireExplicitAnnotation= builder.requiresExplicitAnnotation();
}
/* A globally unique key that identifies the position being annotated (for avoiding double annotations). */
@@ -117,7 +138,7 @@
return true;
}
- /* Is the given element affected by a @NonNullByDefault. */
+ /* Is the given element affected by a @NonNullByDefault v1.x? */
boolean hasNonNullDefault(IBinding enclosingElement) {
if (!fRemoveIfNonNullByDefault) return false;
IAnnotationBinding[] annotations = enclosingElement.getAnnotations();
@@ -148,6 +169,79 @@
return false;
}
+ /* Is the given element affected by a 308-style @NonNullByDefault? */
+ boolean hasNonNullDefault308(IBinding enclosingElement, int parameterRank, DefaultLocation defaultLocation, boolean recursiveCall) {
+ if (!fRemoveIfNonNullByDefault) return false;
+ if (!recursiveCall) {
+ ITypeBinding affectedType= null;
+ if (enclosingElement instanceof IMethodBinding) {
+ switch (defaultLocation) {
+ case RETURN_TYPE:
+ affectedType= ((IMethodBinding) enclosingElement).getReturnType();
+ break;
+ case PARAMETER:
+ affectedType= ((IMethodBinding) enclosingElement).getParameterTypes()[parameterRank];
+ break;
+ default:
+ // no other locations supported yet (fields?)
+ }
+ } else if (enclosingElement instanceof IVariableBinding) {
+ affectedType= ((IVariableBinding) enclosingElement).getType();
+ }
+ if (affectedType != null) {
+ if (affectedType.isTypeVariable() || affectedType.isWildcardType()) {
+ return false; // not affected by @NonNullByDefault
+ }
+ }
+ }
+ IAnnotationBinding[] annotations= enclosingElement.getAnnotations();
+ for (int i= 0; i < annotations.length; i++) {
+ IAnnotationBinding annot= annotations[i];
+ ITypeBinding annotationType= annot.getAnnotationType();
+ if (annotationType != null && annotationType.getQualifiedName().equals(fNonNullByDefaultName)) {
+ IMemberValuePairBinding[] pairs= annot.getDeclaredMemberValuePairs();
+ if (pairs.length > 0) {
+ // does the default's set of locations contain `defaultLocation`?
+ for (int j= 0; j < pairs.length; j++)
+ if (pairs[j].getKey() == null || pairs[j].getKey().equals("value")) { //$NON-NLS-1$
+ return matchesLocation(pairs[j].getValue(), defaultLocation);
+ }
+ }
+ return true;
+ }
+ }
+ if (enclosingElement instanceof IVariableBinding) {
+ IVariableBinding variable= (IVariableBinding) enclosingElement;
+ enclosingElement= variable.isParameter() ? variable.getDeclaringMethod() : variable.getDeclaringClass();
+ return hasNonNullDefault308(enclosingElement, -1, defaultLocation, true);
+ } else if (enclosingElement instanceof IMethodBinding) {
+ return hasNonNullDefault308(((IMethodBinding)enclosingElement).getDeclaringClass(), -1, defaultLocation, true);
+ } else if (enclosingElement instanceof ITypeBinding) {
+ ITypeBinding typeBinding= (ITypeBinding)enclosingElement;
+ if (typeBinding.isLocal())
+ return hasNonNullDefault308(typeBinding.getDeclaringMethod(), -1, defaultLocation, true);
+ else if (typeBinding.isMember())
+ return hasNonNullDefault308(typeBinding.getDeclaringClass(), -1, defaultLocation, true);
+ else
+ return hasNonNullDefault308(typeBinding.getPackage(), -1, defaultLocation, true);
+ }
+ return false;
+ }
+
+ private boolean matchesLocation(Object value, DefaultLocation location) {
+ if (value instanceof Object[]) {
+ Object[] values= (Object[]) value;
+ for (int i= 0; i < values.length; i++) {
+ if (matchesLocation(values[i], location))
+ return true;
+ }
+ } else if (value instanceof IVariableBinding) {
+ String name= ((IVariableBinding) value).getName();
+ return location.name().equals(name);
+ }
+ return false;
+ }
+
public String getMessage() {
return fMessage;
}
@@ -176,8 +270,12 @@
TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
if (!checkExisting(fBodyDeclaration.modifiers(), listRewrite, group))
return;
- if (hasNonNullDefault(fBodyDeclaration.resolveBinding()))
- return; // should be safe, as in this case checkExisting() should've already produced a change (remove existing annotation).
+ if (!fRequireExplicitAnnotation) {
+ if (fUseNullTypeAnnotations
+ ? hasNonNullDefault308(fBodyDeclaration.resolveBinding(), /*parameterRank*/-1, DefaultLocation.RETURN_TYPE, false)
+ : hasNonNullDefault(fBodyDeclaration.resolveBinding()))
+ return; // should be safe, as in this case checkExisting() should've already produced a change (remove existing annotation).
+ }
Annotation newAnnotation= ast.newMarkerAnnotation();
ImportRewrite importRewrite= cuRewrite.getImportRewrite();
String resolvableName= importRewrite.addImport(fAnnotationToAdd);
@@ -198,6 +296,7 @@
}
private SingleVariableDeclaration fArgument;
+ private int fParameterRank;
// for lambda parameter (can return null):
static ParameterAnnotationRewriteOperation create(LambdaExpression lambda, IndexedParameter parameter, String message, Builder builder) {
@@ -223,6 +322,7 @@
super(builder);
fKey= methodBinding.getKey();
fArgument= (SingleVariableDeclaration) parameters.get(paramIdx);
+ fParameterRank= paramIdx;
fKey+= fArgument.getName().getIdentifier();
fMessage= message;
}
@@ -234,6 +334,12 @@
TextEditGroup group= createTextEditGroup(fMessage, cuRewrite);
if (!checkExisting(fArgument.modifiers(), listRewrite, group))
return;
+ if (!fRequireExplicitAnnotation) {
+ if (fUseNullTypeAnnotations
+ ? hasNonNullDefault308(fArgument.resolveBinding(), fParameterRank, DefaultLocation.PARAMETER, false)
+ : hasNonNullDefault(fArgument.resolveBinding()))
+ return; // should be safe, as in this case checkExisting() should've already produced a change (remove existing annotation).
+ }
Annotation newAnnotation= ast.newMarkerAnnotation();
ImportRewrite importRewrite= cuRewrite.getImportRewrite();
String resolvableName= importRewrite.addImport(fAnnotationToAdd);
@@ -299,21 +405,62 @@
public static class Builder {
IProblemLocation fProblem;
+ ChangeKind fChangeKind;
CompilationUnit fUnit;
String fAnnotationToAdd;
String fAnnotationToRemove;
boolean fAllowRemove;
boolean fAffectsParameter;
+ boolean fUseNullTypeAnnotations;
public Builder(IProblemLocation problem, CompilationUnit unit, String annotationToAdd, String annotationToRemove,
- boolean allowRemove, boolean affectsParameter)
+ boolean allowRemove, boolean affectsParameter, ChangeKind changeKind)
{
- fProblem= problem;
+ fChangeKind= changeKind;
fUnit= unit;
fAnnotationToAdd= annotationToAdd;
fAnnotationToRemove= annotationToRemove;
fAllowRemove= allowRemove;
fAffectsParameter= affectsParameter;
+ fProblem= problem;
+ fUseNullTypeAnnotations= usesNullTypeAnnotations(unit.getJavaElement(), annotationToAdd);
+ }
+
+ private boolean usesNullTypeAnnotations(IJavaElement cu, String annotationName) {
+ IJavaProject project= (IJavaProject) cu.getAncestor(IJavaElement.JAVA_PROJECT);
+ if (!JavaModelUtil.is18OrHigher(project)) {
+ return false;
+ }
+ try {
+ IType annotationType= project.findType(annotationName);
+ if (annotationType == null) {
+ return false;
+ }
+ IAnnotation[] annotations= annotationType.getAnnotations();
+ for (int i= 0; i < annotations.length; i++) {
+ if (annotations[i].getElementName().equals(Target.class.getName())) {
+ for (IMemberValuePair valuePair : annotations[i].getMemberValuePairs()) {
+ if (TYPE_USE_NAME.equals(valuePair.getValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ } catch (JavaModelException e) {
+ JavaPlugin.log(e);
+ }
+ return false;
+ }
+
+ public boolean requiresExplicitAnnotation() {
+ switch (fProblem.getProblemId()) {
+ case IProblem.ConflictingInheritedNullAnnotations:
+ case IProblem.ConflictingNullAnnotations:
+ return fChangeKind != ChangeKind.OVERRIDDEN;
+ default:
+ return false;
+ }
}
public void swapAnnotations() {
@@ -498,6 +645,7 @@
case IProblem.IllegalRedefinitionToNonNullParameter:
break;
case IProblem.IllegalReturnNullityRedefinition:
+ case IProblem.ConflictingNullAnnotations:
if (declaringNode == null)
declaringNode= selectedNode;
break;
@@ -515,16 +663,21 @@
// complaint is in signature of this method
MethodDeclaration declaration= (MethodDeclaration) declaringNode;
switch (fProblem.getProblemId()) {
- case IProblem.IllegalDefinitionToNonNullParameter:
- case IProblem.IllegalRedefinitionToNonNullParameter:
+ case IProblem.IllegalReturnNullityRedefinition:
+ if (!hasNullAnnotation(declaration)) {
+ return null; // don't adjust super if local has no explicit annotation (?)
+ }
+ //$FALL-THROUGH$
+ case IProblem.IllegalDefinitionToNonNullParameter:
+ case IProblem.IllegalRedefinitionToNonNullParameter:
+ case IProblem.ConflictingNullAnnotations:
+ if (fAffectsParameter) {
return createChangeOverriddenParameterOperation(cu, declaration, selectedNode, annotationNameLabel);
- case IProblem.IllegalReturnNullityRedefinition:
- if (hasNullAnnotation(declaration)) { // don't adjust super if local has no explicit annotation (?)
- return createChangeOverriddenReturnOperation(cu, declaration, annotationNameLabel);
- }
- break;
- default:
- return null;
+ } else {
+ return createChangeOverriddenReturnOperation(cu, declaration, annotationNameLabel);
+ }
+ default:
+ return null;
}
}
return null;
@@ -629,6 +782,7 @@
}
private MethodDeclaration findMethodDeclarationInUnit(ICompilationUnit cu, IMethodBinding method, boolean sameUnitOnly) {
+ method= method.getMethodDeclaration();
CompilationUnit compilationUnit= findCUForMethod(fUnit, cu, method);
if (compilationUnit == null)
return null;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java
index c7bd5e1..1c509f8 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickFixProcessor.java
@@ -10,7 +10,7 @@
* Benjamin Muskalla <b.muskalla@gmx.net> - [quick fix] Quick fix for missing synchronized modifier - https://bugs.eclipse.org/bugs/show_bug.cgi?id=245250
* Stephan Herrmann - Contributions for
* [quick fix] Add quick fixes for null annotations - https://bugs.eclipse.org/337977
- * [quick fix] The fix change parameter type to @Nonnull generated a null change - https://bugs.eclipse.org/400668
+ * [quick fix] The fix change parameter type to @Nonnull generated a null change - https://bugs.eclipse.org/400668
* [quick fix] don't propose null annotations when those are disabled - https://bugs.eclipse.org/405086
* [quickfix] Update null annotation quick fixes for bug 388281 - https://bugs.eclipse.org/395555
* Lukas Hanke <hanke@yatta.de> - Bug 241696 [quick fix] quickfix to iterate over a collection - https://bugs.eclipse.org/bugs/show_bug.cgi?id=241696
@@ -773,6 +773,7 @@
case IProblem.ConflictingInheritedNullAnnotations:
NullAnnotationsCorrectionProcessor.addReturnAndArgumentTypeProposal(context, problem, ChangeKind.LOCAL, proposals);
NullAnnotationsCorrectionProcessor.addReturnAndArgumentTypeProposal(context, problem, ChangeKind.INVERSE, proposals);
+ NullAnnotationsCorrectionProcessor.addReturnAndArgumentTypeProposal(context, problem, ChangeKind.OVERRIDDEN, proposals);
break;
case IProblem.IllegalQualifiedEnumConstantLabel:
LocalCorrectionsSubProcessor.addIllegalQualifiedEnumConstantLabelProposal(context, problem, proposals);