Change Signature: Support Change Signature quick-fix for Java -> Kotlin case

#KT-9401 Fixed
This commit is contained in:
Alexey Sedunov
2016-02-11 12:12:03 +03:00
parent 8761819117
commit d587bb2ea6
47 changed files with 390 additions and 22 deletions
@@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.LocalTimeCounter
@@ -39,7 +40,7 @@ private val DO_NOT_ANALYZE_NOTIFICATION = "This file was created by KtPsiFactory
var KtFile.doNotAnalyze: String? by UserDataProperty(Key.create("DO_NOT_ANALYZE"))
var KtFile.analysisContext: PsiElement? by UserDataProperty(Key.create("ANALYSIS_CONTEXT"))
var KtFile.moduleInfo: ModuleInfo? by UserDataProperty(Key.create("MODULE_INFO"))
var PsiFile.moduleInfo: ModuleInfo? by UserDataProperty(Key.create("MODULE_INFO"))
class KtPsiFactory(private val project: Project) {
@@ -20,6 +20,7 @@ package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.psi.*
import org.jetbrains.kotlin.asJava.KtLightClass
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
@@ -30,6 +31,7 @@ import org.jetbrains.kotlin.load.java.structure.*
import org.jetbrains.kotlin.load.java.structure.impl.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.scopes.MemberScope
@@ -67,6 +69,19 @@ fun PsiMember.getJavaMemberDescriptor(resolutionFacade: ResolutionFacade? = null
}
}
@JvmOverloads
fun PsiMember.getJavaOrKotlinMemberDescriptor(resolutionFacade: ResolutionFacade? = null): DeclarationDescriptor? {
val callable = unwrapped
return when (callable) {
is PsiMember -> getJavaMemberDescriptor(resolutionFacade)
is KtDeclaration -> {
val descriptor = resolutionFacade?.resolveToDescriptor(callable) ?: callable.resolveToDescriptor()
if (descriptor is ClassDescriptor && this is PsiMethod) descriptor.unsubstitutedPrimaryConstructor else descriptor
}
else -> null
}
}
fun PsiClass.resolveToDescriptor(
resolutionFacade: ResolutionFacade,
declarationTranslator: (KtClassOrObject) -> KtClassOrObject? = { it }
@@ -42,6 +42,8 @@ fun PsiElement.getNullableModuleInfo(): IdeaModuleInfo? = this.getModuleInfo { r
}
private fun PsiElement.getModuleInfo(onFailure: (String) -> IdeaModuleInfo?): IdeaModuleInfo? {
(containingFile.moduleInfo as? IdeaModuleInfo)?.let { return it }
if (this is KtLightElement<*, *>) return this.getModuleInfoForLightElement(onFailure)
val containingJetFile = (this as? KtElement)?.containingFile as? KtFile
@@ -36,11 +36,13 @@ import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getJavaOrKotlinMemberDescriptor
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinMethodDescriptor.Kind
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallerUsage
import org.jetbrains.kotlin.idea.refactoring.j2k
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.*
@@ -518,10 +520,13 @@ val KotlinChangeInfo.oldName: String?
fun KotlinChangeInfo.getAffectedCallables(): Collection<UsageInfo> = methodDescriptor.affectedCallables + propagationTargetUsageInfos
fun ChangeInfo.toJetChangeInfo(originalChangeSignatureDescriptor: KotlinMethodDescriptor): KotlinChangeInfo {
fun ChangeInfo.toJetChangeInfo(
originalChangeSignatureDescriptor: KotlinMethodDescriptor,
resolutionFacade: ResolutionFacade? = null
): KotlinChangeInfo {
val method = method as PsiMethod
val functionDescriptor = method.getJavaMethodDescriptor()!!
val functionDescriptor = method.getJavaOrKotlinMemberDescriptor(resolutionFacade) as CallableDescriptor
val parameterDescriptors = functionDescriptor.valueParameters
//noinspection ConstantConditions
@@ -38,20 +38,15 @@ import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.KtLightMethod
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.caches.resolve.*
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.codeInsight.KotlinFileReferencesResolver
import org.jetbrains.kotlin.idea.core.compareDescriptors
import org.jetbrains.kotlin.idea.refactoring.createTempCopy
import org.jetbrains.kotlin.idea.refactoring.isTrueJavaMethod
import org.jetbrains.kotlin.idea.refactoring.*
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.*
import org.jetbrains.kotlin.idea.refactoring.getBodyScope
import org.jetbrains.kotlin.idea.refactoring.getContainingScope
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
@@ -435,10 +430,9 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor {
}
private fun findKotlinOverrides(changeInfo: ChangeInfo, result: MutableSet<UsageInfo>) {
val method = changeInfo.method
if (!method.isTrueJavaMethod()) return
val method = changeInfo.method as? PsiMethod ?: return
for (overridingMethod in OverridingMethodsSearch.search(method as PsiMethod)) {
for (overridingMethod in OverridingMethodsSearch.search(method)) {
val unwrappedElement = overridingMethod.namedUnwrappedElement as? KtNamedFunction ?: continue
val functionDescriptor = unwrappedElement.resolveToDescriptorIfAny() as? FunctionDescriptor ?: continue
result.add(DeferredJavaMethodOverrideOrSAMUsage(unwrappedElement, functionDescriptor, null))
@@ -856,8 +850,12 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor {
val descriptorWrapper = usages.firstIsInstanceOrNull<OriginalJavaMethodDescriptorWrapper>()
if (descriptorWrapper == null || descriptorWrapper.originalJavaMethodDescriptor != null) return true
val methodDescriptor = method.getJavaMethodDescriptor()?.createDeepCopy() ?: return false
descriptorWrapper.originalJavaMethodDescriptor = KotlinChangeSignatureData(methodDescriptor, method, listOf(methodDescriptor))
val baseDeclaration = method.unwrapped ?: return false
val baseDeclarationDescriptor = method.getJavaOrKotlinMemberDescriptor()?.createDeepCopy() as CallableDescriptor?
?: return false
descriptorWrapper.originalJavaMethodDescriptor = KotlinChangeSignatureData(baseDeclarationDescriptor,
baseDeclaration,
listOf(baseDeclarationDescriptor))
// This change info is used as a placeholder before primary method update
// It gets replaced with real change info afterwards
@@ -910,12 +908,37 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor {
}
override fun processPrimaryMethod(changeInfo: ChangeInfo): Boolean {
if (changeInfo !is KotlinChangeInfo) return false
val ktChangeInfo = when (changeInfo) {
is KotlinChangeInfo -> changeInfo
is JavaChangeInfo -> {
val method = changeInfo.method as? KtLightMethod ?: return false
var baseFunction = method.getOrigin() ?: return false
if (baseFunction is KtClass) {
baseFunction = baseFunction.createPrimaryConstructorIfAbsent()
}
val resolutionFacade = baseFunction.getResolutionFacade()
val baseFunctionDescriptor = resolutionFacade.resolveToDescriptor(baseFunction) as FunctionDescriptor
val methodDescriptor = KotlinChangeSignatureData(baseFunctionDescriptor, baseFunction, listOf(baseFunctionDescriptor))
for (primaryFunction in changeInfo.methodDescriptor.primaryCallables) {
primaryFunction.processUsage(changeInfo, primaryFunction.declaration, UsageInfo.EMPTY_ARRAY)
val dummyClass = JavaPsiFacade.getElementFactory(method.project).createClass("Dummy")
val dummyMethod = createJavaMethod(method, dummyClass)
dummyMethod.containingFile.moduleInfo = baseFunction.getModuleInfo()
try {
changeInfo.updateMethod(dummyMethod)
JavaChangeSignatureUsageProcessor().processPrimaryMethod(changeInfo)
changeInfo.toJetChangeInfo(methodDescriptor, resolutionFacade)
}
finally {
changeInfo.updateMethod(method)
}
}
else -> return false
}
changeInfo.primaryMethodUpdated()
for (primaryFunction in ktChangeInfo.methodDescriptor.primaryCallables) {
primaryFunction.processUsage(ktChangeInfo, primaryFunction.declaration, UsageInfo.EMPTY_ARRAY)
}
ktChangeInfo.primaryMethodUpdated()
return true
}
@@ -20,12 +20,13 @@ import com.intellij.psi.PsiElement
import com.intellij.refactoring.changeSignature.CallerUsageInfo
import com.intellij.refactoring.changeSignature.OverriderUsageInfo
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.DeferredJavaMethodKotlinCallerUsage
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.JavaMethodKotlinUsageWithDelegate
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallerUsage
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.types.*
@@ -82,7 +83,7 @@ fun KotlinType.renderTypeWithSubstitution(substitutor: TypeSubstitutor?, default
// This method is used to create full copies of functions (including copies of all types)
// It's needed to prevent accesses to PSI (e.g. using LazyJavaClassifierType properties) when Change signature invalidates it
// See KotlinChangeSignatureTest.testSAMChangeMethodReturnType
fun FunctionDescriptor.createDeepCopy() = substitute(TypeSubstitutor.create(ForceTypeCopySubstitution))
fun DeclarationDescriptor.createDeepCopy() = (this as? JavaMethodDescriptor)?.substitute(TypeSubstitutor.create(ForceTypeCopySubstitution)) ?: this
private object ForceTypeCopySubstitution : TypeSubstitution() {
override fun get(key: KotlinType) =
@@ -0,0 +1,5 @@
open class K {
open fun foo(i: Int) {
}
}
@@ -0,0 +1,7 @@
// "Add 'int' as 1st parameter to method 'foo'" "true"
public class J {
void foo() {
new K().foo(1);
}
}
@@ -0,0 +1,5 @@
open class K {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Add 'int' as 1st parameter to method 'foo'" "true"
public class J {
void foo() {
new K().foo(<caret>1);
}
}
@@ -0,0 +1,5 @@
open class K(i: Int) {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Add 'int' as 1st parameter to method 'K'" "true"
public class J {
void foo() {
new K(1);
}
}
@@ -0,0 +1,5 @@
open class K {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Add 'int' as 1st parameter to method 'K'" "true"
public class J {
void foo() {
new K(<caret>1);
}
}
@@ -0,0 +1,5 @@
open class K(i: Int) {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Add 'int' as 1st parameter to method 'K'" "true"
public class J {
void foo() {
new K(1);
}
}
@@ -0,0 +1,5 @@
open class K() {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Add 'int' as 1st parameter to method 'K'" "true"
public class J {
void foo() {
new K(<caret>1);
}
}
@@ -0,0 +1,7 @@
open class K {
constructor(i: Int)
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Add 'int' as 1st parameter to method 'K'" "true"
public class J {
void foo() {
new K(1);
}
}
@@ -0,0 +1,7 @@
open class K {
constructor()
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Add 'int' as 1st parameter to method 'K'" "true"
public class J {
void foo() {
new K(<caret>1);
}
}
@@ -0,0 +1,5 @@
open class K {
open fun foo(n: Int, b: String) {
}
}
@@ -0,0 +1,7 @@
// "Change 2nd parameter of method 'foo' from 'boolean' to 'String'" "true"
public class J {
void foo() {
new K().foo(1, <caret>"2");
}
}
@@ -0,0 +1,5 @@
open class K {
open fun foo(n: Int, b: Boolean) {
}
}
@@ -0,0 +1,7 @@
// "Change 2nd parameter of method 'foo' from 'boolean' to 'String'" "true"
public class J {
void foo() {
new K().foo(1, <caret>"2");
}
}
@@ -0,0 +1,5 @@
open class K(n: Int, b: String) {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Change 2nd parameter of method 'K' from 'boolean' to 'String'" "true"
public class J {
void foo() {
new K(1, <caret>"2");
}
}
@@ -0,0 +1,5 @@
open class K(n: Int, b: Boolean) {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Change 2nd parameter of method 'K' from 'boolean' to 'String'" "true"
public class J {
void foo() {
new K(1, <caret>"2");
}
}
@@ -0,0 +1,7 @@
open class K {
constructor(n: Int, b: String)
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Change 2nd parameter of method 'K' from 'boolean' to 'String'" "true"
public class J {
void foo() {
new K(1, <caret>"2");
}
}
@@ -0,0 +1,7 @@
open class K {
constructor(n: Int, b: Boolean)
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Change 2nd parameter of method 'K' from 'boolean' to 'String'" "true"
public class J {
void foo() {
new K(1, <caret>"2");
}
}
@@ -0,0 +1,5 @@
open class K {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Remove 1st parameter from method 'foo'" "true"
public class J {
void foo() {
new K().foo(<caret>);
}
}
@@ -0,0 +1,5 @@
open class K {
open fun foo(n: Int) {
}
}
@@ -0,0 +1,7 @@
// "Remove 1st parameter from method 'foo'" "true"
public class J {
void foo() {
new K().foo(<caret>);
}
}
@@ -0,0 +1,5 @@
open class K() {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Remove 1st parameter from method 'K'" "true"
public class J {
void foo() {
new K(<caret>);
}
}
@@ -0,0 +1,5 @@
open class K(n: Int) {
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Remove 1st parameter from method 'K'" "true"
public class J {
void foo() {
new K(<caret>);
}
}
@@ -0,0 +1,7 @@
open class K {
constructor()
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Remove 1st parameter from method 'K'" "true"
public class J {
void foo() {
new K(<caret>);
}
}
@@ -0,0 +1,7 @@
open class K {
constructor(n: Int)
open fun foo() {
}
}
@@ -0,0 +1,7 @@
// "Remove 1st parameter from method 'K'" "true"
public class J {
void foo() {
new K(<caret>);
}
}
@@ -463,6 +463,75 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/removeJavaMethodParameter.before.Main.kt");
doTestWithExtraFile(fileName);
}
@TestMetadata("idea/testData/quickfix/changeSignature/jk")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Jk extends AbstractQuickFixMultiFileTest {
public void testAllFilesPresentInJk() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/changeSignature/jk"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), true);
}
@TestMetadata("jkAddFunctionParameter.before.Main.java")
public void testJkAddFunctionParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkAddFunctionParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
@TestMetadata("jkAddImplicitPrimaryConstructorParameter.before.Main.java")
public void testJkAddImplicitPrimaryConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkAddImplicitPrimaryConstructorParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
@TestMetadata("jkAddPrimaryConstructorParameter.before.Main.java")
public void testJkAddPrimaryConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkAddPrimaryConstructorParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
@TestMetadata("jkAddSecondaryConstructorParameter.before.Main.java")
public void testJkAddSecondaryConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkAddSecondaryConstructorParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
@TestMetadata("jkChangeFunctionParameter.before.Main.java")
public void testJkChangeFunctionParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkChangeFunctionParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
@TestMetadata("jkChangePrimaryConstructorParameter.before.Main.java")
public void testJkChangePrimaryConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkChangePrimaryConstructorParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
@TestMetadata("jkChangeSecondaryConstructorParameter.before.Main.java")
public void testJkChangeSecondaryConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkChangeSecondaryConstructorParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
@TestMetadata("jkRemoveFunctionParameter.before.Main.java")
public void testJkRemoveFunctionParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkRemoveFunctionParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
@TestMetadata("jkRemovePrimaryConstructorParameter.before.Main.java")
public void testJkRemovePrimaryConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkRemovePrimaryConstructorParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
@TestMetadata("jkRemoveSecondaryConstructorParameter.before.Main.java")
public void testJkRemoveSecondaryConstructorParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/changeSignature/jk/jkRemoveSecondaryConstructorParameter.before.Main.java");
doTestWithExtraFile(fileName);
}
}
}
@TestMetadata("idea/testData/quickfix/checkArguments")