diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/descriptorUtils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/descriptorUtils.kt index d30e0de77c6..9a39a6ba284 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/descriptorUtils.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/descriptorUtils.kt @@ -59,7 +59,9 @@ fun DeclarationDescriptorWithVisibility.isVisible( public fun compareDescriptors(project: Project, d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean { if (d1 == d2) return true if (d1 == null || d2 == null) return false - if (DescriptorToSourceUtilsIde.getAllDeclarations(project, d1) == DescriptorToSourceUtilsIde.getAllDeclarations(project, d2)) return true + val declarations1 = DescriptorToSourceUtilsIde.getAllDeclarations(project, d1) + val declarations2 = DescriptorToSourceUtilsIde.getAllDeclarations(project, d2) + if (declarations1 == declarations2 && declarations1.isNotEmpty()) return true return DescriptorRenderer.FQ_NAMES_IN_TYPES.render(d1) == DescriptorRenderer.FQ_NAMES_IN_TYPES.render(d2) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetMutableMethodDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetMutableMethodDescriptor.kt index 30529227ac2..6794773a11c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetMutableMethodDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetMutableMethodDescriptor.kt @@ -34,7 +34,10 @@ public class JetMutableMethodDescriptor(val original: JetMethodDescriptor): JetM } public fun removeParameter(index: Int) { - parameters.remove(index) + val paramInfo = parameters.remove(index) + if (paramInfo == receiver) { + receiver = null + } } public fun renameParameter(index: Int, newName: String) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetParameterInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetParameterInfo.kt index c5a7d506bc0..2130cb187c1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetParameterInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetParameterInfo.kt @@ -22,6 +22,7 @@ import com.intellij.refactoring.changeSignature.ParameterInfo import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.compareDescriptors import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.JetFunctionDefinitionUsage import org.jetbrains.kotlin.idea.references.JetReference import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers @@ -53,21 +54,34 @@ public class JetParameterInfo( val file = defaultValueForCall?.getContainingFile() as? JetFile defaultValueParameterReferences = if (defaultValueForCall != null && file != null && (file.isPhysical() || file.analysisContext != null)) { + val project = file.getProject() val map = LinkedHashMap() defaultValueForCall!!.accept( object : JetTreeVisitorVoid() { private fun selfParameterOrNull(parameter: DeclarationDescriptor?): ValueParameterDescriptor? { return if (parameter is ValueParameterDescriptor && - parameter.getContainingDeclaration() == functionDescriptor) parameter else null + compareDescriptors(project, parameter.getContainingDeclaration(), functionDescriptor)) { + parameter + } else null + } + + private fun selfReceiverOrNull(receiverDescriptor: DeclarationDescriptor?): DeclarationDescriptor? { + if (compareDescriptors(project, + receiverDescriptor, + functionDescriptor.getExtensionReceiverParameter()?.getContainingDeclaration())) { + return receiverDescriptor + } + if (compareDescriptors(project, + receiverDescriptor, + functionDescriptor.getDispatchReceiverParameter()?.getContainingDeclaration())) { + return receiverDescriptor + } + return null } private fun selfReceiverOrNull(receiver: ThisReceiver?): DeclarationDescriptor? { - return when (receiver) { - functionDescriptor.getExtensionReceiverParameter()?.getValue(), - functionDescriptor.getDispatchReceiverParameter()?.getValue()-> receiver?.getDeclarationDescriptor() - else -> null - } + return selfReceiverOrNull(receiver?.getDeclarationDescriptor()) } private fun getRelevantDescriptor( @@ -86,11 +100,7 @@ public class JetParameterInfo( val resolvedCall = expression.getResolvedCall(context) ?: return null (resolvedCall.getResultingDescriptor() as? ReceiverParameterDescriptor)?.let { - return when (it) { - functionDescriptor.getDispatchReceiverParameter(), - functionDescriptor.getExtensionReceiverParameter() -> it - else -> null - } + return if (selfReceiverOrNull(it.getContainingDeclaration()) != null) it else null } selfReceiverOrNull(resolvedCall.getExtensionReceiver() as? ThisReceiver)?.let { return it } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt index d216fc06f3f..d7de4f7543b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt @@ -168,6 +168,13 @@ public class KotlinInplaceParameterIntroducer( var addedRange: TextRange? = null val builder = StringBuilder() + (callable as? JetFunction)?.getReceiverTypeReference()?.let { receiverTypeRef -> + builder.append(receiverTypeRef.getText()).append('.') + if (!descriptor.withDefaultValue && receiverTypeRef in parametersToRemove) { + rangesToRemove.add(TextRange(0, builder.length())) + } + } + builder.append(callable.getName()) val parameters = callable.getValueParameters() @@ -192,7 +199,7 @@ public class KotlinInplaceParameterIntroducer( if (parameter == addedParameter) { addedRange = range } - else if (parameter in parametersToRemove && !descriptor.withDefaultValue) { + else if (!descriptor.withDefaultValue && parameter in parametersToRemove) { rangesToRemove.add(range) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt index d607a29b771..051ae041722 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt @@ -90,7 +90,7 @@ public class KotlinIntroduceParameterDialog private constructor( private val typeField = NameSuggestionsField(typeNameSuggestions, project, JetFileType.INSTANCE) private var replaceAllCheckBox: JCheckBox? = null private var defaultValueCheckBox: JCheckBox? = null - private val removeParamsCheckBoxes = LinkedHashMap(descriptor.parametersToRemove.size()) + private val removeParamsCheckBoxes = LinkedHashMap(descriptor.parametersToRemove.size()) private var parameterTablePanel: KotlinParameterTablePanel? = null private val commandName = if (lambdaExtractionDescriptor != null) INTRODUCE_LAMBDA_PARAMETER else INTRODUCE_PARAMETER @@ -218,7 +218,8 @@ public class KotlinIntroduceParameterDialog private constructor( } for (parameter in descriptor.parametersToRemove) { - val cb = NonFocusableCheckBox("Remove parameter '${parameter.getName()}' no longer used") + val removeWhat = if (parameter is JetParameter) "parameter '${parameter.getName()}'" else "receiver" + val cb = NonFocusableCheckBox("Remove $removeWhat no longer used") removeParamsCheckBoxes[cb] = parameter cb.setSelected(true) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt index 0ab3f640104..768a1e8e597 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt @@ -33,6 +33,7 @@ import com.intellij.usageView.UsageInfo import com.intellij.usageView.UsageViewDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester @@ -58,8 +59,10 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils -import java.util.Collections +import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver +import java.util.* import kotlin.test.fail public data class IntroduceParameterDescriptor( @@ -70,9 +73,9 @@ public data class IntroduceParameterDescriptor( val newParameterTypeText: String, val newArgumentValue: JetExpression, val withDefaultValue: Boolean, - val parametersUsages: Map>, + val parametersUsages: Map>, val occurrencesToReplace: List, - val parametersToRemove: List = getParametersToRemove(withDefaultValue, parametersUsages, occurrencesToReplace), + val parametersToRemove: List = getParametersToRemove(withDefaultValue, parametersUsages, occurrencesToReplace), val occurrenceReplacer: IntroduceParameterDescriptor.(JetPsiRange) -> Unit = {} ) { val originalOccurrence: JetPsiRange @@ -103,16 +106,16 @@ public data class IntroduceParameterDescriptor( fun getParametersToRemove( withDefaultValue: Boolean, - parametersUsages: Map>, + parametersUsages: Map>, occurrencesToReplace: List -): List { +): List { if (withDefaultValue) return Collections.emptyList() val occurrenceRanges = occurrencesToReplace.map { it.getTextRange() } return parametersUsages.entrySet() .filter { - it.value.all { paramRef -> - occurrenceRanges.any { occurrenceRange -> occurrenceRange.contains(paramRef.getElement().getTextRange()) } + it.value.all { paramUsage -> + occurrenceRanges.any { occurrenceRange -> occurrenceRange.contains(paramUsage.getTextRange()) } } } .map { it.key } @@ -127,7 +130,11 @@ fun IntroduceParameterDescriptor.performRefactoring() { val parameters = callable.getValueParameters() val withReceiver = methodDescriptor.receiver != null parametersToRemove - .map { parameters.indexOf(it) + if (withReceiver) 1 else 0 } + .map { + if (it is JetParameter) { + parameters.indexOf(it) + if (withReceiver) 1 else 0 + } else 0 + } .sortDescending() .forEach { methodDescriptor.removeParameter(it) } } @@ -227,7 +234,7 @@ public open class KotlinIntroduceParameterHandler( val nameValidator = JetNameValidatorImpl(body, null, JetNameValidatorImpl.Target.PROPERTIES) val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(replacementType, nameValidator, "p")) - val parametersUsages = findInternalParameterUsages(targetParent) + val parametersUsages = findInternalUsagesOfParametersAndReceiver(targetParent, functionDescriptor) val forbiddenRanges = if (targetParent is JetClass) { @@ -331,17 +338,51 @@ public open class KotlinIntroduceParameterHandler( } } -private fun findInternalParameterUsages(targetParent: JetNamedDeclaration): Map> { - return targetParent.getValueParameters() +private fun findInternalUsagesOfParametersAndReceiver( + targetParent: JetNamedDeclaration, + targetDescriptor: FunctionDescriptor +): Map> { + val usages = ArrayList>>() + targetParent.getValueParameters() .filter { !it.hasValOrVar() } .map { it to DefaultSearchHelper() .newRequest(UsagesSearchTarget(element = it)) .search() - .toList() + .map { it.getElement() as JetElement } } - .filter { it.second.isNotEmpty() } - .toMap() + .filterTo(usages) { it.second.isNotEmpty() } + val receiverTypeRef = (targetParent as? JetFunction)?.getReceiverTypeReference() + if (receiverTypeRef != null) { + val receiverUsages = ArrayList() + targetParent.acceptChildren( + object : JetTreeVisitorVoid() { + override fun visitThisExpression(expression: JetThisExpression) { + super.visitThisExpression(expression) + + if (expression.getInstanceReference().getReference()?.resolve() == targetDescriptor) { + receiverUsages.add(expression) + } + } + + override fun visitJetElement(element: JetElement) { + super.visitJetElement(element) + + val bindingContext = element.analyze() + val resolvedCall = element.getResolvedCall(bindingContext) ?: return + + if ((resolvedCall.getExtensionReceiver() as? ThisReceiver)?.getDeclarationDescriptor() == targetDescriptor || + (resolvedCall.getDispatchReceiver() as? ThisReceiver)?.getDeclarationDescriptor() == targetDescriptor) { + receiverUsages.add(resolvedCall.getCall().getCallElement()) + } + } + } + ) + if (receiverUsages.isNotEmpty()) { + usages.add(receiverTypeRef to receiverUsages) + } + } + return usages.toMap() } trait KotlinIntroduceLambdaParameterHelper: KotlinIntroduceParameterHelper { @@ -376,7 +417,7 @@ public open class KotlinIntroduceLambdaParameterHandler( newParameterTypeText = "", // to be chosen in the dialog newArgumentValue = JetPsiFactory(project).createExpression("{}"), // substituted later withDefaultValue = false, - parametersUsages = findInternalParameterUsages(callable), + parametersUsages = findInternalUsagesOfParametersAndReceiver(callable, callableDescriptor), occurrencesToReplace = listOf(originalRange), parametersToRemove = listOf() ) diff --git a/idea/testData/refactoring/introduceParameter/substituteDoubleReceiver.kt.after b/idea/testData/refactoring/introduceParameter/substituteDoubleReceiver.kt.after index 9f0b52ac3b9..d575fce04c7 100644 --- a/idea/testData/refactoring/introduceParameter/substituteDoubleReceiver.kt.after +++ b/idea/testData/refactoring/introduceParameter/substituteDoubleReceiver.kt.after @@ -2,19 +2,19 @@ public inline fun with(receiver: T, f: T.() -> R): R = receiver.f() class A(val a: Int) { - fun A.foo(i: Int): Int { + fun foo(i: Int): Int { return i / 2 } fun test() { val a1 = A(1) - a1.foo(a + a1.a) + foo(a + a1.a) } } fun test() { val t = with(A(1)) { val a = A(2) - a.foo(this.a + a.a) + foo(this.a + a.a) } } \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/unusedDispatchReceiver.kt b/idea/testData/refactoring/introduceParameter/unusedDispatchReceiver.kt new file mode 100644 index 00000000000..753b523c499 --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/unusedDispatchReceiver.kt @@ -0,0 +1,14 @@ +// WITH_RUNTIME +// WITH_DEFAULT_VALUE: false +class A(val n: Int) { + fun foo(): Int { + return n + 1 + } +} + +fun test() { + A(1).foo() + with(A(1)) { + foo() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/unusedDispatchReceiver.kt.after b/idea/testData/refactoring/introduceParameter/unusedDispatchReceiver.kt.after new file mode 100644 index 00000000000..5e166c8cc54 --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/unusedDispatchReceiver.kt.after @@ -0,0 +1,15 @@ +// WITH_RUNTIME +// WITH_DEFAULT_VALUE: false +class A(val n: Int) { + fun foo(i: Int): Int { + return i + } +} + +fun test() { + val a = A(1) + a.foo(a.n + 1) + with(A(1)) { + foo(n + 1) + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/unusedExtensionReceiver.kt b/idea/testData/refactoring/introduceParameter/unusedExtensionReceiver.kt new file mode 100644 index 00000000000..a213f3e7f5d --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/unusedExtensionReceiver.kt @@ -0,0 +1,14 @@ +// WITH_RUNTIME +// WITH_DEFAULT_VALUE: false +class A(val n: Int) + +fun A.foo(): Int { + return n + 1 +} + +fun test() { + A(1).foo() + with(A(1)) { + foo() + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/unusedExtensionReceiver.kt.after b/idea/testData/refactoring/introduceParameter/unusedExtensionReceiver.kt.after new file mode 100644 index 00000000000..191b02e9b2c --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/unusedExtensionReceiver.kt.after @@ -0,0 +1,15 @@ +// WITH_RUNTIME +// WITH_DEFAULT_VALUE: false +class A(val n: Int) + +fun foo(i: Int): Int { + return i +} + +fun test() { + val a = A(1) + foo(a.n + 1) + with(A(1)) { + foo(n + 1) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java index 88ec25df485..c68f5ed50f6 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java @@ -2569,6 +2569,18 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doIntroduceSimpleParameterTest(fileName); } + @TestMetadata("unusedDispatchReceiver.kt") + public void testUnusedDispatchReceiver() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/unusedDispatchReceiver.kt"); + doIntroduceSimpleParameterTest(fileName); + } + + @TestMetadata("unusedExtensionReceiver.kt") + public void testUnusedExtensionReceiver() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/unusedExtensionReceiver.kt"); + doIntroduceSimpleParameterTest(fileName); + } + @TestMetadata("valueAtCallSite.kt") public void testValueAtCallSite() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/valueAtCallSite.kt");