Change Signature: Processing of non-Kotlin usages

#KT-5810 Fixed
 #KT-4187 Fixed
This commit is contained in:
Alexey Sedunov
2014-11-18 21:17:23 +03:00
parent c35c4072c6
commit 1008225116
20 changed files with 324 additions and 39 deletions
@@ -22,6 +22,7 @@ import java.util.Collections
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import java.util.ArrayList
import org.jetbrains.jet.lang.psi.psiUtil.isExtensionDeclaration
import org.jetbrains.jet.utils.addToStdlib.singletonOrEmptyList
public fun JetClassOrObject.toLightClass(): KotlinLightClass? = LightClassUtil.getPsiClass(this) as KotlinLightClass?
@@ -43,10 +44,11 @@ public fun JetDeclaration.toLightElements(): List<PsiNamedElement> =
public fun PsiElement.toLightMethods(): List<PsiMethod> =
when (this) {
is JetNamedFunction -> Collections.singletonList(LightClassUtil.getLightClassMethod(this))
is JetNamedFunction -> LightClassUtil.getLightClassMethod(this).singletonOrEmptyList()
is JetProperty -> LightClassUtil.getLightClassPropertyMethods(this).toList()
is JetParameter -> LightClassUtil.getLightClassPropertyMethods(this).toList()
is JetPropertyAccessor -> Collections.singletonList(LightClassUtil.getLightClassAccessorMethod(this))
is JetPropertyAccessor -> LightClassUtil.getLightClassAccessorMethod(this).singletonOrEmptyList()
is JetClass -> Collections.singletonList(LightClassUtil.getPsiClass(this).getConstructors()[0])
is PsiMethod -> Collections.singletonList(this)
else -> listOf()
}
@@ -19,9 +19,22 @@ package org.jetbrains.jet.plugin.refactoring.changeSignature;
import com.intellij.lang.Language;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiType;
import com.intellij.refactoring.changeSignature.ChangeInfo;
import com.intellij.refactoring.changeSignature.ChangeSignatureProcessor;
import com.intellij.refactoring.changeSignature.JavaChangeInfo;
import com.intellij.refactoring.changeSignature.ParameterInfoImpl;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.Function;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.ContainerUtil;
import kotlin.KotlinPackage;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.asJava.AsJavaPackage;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.descriptors.Visibilities;
@@ -31,7 +44,6 @@ import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.JetLanguage;
import org.jetbrains.jet.plugin.refactoring.changeSignature.usages.JetFunctionDefinitionUsage;
import java.util.Collection;
import java.util.HashMap;
@@ -49,6 +61,10 @@ public class JetChangeInfo implements ChangeInfo {
private final JetGeneratedInfo generatedInfo;
private Boolean parameterNamesChanged;
private Map<String, Integer> oldNameToParameterIndex;
private boolean primaryMethodUpdated;
@Nullable
private JavaChangeInfo javaChangeInfo;
private final PsiMethod originalPsiMethod;
public JetChangeInfo(
JetMethodDescriptor oldDescriptor,
@@ -68,6 +84,14 @@ public class JetChangeInfo implements ChangeInfo {
this.newParameters = newParameters;
this.context = context;
this.generatedInfo = generatedInfo;
this.originalPsiMethod = getCurrentPsiMethod();
}
@Nullable
private PsiMethod getCurrentPsiMethod() {
List<PsiMethod> psiMethods = AsJavaPackage.toLightMethods(getMethod());
assert psiMethods.size() <= 1 : "Multiple light methods: " + getMethod().getText();
return KotlinPackage.firstOrNull(psiMethods);
}
public String getNewSignature(@Nullable JetFunction inheritedFunction, boolean isInherited) {
@@ -263,7 +287,56 @@ public class JetChangeInfo implements ChangeInfo {
}
@NotNull
public Collection<JetFunctionDefinitionUsage> getAffectedFunctions() {
public Collection<UsageInfo> getAffectedFunctions() {
return oldDescriptor.getAffectedFunctions();
}
@Nullable
public JavaChangeInfo getOrCreateJavaChangeInfo() {
if (javaChangeInfo == null) {
final PsiMethod currentPsiMethod = getCurrentPsiMethod();
if (originalPsiMethod == null || currentPsiMethod == null) return null;
/*
* When primaryMethodUpdated is false, changes to the primary Kotlin declaration are already confirmed, but not yet applied.
* It means that originalPsiMethod has already expired, but new one can't be created until Kotlin declaration is updated
* (signified by primaryMethodUpdated being true). It means we can't know actual PsiType, visibility, etc.
* to use in JavaChangeInfo. However they are not actually used at this point since only parameter count and order matters here
* So we resort to this hack and pass around "default" type (void) and visibility (package-local)
*/
String javaVisibility = primaryMethodUpdated
? VisibilityUtil.getVisibilityModifier(currentPsiMethod.getModifierList())
: PsiModifier.PACKAGE_LOCAL;
JetParameterInfo[] newParameters = getNewParameters();
ParameterInfoImpl[] newJavaParameters = ContainerUtil.map2Array(
KotlinPackage.withIndices(newParameters),
new ParameterInfoImpl[newParameters.length],
new Function<Pair<? extends Integer, ? extends JetParameterInfo>, ParameterInfoImpl>() {
@Override
public ParameterInfoImpl fun(Pair<? extends Integer, ? extends JetParameterInfo> pair) {
JetParameterInfo info = pair.getSecond();
PsiType type = primaryMethodUpdated
? currentPsiMethod.getParameterList().getParameters()[pair.getFirst()].getType()
: PsiType.VOID;
return new ParameterInfoImpl(info.getOldIndex(), info.getName(), type, info.getDefaultValueText());
}
}
);
PsiType returnType = primaryMethodUpdated ? currentPsiMethod.getReturnType() : PsiType.VOID;
javaChangeInfo = new ChangeSignatureProcessor(
getMethod().getProject(), originalPsiMethod, false, javaVisibility, getNewName(), returnType, newJavaParameters
).getChangeInfo();
javaChangeInfo.updateMethod(currentPsiMethod);
}
return javaChangeInfo;
}
public void primaryMethodUpdated() {
primaryMethodUpdated = true;
javaChangeInfo = null;
}
}
@@ -21,6 +21,8 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.refactoring.changeSignature.MethodDescriptor;
import com.intellij.refactoring.changeSignature.OverriderUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import kotlin.Function1;
@@ -48,7 +50,7 @@ public final class JetChangeSignatureData implements JetMethodDescriptor {
private final List<JetParameterInfo> parameters;
@NotNull
private final Collection<FunctionDescriptor> descriptorsForSignatureChange;
private Collection<JetFunctionDefinitionUsage> affectedFunctions = null;
private Collection<UsageInfo> affectedFunctions = null;
public JetChangeSignatureData(
@NotNull FunctionDescriptor baseDescriptor,
@@ -92,47 +94,45 @@ public final class JetChangeSignatureData implements JetMethodDescriptor {
@Override
@NotNull
public Collection<JetFunctionDefinitionUsage> getAffectedFunctions() {
public Collection<UsageInfo> getAffectedFunctions() {
if (affectedFunctions == null) {
affectedFunctions = KotlinPackage.flatMapTo(
descriptorsForSignatureChange,
new HashSet<JetFunctionDefinitionUsage>(),
new Function1<FunctionDescriptor, Iterable<? extends JetFunctionDefinitionUsage>>() {
@NotNull
private Collection<JetFunctionDefinitionUsage> computeHierarchyFrom(@NotNull FunctionDescriptor baseDescriptor) {
PsiElement declaration = DescriptorToDeclarationUtil.INSTANCE$.getDeclaration(baseDeclaration.getProject(), baseDescriptor);
assert declaration != null : "No declaration found for " + baseDescriptor;
new HashSet<UsageInfo>(),
new Function1<FunctionDescriptor, Iterable<? extends UsageInfo>>() {
@Override
public Iterable<? extends UsageInfo> invoke(FunctionDescriptor descriptor) {
PsiElement declaration = DescriptorToDeclarationUtil.INSTANCE$.getDeclaration(baseDeclaration.getProject(),
descriptor);
assert declaration != null : "No declaration found for " + descriptor;
Set<JetFunctionDefinitionUsage> result = Sets.newHashSet();
Set<UsageInfo> result = Sets.newHashSet();
result.add(new JetFunctionDefinitionUsage(declaration, false));
if (!(declaration instanceof JetNamedFunction)) return result;
PsiMethod lightMethod = LightClassUtil.getLightClassMethod((JetNamedFunction) declaration);
final PsiMethod baseLightMethod = LightClassUtil.getLightClassMethod((JetNamedFunction) declaration);
// there are valid situations when light method is null: local functions and literals
if (lightMethod == null) return result;
if (baseLightMethod == null) return result;
return KotlinPackage.filterNotNullTo(
KotlinPackage.map(
OverridingMethodsSearch.search(lightMethod).findAll(),
new Function1<PsiMethod, JetFunctionDefinitionUsage>() {
OverridingMethodsSearch.search(baseLightMethod).findAll(),
new Function1<PsiMethod, UsageInfo>() {
@Override
public JetFunctionDefinitionUsage invoke(PsiMethod method) {
JetDeclaration declaration = (method instanceof KotlinLightMethod)
? ((KotlinLightMethod) method).getOrigin()
: null;
return declaration != null ? new JetFunctionDefinitionUsage(declaration, true) : null;
public UsageInfo invoke(PsiMethod method) {
if (method instanceof KotlinLightMethod) {
JetDeclaration declaration = ((KotlinLightMethod) method).getOrigin();
return declaration != null ? new JetFunctionDefinitionUsage(declaration, true) : null;
}
return new OverriderUsageInfo(method, baseLightMethod, true, true, true);
}
}
),
result
);
}
@Override
public Iterable<? extends JetFunctionDefinitionUsage> invoke(FunctionDescriptor descriptor) {
return computeHierarchyFrom(descriptor);
}
}
);
}
@@ -21,15 +21,19 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.changeSignature.ChangeSignatureProcessorBase;
import com.intellij.refactoring.changeSignature.ChangeSignatureUsageProcessor;
import com.intellij.refactoring.changeSignature.*;
import com.intellij.refactoring.rename.RenameUtil;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import kotlin.Function1;
import kotlin.KotlinPackage;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.plugin.refactoring.changeSignature.usages.JetUsageInfo;
import org.jetbrains.jet.plugin.refactoring.changeSignature.usages.KotlinWrapperForJavaUsageInfos;
import java.util.*;
@@ -53,6 +57,43 @@ public class JetChangeSignatureProcessor extends ChangeSignatureProcessorBase {
return (JetChangeInfo) super.getChangeInfo();
}
@NotNull
@Override
protected UsageInfo[] findUsages() {
UsageInfo[] kotlinUsages = super.findUsages();
JavaChangeInfo javaChangeInfo = getChangeInfo().getOrCreateJavaChangeInfo();
if (javaChangeInfo == null) return kotlinUsages;
List<UsageInfo> javaUsages = new ArrayList<UsageInfo>();
KotlinPackage.filterNotTo(
new JavaChangeSignatureUsageProcessor().findUsages(javaChangeInfo),
javaUsages,
new Function1<UsageInfo, Boolean>() {
@Override
public Boolean invoke(UsageInfo info) {
// Filter overriding declarations since they are already found by our usage processor
return info instanceof OverriderUsageInfo;
}
}
);
Pair<List<? extends UsageInfo>, List<? extends UsageInfo>> usagesByKotlinProcessor = KotlinPackage.partition(
kotlinUsages,
new Function1<UsageInfo, Boolean>() {
@Override
public Boolean invoke(UsageInfo info) {
return info instanceof JetUsageInfo;
}
}
);
javaUsages.addAll(usagesByKotlinProcessor.getSecond());
List<UsageInfo> allUsages = new ArrayList<UsageInfo>();
allUsages.add(new KotlinWrapperForJavaUsageInfos(javaUsages.toArray(new UsageInfo[javaUsages.size()]), getChangeInfo().getMethod()));
allUsages.addAll(usagesByKotlinProcessor.getFirst());
return allUsages.toArray(new UsageInfo[allUsages.size()]);
}
@Override
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
for (ChangeSignatureUsageProcessor processor : ChangeSignatureUsageProcessor.EP_NAME.getExtensions()) {
@@ -21,9 +21,7 @@ import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.refactoring.changeSignature.ChangeInfo;
import com.intellij.refactoring.changeSignature.ChangeSignatureUsageProcessor;
import com.intellij.refactoring.changeSignature.ParameterInfo;
import com.intellij.refactoring.changeSignature.*;
import com.intellij.refactoring.rename.ResolveSnapshotProvider;
import com.intellij.refactoring.util.TextOccurrencesUtil;
import com.intellij.usageView.UsageInfo;
@@ -61,8 +59,13 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
}
private static void findAllMethodUsages(JetChangeInfo changeInfo, Set<UsageInfo> result) {
for (JetFunctionDefinitionUsage functionUsageInfo : changeInfo.getAffectedFunctions()) {
findOneMethodUsages(functionUsageInfo, changeInfo, result);
for (UsageInfo functionUsageInfo : changeInfo.getAffectedFunctions()) {
if (functionUsageInfo instanceof JetFunctionDefinitionUsage) {
findOneMethodUsages((JetFunctionDefinitionUsage) functionUsageInfo, changeInfo, result);
}
else {
result.add(functionUsageInfo);
}
}
}
@@ -237,11 +240,26 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
@Override
public boolean processUsage(ChangeInfo changeInfo, UsageInfo usageInfo, boolean beforeMethodChange, UsageInfo[] usages) {
if (beforeMethodChange)
return true;
if (usageInfo instanceof KotlinWrapperForJavaUsageInfos) {
JavaChangeInfo javaChangeInfo = ((JetChangeInfo) changeInfo).getOrCreateJavaChangeInfo();
assert javaChangeInfo != null : "JavaChangeInfo not found: " + changeInfo.getMethod().getText();
UsageInfo[] javaUsageInfos = ((KotlinWrapperForJavaUsageInfos) usageInfo).getJavaUsageInfos();
ChangeSignatureUsageProcessor[] processors = ChangeSignatureUsageProcessor.EP_NAME.getExtensions();
for (UsageInfo usage : javaUsageInfos) {
if (usage instanceof OverriderUsageInfo && beforeMethodChange) continue;
for (ChangeSignatureUsageProcessor processor : processors) {
if (usage instanceof OverriderUsageInfo) {
processor.processUsage(javaChangeInfo, usage, true, javaUsageInfos);
}
if (processor.processUsage(javaChangeInfo, usage, beforeMethodChange, javaUsageInfos)) break;
}
}
}
if (beforeMethodChange) return true;
PsiElement element = usageInfo.getElement();
if (element != null)
return usageInfo instanceof JetUsageInfo ? ((JetUsageInfo) usageInfo).processUsage((JetChangeInfo) changeInfo, element) : true;
else
@@ -250,6 +268,7 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro
@Override
public boolean processPrimaryMethod(ChangeInfo changeInfo) {
((JetChangeInfo)changeInfo).primaryMethodUpdated();
return true;
}
@@ -18,17 +18,17 @@ package org.jetbrains.jet.plugin.refactoring.changeSignature;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.changeSignature.MethodDescriptor;
import com.intellij.usageView.UsageInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.Visibility;
import org.jetbrains.jet.plugin.refactoring.changeSignature.usages.JetFunctionDefinitionUsage;
import java.util.Collection;
public interface JetMethodDescriptor extends MethodDescriptor<JetParameterInfo, Visibility> {
@NotNull
Collection<JetFunctionDefinitionUsage> getAffectedFunctions();
Collection<UsageInfo> getAffectedFunctions();
boolean isConstructor();
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.plugin.refactoring.changeSignature.usages
import com.intellij.psi.PsiElement
import com.intellij.usageView.UsageInfo
import org.jetbrains.jet.plugin.refactoring.changeSignature.JetChangeInfo
import com.intellij.refactoring.changeSignature.ChangeInfo
public class KotlinWrapperForJavaUsageInfos(
val javaUsageInfos: Array<UsageInfo>,
val primaryMethod: PsiElement
): UsageInfo(primaryMethod)
@@ -0,0 +1,9 @@
class Foo extends A {
Foo() {
super(2, "abc");
}
void foo() {
new A(1, "abc");
}
}
@@ -0,0 +1,4 @@
open class A(n: Int,
s: String)
class B: A(1, "abc")
@@ -0,0 +1,9 @@
class Foo extends A {
Foo() {
super(2);
}
void foo() {
new A(1);
}
}
@@ -0,0 +1,3 @@
open class A(<caret>n: Int)
class B: A(1)
@@ -0,0 +1,22 @@
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");
}
}
@@ -0,0 +1,9 @@
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,22 @@
class X extends A {
@Override
String foo(int n) {
return "";
}
}
class Y extends X {
@Override
String foo(int n) {
return super.foo(n);
}
}
class Test {
void test() {
new A().foo(1);
new B().foo(2);
new X().foo(3);
new Y().foo(4);
}
}
@@ -0,0 +1,7 @@
open class A {
open fun <caret>foo(n: Int): String = ""
}
class B: A() {
override fun foo(n: Int): String = ""
}
@@ -0,0 +1,7 @@
import static _DefaultPackage.bar;
class J {
void test() {
bar(1);
}
}
@@ -0,0 +1 @@
fun bar(n: Int): String = ""
@@ -0,0 +1,7 @@
import static _DefaultPackage.foo;
class J {
void test() {
foo(1);
}
}
@@ -0,0 +1 @@
fun <caret>foo(n: Int): String = ""
@@ -304,6 +304,28 @@ public class JetChangeSignatureTest extends KotlinCodeInsightTestCase {
doTest(changeInfo);
}
public void testConstructorJavaUsages() throws Exception {
JetChangeInfo changeInfo = getChangeInfo();
JetParameterInfo newParameter = new JetParameterInfo("s", KotlinBuiltIns.getInstance().getStringType());
newParameter.setDefaultValueText("\"abc\"");
changeInfo.addParameter(newParameter);
doTest(changeInfo);
}
public void testFunctionJavaUsagesAndOverrides() throws Exception {
JetChangeInfo changeInfo = getChangeInfo();
JetParameterInfo newParameter = new JetParameterInfo("s", KotlinBuiltIns.getInstance().getStringType());
newParameter.setDefaultValueText("\"abc\"");
changeInfo.addParameter(newParameter);
doTest(changeInfo);
}
public void testFunctionRenameJavaUsages() throws Exception {
JetChangeInfo changeInfo = getChangeInfo();
changeInfo.setNewName("bar");
doTest(changeInfo);
}
@NotNull
@Override
protected String getTestDataPath() {