Change Signature: Propagate nullability annotations to overriding methods in Java

This commit is contained in:
Alexey Sedunov
2014-11-28 14:37:51 +03:00
parent c9873428de
commit 250940c824
16 changed files with 231 additions and 37 deletions
@@ -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);
}
}
}
}
@@ -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");
}
}
@@ -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 = ""
}
@@ -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 "";
}
}
@@ -0,0 +1,3 @@
open class A {
open fun foo(n: Int, s: String?, o: Any): String? = ""
}
@@ -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 "";
}
}
@@ -0,0 +1,3 @@
open class A {
open fun <caret>foo(n: Int, s: String, o: Any?): String = ""
}
@@ -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 "";
}
}
@@ -0,0 +1,3 @@
open class A {
open fun <caret>foo(n: String?, s: Int, o: Long?): Any? = ""
}
@@ -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 "";
}
}
@@ -0,0 +1,3 @@
open class A {
open fun <caret>foo(n: Int, s: String, o: Any?): String = ""
}
@@ -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);
}