Refactoring: change signature should affected expect/actual

#KT-33972 Fixed
This commit is contained in:
Dmitry Gridin
2019-09-04 15:13:55 +07:00
parent 484dda478e
commit e3ce799993
53 changed files with 473 additions and 63 deletions
@@ -188,7 +188,7 @@ class ConvertFunctionToPropertyIntention :
} }
} }
private fun findMainElement(callables: List<PsiElement>): PsiElement? { private fun findMainElement(callables: Collection<PsiElement>): PsiElement? {
if (editor == null) return null if (editor == null) return null
val offset = editor.caretModel.offset val offset = editor.caretModel.offset
return callables.find { file == it.containingFile && offset in it.textRange } return callables.find { file == it.containingFile && offset in it.textRange }
@@ -35,31 +35,24 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations import org.jetbrains.kotlin.idea.core.getDeepestSuperDeclarations
import org.jetbrains.kotlin.idea.core.getDirectlyOverriddenDeclarations import org.jetbrains.kotlin.idea.core.getDirectlyOverriddenDeclarations
import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachOverridingElement
import org.jetbrains.kotlin.idea.util.actualsForExpected import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.idea.util.liftToExpected import org.jetbrains.kotlin.idea.util.liftToExpected
import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope
import java.util.*
abstract class CallableRefactoring<out T : CallableDescriptor>( abstract class CallableRefactoring<out T : CallableDescriptor>(
val project: Project, val project: Project,
callableDescriptor: T, val callableDescriptor: T,
val commandName: String val commandName: String
) { ) {
private val LOG = Logger.getInstance(CallableRefactoring::class.java) private val LOG = Logger.getInstance(CallableRefactoring::class.java)
@Suppress("UNCHECKED_CAST")
val callableDescriptor = callableDescriptor.liftToExpected() as? T ?: callableDescriptor
private val kind = (callableDescriptor as? CallableMemberDescriptor)?.kind ?: DECLARATION private val kind = (callableDescriptor as? CallableMemberDescriptor)?.kind ?: DECLARATION
protected open fun forcePerformForSelectedFunctionOnly(): Boolean { protected open fun forcePerformForSelectedFunctionOnly(): Boolean {
@@ -184,15 +177,34 @@ abstract class CallableRefactoring<out T : CallableDescriptor>(
} }
} }
fun getAffectedCallables(project: Project, descriptorsForChange: Collection<CallableDescriptor>): List<PsiElement> { fun getAffectedCallables(project: Project, descriptorsForChange: Collection<CallableDescriptor>): Collection<PsiElement> {
val baseCallables = descriptorsForChange.mapNotNull { DescriptorToSourceUtilsIde.getAnyDeclaration(project, it) } val results = hashSetOf<PsiElement>()
return baseCallables + baseCallables.flatMapTo(HashSet<PsiElement>()) { callable -> for (descriptor in descriptorsForChange) {
if (callable is KtDeclaration && callable.isExpectDeclaration()) { val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) ?: continue
callable.actualsForExpected() collectAffectedCallables(declaration, results)
} else { }
callable.toLightMethods().flatMap { psiMethod ->
val overrides = OverridingMethodsSearch.search(psiMethod).findAll() return results
overrides.map { method -> method.namedUnwrappedElement ?: method } }
private fun collectAffectedCallables(declaration: PsiElement, results: MutableCollection<PsiElement>) {
if (!results.add(declaration)) return
if (declaration is KtDeclaration) {
for (it in declaration.actualsForExpected()) {
collectAffectedCallables(it, results)
}
declaration.liftToExpected()?.let { collectAffectedCallables(it, results) }
if (declaration !is KtCallableDeclaration) return
declaration.forEachOverridingElement { _, overridingElement ->
results += overridingElement.namedUnwrappedElement ?: overridingElement
true
}
} else {
for (psiMethod in declaration.toLightMethods()) {
OverridingMethodsSearch.search(psiMethod).forEach {
results += it.namedUnwrappedElement ?: it
} }
} }
} }
@@ -28,17 +28,13 @@ import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage
import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachOverridingElement import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachOverridingElement
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.idea.util.actualsForExpected
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.utils.SmartList
import java.util.*
class KotlinChangeSignatureData( class KotlinChangeSignatureData(
override val baseDescriptor: CallableDescriptor, override val baseDescriptor: CallableDescriptor,
@@ -101,45 +97,76 @@ class KotlinChangeSignatureData(
} }
override val affectedCallables: Collection<UsageInfo> by lazy { override val affectedCallables: Collection<UsageInfo> by lazy {
primaryCallables + primaryCallables.flatMapTo(HashSet<UsageInfo>()) { primaryFunction -> val results = hashSetOf<UsageInfo>()
val primaryDeclaration = primaryFunction.declaration as? KtDeclaration ?: return@flatMapTo emptyList()
if (primaryDeclaration.isExpectDeclaration()) { results += primaryCallables
return@flatMapTo primaryDeclaration.actualsForExpected().mapNotNull { for (primaryCallable in primaryCallables) {
val callableDescriptor = when (val descriptor = it.unsafeResolveToDescriptor()) { val primaryDeclaration = primaryCallable.declaration as? KtDeclaration ?: continue
is CallableDescriptor -> descriptor collectMembers(primaryDeclaration, primaryCallable, results)
is ClassDescriptor -> descriptor.unsubstitutedPrimaryConstructor ?: return@mapNotNull null
else -> return@mapNotNull null
}
KotlinCallableDefinitionUsage<PsiElement>(it, callableDescriptor, primaryFunction, null)
}
}
if (primaryDeclaration !is KtCallableDeclaration) return@flatMapTo emptyList()
val results = SmartList<UsageInfo>()
primaryDeclaration.forEachOverridingElement { baseElement, overridingElement ->
val currentDeclaration = overridingElement.namedUnwrappedElement
results += when (currentDeclaration) {
is KtDeclaration -> {
val overridingDescriptor = currentDeclaration.unsafeResolveToDescriptor() as CallableDescriptor
KotlinCallableDefinitionUsage(currentDeclaration, overridingDescriptor, primaryFunction, null)
}
is PsiMethod -> {
val baseMethod = baseElement as? PsiMethod ?: return@forEachOverridingElement true
OverriderUsageInfo(currentDeclaration, baseMethod, true, true, true)
}
else -> return@forEachOverridingElement true
}
true
}
results
} }
results
}
private fun collectMembers(
declaration: KtDeclaration,
primaryFunction: KotlinCallableDefinitionUsage<PsiElement>,
results: MutableCollection<UsageInfo>
) {
if (declaration.isEffectivelyActual()) {
declaration.liftToExpected()?.let { collectExpectActualMembers(it, primaryFunction, results) }
}
if (declaration.isExpectDeclaration()) for (it in declaration.actualsForExpected()) collectExpectActualMembers(it, primaryFunction, results)
if (declaration !is KtCallableDeclaration) return
declaration.forEachOverridingElement { baseElement, overridingElement ->
val currentDeclaration = overridingElement.namedUnwrappedElement
results += when (currentDeclaration) {
is KtDeclaration -> {
val overridingDescriptor = currentDeclaration.unsafeResolveToDescriptor() as CallableDescriptor
KotlinCallableDefinitionUsage(
currentDeclaration,
overridingDescriptor,
primaryFunction,
null,
canDropOverride = false
)
}
is PsiMethod -> {
val baseMethod = baseElement as? PsiMethod ?: return@forEachOverridingElement true
OverriderUsageInfo(currentDeclaration, baseMethod, true, true, true)
}
else -> return@forEachOverridingElement true
}
true
}
}
private fun collectExpectActualMembers(
it: KtDeclaration,
primaryFunction: KotlinCallableDefinitionUsage<PsiElement>,
results: MutableCollection<UsageInfo>
) {
val callableDescriptor = when (val descriptor = it.unsafeResolveToDescriptor()) {
is CallableDescriptor -> descriptor
is ClassDescriptor -> descriptor.unsubstitutedPrimaryConstructor ?: return
else -> return
}
val usage = KotlinCallableDefinitionUsage<PsiElement>(
it,
callableDescriptor,
primaryFunction,
null,
canDropOverride = false
)
if (results.add(usage)) collectMembers(it, primaryFunction, results)
} }
override fun getParameters(): List<KotlinParameterInfo> = parameters override fun getParameters(): List<KotlinParameterInfo> = parameters
@@ -994,7 +994,7 @@ fun DialogWrapper.showWithTransaction() {
TransactionGuard.submitTransaction(disposable, Runnable { show() }) TransactionGuard.submitTransaction(disposable, Runnable { show() })
} }
fun PsiMethod.checkDeclarationConflict(name: String, conflicts: MultiMap<PsiElement, String>, callables: List<PsiElement>) { fun PsiMethod.checkDeclarationConflict(name: String, conflicts: MultiMap<PsiElement, String>, callables: Collection<PsiElement>) {
containingClass containingClass
?.findMethodsByName(name, true) ?.findMethodsByName(name, true)
// as is necessary here: see KT-10386 // as is necessary here: see KT-10386
@@ -0,0 +1 @@
expect fun f1(i: Int, s: String)
@@ -0,0 +1 @@
expect fun String.f1(i: Int)
@@ -0,0 +1,2 @@
//
actual fun f1(i: Int, s: String) {}
@@ -0,0 +1,2 @@
//
actual fun String.f1(i: Int) {}
@@ -0,0 +1,2 @@
// "Convert parameter to receiver" "true"
actual fun f1(i: Int, <caret>s: String) {}
@@ -0,0 +1,2 @@
// "Convert parameter to receiver" "true"
actual fun String.f1(i: Int<caret>) {}
@@ -0,0 +1,2 @@
// "Convert parameter to receiver" "true"
expect fun f1(i: Int, <caret>s: String)
@@ -0,0 +1,2 @@
// "Convert parameter to receiver" "true"
expect fun String.f1(i: Int<caret>)
@@ -0,0 +1,2 @@
//
actual fun f1(i: Int, s: String) {}
@@ -0,0 +1,2 @@
//
actual fun String.f1(i: Int) {}
@@ -0,0 +1 @@
actual fun f1(i: Int, s: String) {}
@@ -0,0 +1 @@
actual fun String.f1(i: Int) {}
@@ -0,0 +1,3 @@
expect class A {
fun c(a: Int, b: String)
}
@@ -0,0 +1,3 @@
expect class A {
fun String.c(a: Int)
}
@@ -0,0 +1,7 @@
actual open class A {
actual open fun c(a: Int, b: String) {}
}
class B : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,7 @@
actual open class A {
actual open fun String.c(a: Int) {}
}
class B : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,8 @@
// "Convert parameter to receiver" "true"
actual open class A {
actual open fun c(a: Int, b: String) {}
}
class B : A() {
override fun c(a: Int, <caret>b: String) {}
}
@@ -0,0 +1,8 @@
// "Convert parameter to receiver" "true"
actual open class A {
actual open fun String.c(a: Int) {}
}
class B : A() {
override fun String.c(a: Int<caret>) {}
}
@@ -0,0 +1,3 @@
expect class A {
fun c(a: Int, b: String) {}
}
@@ -0,0 +1,3 @@
expect class A {
fun String.c(a: Int) {}
}
@@ -0,0 +1,8 @@
//
actual open class A {
actual open fun c(a: Int, b: String) {}
}
class B : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,8 @@
//
actual open class A {
actual open fun String.c(a: Int) {}
}
class B : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,8 @@
// "Convert parameter to receiver" "true"
actual open class A {
actual open fun c(a: Int, <caret>b: String) {}
}
class B : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,8 @@
// "Convert parameter to receiver" "true"
actual open class A {
actual open fun String.c(a: Int<caret>) {}
}
class B : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,4 @@
// "Convert parameter to receiver" "true"
expect class A {
fun c(a: Int, <caret>b: String)
}
@@ -0,0 +1,4 @@
// "Convert parameter to receiver" "true"
expect class A {
fun String.c(a: Int<caret>)
}
@@ -0,0 +1,8 @@
//
actual open class A {
actual open fun c(a: Int, b: String) {}
}
class B : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,8 @@
//
actual open class A {
actual open fun String.c(a: Int) {}
}
class B : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,7 @@
actual open class A {
actual open fun c(a: Int, b: String) {}
}
class B : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,7 @@
actual open class A {
actual open fun String.c(a: Int) {}
}
class B : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,8 @@
// "Convert parameter to receiver" "true"
expect open class A() {
open fun c(a: Int, <caret>b: String)
}
class C : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,8 @@
// "Convert parameter to receiver" "true"
expect open class A() {
open fun String.c(a: Int<caret>)
}
class C : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,8 @@
//
actual open class A {
actual open fun c(a: Int, b: String) {}
}
class B : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,8 @@
//
actual open class A {
actual open fun String.c(a: Int) {}
}
class B : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,7 @@
actual open class A {
actual open fun c(a: Int, b: String) {}
}
class B : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,7 @@
actual open class A {
actual open fun String.c(a: Int) {}
}
class B : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,8 @@
// "Convert parameter to receiver" "true"
expect open class A() {
open fun c(a: Int, b: String)
}
class C : A() {
override fun c(a: Int, <caret>b: String) {}
}
@@ -0,0 +1,8 @@
// "Convert parameter to receiver" "true"
expect open class A() {
open fun String.c(a: Int)
}
class C : A() {
override fun String.c(a: Int<caret>) {}
}
@@ -0,0 +1,8 @@
//
actual open class A {
actual open fun c(a: Int, b: String) {}
}
class B : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,8 @@
//
actual open class A {
actual open fun String.c(a: Int) {}
}
class B : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,7 @@
actual open class A {
actual open fun c(a: Int, b: String) {}
}
class B : A() {
override fun c(a: Int, b: String) {}
}
@@ -0,0 +1,7 @@
actual open class A {
actual open fun String.c(a: Int) {}
}
class B : A() {
override fun String.c(a: Int) {}
}
@@ -0,0 +1,11 @@
expect open class A() {
open fun c(a: Int, b: String)
}
class C : A() {
override fun c(a: Int, b: String) {}
}
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
@@ -0,0 +1,11 @@
expect open class A() {
open fun String.c(a: Int)
}
class C : A() {
override fun String.c(a: Int) {}
}
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
@@ -0,0 +1,18 @@
//
actual open class A {
actual open fun c(a: Int, b: String) {}
}
open class B : A() {
override fun c(a: Int, b: String) {}
}
open class D : B() {
override fun c(a: Int, b: String) {}
}
fun test(a: Int, b: String) {
with(A()) {
c(a, b)
}
}
@@ -0,0 +1,18 @@
//
actual open class A {
actual open fun String.c(a: Int) {}
}
open class B : A() {
override fun String.c(a: Int) {}
}
open class D : B() {
override fun String.c(a: Int) {}
}
fun test(a: Int, b: String) {
with(A()) {
b.c(a)
}
}
@@ -0,0 +1,18 @@
// "Convert parameter to receiver" "true"
actual open class A {
actual open fun c(a: Int, b: String) {}
}
open class B : A() {
override fun c(a: Int, <caret>b: String) {}
}
open class D : B() {
override fun c(a: Int, b: String) {}
}
fun test(a: Int, b: String) {
with(A()) {
c(a, b)
}
}
@@ -0,0 +1,18 @@
// "Convert parameter to receiver" "true"
actual open class A {
actual open fun String.c(a: Int) {}
}
open class B : A() {
override fun String.c(a: Int<caret>) {}
}
open class D : B() {
override fun String.c(a: Int) {}
}
fun test(a: Int, b: String) {
with(A()) {
b.c(a)
}
}
@@ -122,6 +122,59 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
} }
} }
@TestMetadata("idea/testData/multiModuleQuickFix/changeSignature")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ChangeSignature extends AbstractQuickFixMultiModuleTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
@TestMetadata("actual")
public void testActual() throws Exception {
runTest("idea/testData/multiModuleQuickFix/changeSignature/actual/");
}
public void testAllFilesPresentInChangeSignature() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/multiModuleQuickFix/changeSignature"), Pattern.compile("^([^\\.]+)$"), TargetBackend.ANY, true);
}
@TestMetadata("expect")
public void testExpect() throws Exception {
runTest("idea/testData/multiModuleQuickFix/changeSignature/expect/");
}
@TestMetadata("override")
public void testOverride() throws Exception {
runTest("idea/testData/multiModuleQuickFix/changeSignature/override/");
}
@TestMetadata("override2")
public void testOverride2() throws Exception {
runTest("idea/testData/multiModuleQuickFix/changeSignature/override2/");
}
@TestMetadata("override3")
public void testOverride3() throws Exception {
runTest("idea/testData/multiModuleQuickFix/changeSignature/override3/");
}
@TestMetadata("override4")
public void testOverride4() throws Exception {
runTest("idea/testData/multiModuleQuickFix/changeSignature/override4/");
}
@TestMetadata("override5")
public void testOverride5() throws Exception {
runTest("idea/testData/multiModuleQuickFix/changeSignature/override5/");
}
@TestMetadata("override6")
public void testOverride6() throws Exception {
runTest("idea/testData/multiModuleQuickFix/changeSignature/override6/");
}
}
@TestMetadata("idea/testData/multiModuleQuickFix/createActual") @TestMetadata("idea/testData/multiModuleQuickFix/createActual")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)