Change Signature: Propagate nullability annotations to overriding methods in Java
This commit is contained in:
+80
-4
@@ -16,12 +16,11 @@
|
||||
|
||||
package org.jetbrains.jet.plugin.refactoring.changeSignature;
|
||||
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.refactoring.changeSignature.*;
|
||||
@@ -317,6 +316,75 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
|
||||
return callElement != null ? new JavaMethodKotlinCallUsage(callElement, javaMethodChangeInfo) : null;
|
||||
}
|
||||
|
||||
private static class NullabilityPropagator {
|
||||
private final NullableNotNullManager nullManager;
|
||||
private final JavaPsiFacade javaPsiFacade;
|
||||
private final JavaCodeStyleManager javaCodeStyleManager;
|
||||
private final PsiAnnotation methodAnnotation;
|
||||
private final PsiAnnotation[] parameterAnnotations;
|
||||
|
||||
public NullabilityPropagator(@NotNull PsiMethod baseMethod) {
|
||||
Project project = baseMethod.getProject();
|
||||
this.nullManager = NullableNotNullManager.getInstance(project);
|
||||
this.javaPsiFacade = JavaPsiFacade.getInstance(project);
|
||||
this.javaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
|
||||
|
||||
this.methodAnnotation = getNullabilityAnnotation(baseMethod);
|
||||
this.parameterAnnotations = ContainerUtil.map2Array(
|
||||
baseMethod.getParameterList().getParameters(),
|
||||
PsiAnnotation.class,
|
||||
new Function<PsiParameter, PsiAnnotation>() {
|
||||
@Override
|
||||
public PsiAnnotation fun(PsiParameter parameter) {
|
||||
return getNullabilityAnnotation(parameter);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiAnnotation getNullabilityAnnotation(@NotNull PsiModifierListOwner element) {
|
||||
PsiAnnotation nullAnnotation = nullManager.getNullableAnnotation(element, false);
|
||||
PsiAnnotation notNullAnnotation = nullManager.getNotNullAnnotation(element, false);
|
||||
if ((nullAnnotation == null) == (notNullAnnotation == null)) return null;
|
||||
return nullAnnotation != null ? nullAnnotation : notNullAnnotation;
|
||||
}
|
||||
|
||||
private void addNullabilityAnnotationIfApplicable(@NotNull PsiModifierListOwner element, @Nullable PsiAnnotation annotation) {
|
||||
PsiAnnotation nullableAnnotation = nullManager.getNullableAnnotation(element, false);
|
||||
PsiAnnotation notNullAnnotation = nullManager.getNotNullAnnotation(element, false);
|
||||
|
||||
if (notNullAnnotation != null && nullableAnnotation == null && element instanceof PsiMethod) return;
|
||||
|
||||
String annotationQualifiedName = annotation != null ? annotation.getQualifiedName() : null;
|
||||
if (annotationQualifiedName != null
|
||||
&& javaPsiFacade.findClass(annotationQualifiedName, element.getResolveScope()) == null) return;
|
||||
|
||||
if (notNullAnnotation != null) {
|
||||
notNullAnnotation.delete();
|
||||
}
|
||||
if (nullableAnnotation != null) {
|
||||
nullableAnnotation.delete();
|
||||
}
|
||||
|
||||
if (annotationQualifiedName == null) return;
|
||||
|
||||
PsiModifierList modifierList = element.getModifierList();
|
||||
if (modifierList != null) {
|
||||
modifierList.addAnnotation(annotationQualifiedName);
|
||||
javaCodeStyleManager.shortenClassReferences(element);
|
||||
}
|
||||
}
|
||||
|
||||
public void processMethod(@NotNull PsiMethod currentMethod) {
|
||||
PsiParameter[] currentParameters = currentMethod.getParameterList().getParameters();
|
||||
addNullabilityAnnotationIfApplicable(currentMethod, methodAnnotation);
|
||||
for (int i = 0; i < parameterAnnotations.length; i++) {
|
||||
addNullabilityAnnotationIfApplicable(currentParameters[i], parameterAnnotations[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processUsage(ChangeInfo changeInfo, UsageInfo usageInfo, boolean beforeMethodChange, UsageInfo[] usages) {
|
||||
PsiElement method = changeInfo.getMethod();
|
||||
@@ -328,6 +396,8 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
|
||||
UsageInfo[] javaUsageInfos = ((KotlinWrapperForJavaUsageInfos) usageInfo).getJavaUsageInfos();
|
||||
ChangeSignatureUsageProcessor[] processors = ChangeSignatureUsageProcessor.EP_NAME.getExtensions();
|
||||
|
||||
NullabilityPropagator nullabilityPropagator = new NullabilityPropagator(javaChangeInfo.getMethod());
|
||||
|
||||
for (UsageInfo usage : javaUsageInfos) {
|
||||
if (usage instanceof OverriderUsageInfo && beforeMethodChange) continue;
|
||||
for (ChangeSignatureUsageProcessor processor : processors) {
|
||||
@@ -337,6 +407,12 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
|
||||
}
|
||||
if (processor.processUsage(javaChangeInfo, usage, beforeMethodChange, javaUsageInfos)) break;
|
||||
}
|
||||
if (usage instanceof OverriderUsageInfo) {
|
||||
PsiMethod overridingMethod = ((OverriderUsageInfo)usage).getElement();
|
||||
if (overridingMethod != null) {
|
||||
nullabilityPropagator.processMethod(overridingMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class X extends A {
|
||||
@NotNull
|
||||
@Override
|
||||
String foo(int n, @NotNull String s, @Nullable Object o) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
class Y extends X {
|
||||
@NotNull
|
||||
@Override
|
||||
String foo(int n, @NotNull String s, @Nullable Object o) {
|
||||
return super.foo(n, s, o);
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
new A().foo(1, "abc", "def");
|
||||
new B().foo(2, "abc", "def");
|
||||
new X().foo(3, "abc", "def");
|
||||
new Y().foo(4, "abc", "def");
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
open class A {
|
||||
open fun foo(n: Int, s: String, o: Any?): String = ""
|
||||
}
|
||||
|
||||
class B: A() {
|
||||
override fun foo(n: Int, s: String, o: Any?): String = ""
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
class X extends A {
|
||||
@Override
|
||||
String foo(int n, String s) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
class Y extends X {
|
||||
@Override
|
||||
String foo(int n, String s) {
|
||||
return super.foo(n, s);
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
new A().foo(1, "abc");
|
||||
new B().foo(2, "abc");
|
||||
new X().foo(3, "abc");
|
||||
new Y().foo(4, "abc");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
open class A {
|
||||
open fun foo(n: Int, s: String): String = ""
|
||||
}
|
||||
|
||||
class B: A() {
|
||||
override fun foo(n: Int, s: String): String = ""
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class X extends A {
|
||||
@NotNull
|
||||
@Override
|
||||
String foo(int n, @Nullable String s, @NotNull Object o) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
class Y extends A {
|
||||
@Nullable
|
||||
@Override
|
||||
String foo(int n, @Nullable String s, @NotNull Object o) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
class Z extends A {
|
||||
@Nullable
|
||||
@Override
|
||||
String foo(int n, @Nullable String s, @NotNull Object o) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
open class A {
|
||||
open fun foo(n: Int, s: String?, o: Any): String? = ""
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class X extends A {
|
||||
@NotNull
|
||||
@Override
|
||||
String foo(int n, @NotNull String s, @Nullable Object o) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
class Y extends A {
|
||||
@Override
|
||||
String foo(int n, String s, Object o) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
class Z extends A {
|
||||
@Nullable
|
||||
@Override
|
||||
String foo(int n, @Nullable String s, @NotNull Object o) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
open class A {
|
||||
open fun <caret>foo(n: Int, s: String, o: Any?): String = ""
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class Y extends A {
|
||||
@Nullable
|
||||
@Override
|
||||
Object foo(@Nullable String n, int s, @Nullable Long o) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
open class A {
|
||||
open fun <caret>foo(n: String?, s: Int, o: Long?): Any? = ""
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class Y extends A {
|
||||
@Override
|
||||
String foo(int n, String s, Object o) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
open class A {
|
||||
open fun <caret>foo(n: Int, s: String, o: Any?): String = ""
|
||||
}
|
||||
+35
-4
@@ -319,11 +319,42 @@ public class JetChangeSignatureTest extends KotlinCodeInsightTestCase {
|
||||
doTest(changeInfo);
|
||||
}
|
||||
|
||||
public void testFunctionJavaUsagesAndOverrides() throws Exception {
|
||||
public void testFunctionJavaUsagesAndOverridesAddParam() throws Exception {
|
||||
JetChangeInfo changeInfo = getChangeInfo();
|
||||
JetParameterInfo newParameter = new JetParameterInfo("s", KotlinBuiltIns.getInstance().getStringType());
|
||||
newParameter.setDefaultValueText("\"abc\"");
|
||||
changeInfo.addParameter(newParameter);
|
||||
|
||||
JetParameterInfo param1 = new JetParameterInfo("s", KotlinBuiltIns.getInstance().getStringType());
|
||||
param1.setDefaultValueText("\"abc\"");
|
||||
changeInfo.addParameter(param1);
|
||||
|
||||
JetParameterInfo param2 = new JetParameterInfo("o", KotlinBuiltIns.getInstance().getNullableAnyType());
|
||||
param2.setDefaultValueText("\"def\"");
|
||||
changeInfo.addParameter(param2);
|
||||
|
||||
doTest(changeInfo);
|
||||
}
|
||||
|
||||
public void testFunctionJavaUsagesAndOverridesChangeNullability() throws Exception {
|
||||
JetChangeInfo changeInfo = getChangeInfo();
|
||||
|
||||
JetParameterInfo[] newParameters = changeInfo.getNewParameters();
|
||||
newParameters[1].setTypeText("String?");
|
||||
newParameters[2].setTypeText("Any");
|
||||
|
||||
changeInfo.setNewReturnTypeText("String?");
|
||||
|
||||
doTest(changeInfo);
|
||||
}
|
||||
|
||||
public void testFunctionJavaUsagesAndOverridesChangeTypes() throws Exception {
|
||||
JetChangeInfo changeInfo = getChangeInfo();
|
||||
|
||||
JetParameterInfo[] newParameters = changeInfo.getNewParameters();
|
||||
newParameters[0].setTypeText("String?");
|
||||
newParameters[1].setTypeText("Int");
|
||||
newParameters[2].setTypeText("Long?");
|
||||
|
||||
changeInfo.setNewReturnTypeText("Any?");
|
||||
|
||||
doTest(changeInfo);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user