Introduce Parameter: Suggest removing extension receiver which becomes unused after new parameter is added

This commit is contained in:
Alexey Sedunov
2015-06-17 16:05:50 +03:00
parent 5409b19abc
commit 378c1744cb
12 changed files with 168 additions and 34 deletions
@@ -59,7 +59,9 @@ fun DeclarationDescriptorWithVisibility.isVisible(
public fun compareDescriptors(project: Project, d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean { public fun compareDescriptors(project: Project, d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean {
if (d1 == d2) return true if (d1 == d2) return true
if (d1 == null || d2 == null) return false 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) return DescriptorRenderer.FQ_NAMES_IN_TYPES.render(d1) == DescriptorRenderer.FQ_NAMES_IN_TYPES.render(d2)
} }
@@ -34,7 +34,10 @@ public class JetMutableMethodDescriptor(val original: JetMethodDescriptor): JetM
} }
public fun removeParameter(index: Int) { 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) { public fun renameParameter(index: Int, newName: String) {
@@ -22,6 +22,7 @@ import com.intellij.refactoring.changeSignature.ParameterInfo
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze 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.refactoring.changeSignature.usages.JetFunctionDefinitionUsage
import org.jetbrains.kotlin.idea.references.JetReference import org.jetbrains.kotlin.idea.references.JetReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
@@ -53,21 +54,34 @@ public class JetParameterInfo(
val file = defaultValueForCall?.getContainingFile() as? JetFile val file = defaultValueForCall?.getContainingFile() as? JetFile
defaultValueParameterReferences = defaultValueParameterReferences =
if (defaultValueForCall != null && file != null && (file.isPhysical() || file.analysisContext != null)) { if (defaultValueForCall != null && file != null && (file.isPhysical() || file.analysisContext != null)) {
val project = file.getProject()
val map = LinkedHashMap<PsiReference, DeclarationDescriptor>() val map = LinkedHashMap<PsiReference, DeclarationDescriptor>()
defaultValueForCall!!.accept( defaultValueForCall!!.accept(
object : JetTreeVisitorVoid() { object : JetTreeVisitorVoid() {
private fun selfParameterOrNull(parameter: DeclarationDescriptor?): ValueParameterDescriptor? { private fun selfParameterOrNull(parameter: DeclarationDescriptor?): ValueParameterDescriptor? {
return if (parameter is 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? { private fun selfReceiverOrNull(receiver: ThisReceiver?): DeclarationDescriptor? {
return when (receiver) { return selfReceiverOrNull(receiver?.getDeclarationDescriptor())
functionDescriptor.getExtensionReceiverParameter()?.getValue(),
functionDescriptor.getDispatchReceiverParameter()?.getValue()-> receiver?.getDeclarationDescriptor()
else -> null
}
} }
private fun getRelevantDescriptor( private fun getRelevantDescriptor(
@@ -86,11 +100,7 @@ public class JetParameterInfo(
val resolvedCall = expression.getResolvedCall(context) ?: return null val resolvedCall = expression.getResolvedCall(context) ?: return null
(resolvedCall.getResultingDescriptor() as? ReceiverParameterDescriptor)?.let { (resolvedCall.getResultingDescriptor() as? ReceiverParameterDescriptor)?.let {
return when (it) { return if (selfReceiverOrNull(it.getContainingDeclaration()) != null) it else null
functionDescriptor.getDispatchReceiverParameter(),
functionDescriptor.getExtensionReceiverParameter() -> it
else -> null
}
} }
selfReceiverOrNull(resolvedCall.getExtensionReceiver() as? ThisReceiver)?.let { return it } selfReceiverOrNull(resolvedCall.getExtensionReceiver() as? ThisReceiver)?.let { return it }
@@ -168,6 +168,13 @@ public class KotlinInplaceParameterIntroducer(
var addedRange: TextRange? = null var addedRange: TextRange? = null
val builder = StringBuilder() 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()) builder.append(callable.getName())
val parameters = callable.getValueParameters() val parameters = callable.getValueParameters()
@@ -192,7 +199,7 @@ public class KotlinInplaceParameterIntroducer(
if (parameter == addedParameter) { if (parameter == addedParameter) {
addedRange = range addedRange = range
} }
else if (parameter in parametersToRemove && !descriptor.withDefaultValue) { else if (!descriptor.withDefaultValue && parameter in parametersToRemove) {
rangesToRemove.add(range) rangesToRemove.add(range)
} }
@@ -90,7 +90,7 @@ public class KotlinIntroduceParameterDialog private constructor(
private val typeField = NameSuggestionsField(typeNameSuggestions, project, JetFileType.INSTANCE) private val typeField = NameSuggestionsField(typeNameSuggestions, project, JetFileType.INSTANCE)
private var replaceAllCheckBox: JCheckBox? = null private var replaceAllCheckBox: JCheckBox? = null
private var defaultValueCheckBox: JCheckBox? = null private var defaultValueCheckBox: JCheckBox? = null
private val removeParamsCheckBoxes = LinkedHashMap<JCheckBox, JetParameter>(descriptor.parametersToRemove.size()) private val removeParamsCheckBoxes = LinkedHashMap<JCheckBox, JetElement>(descriptor.parametersToRemove.size())
private var parameterTablePanel: KotlinParameterTablePanel? = null private var parameterTablePanel: KotlinParameterTablePanel? = null
private val commandName = if (lambdaExtractionDescriptor != null) INTRODUCE_LAMBDA_PARAMETER else INTRODUCE_PARAMETER 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) { 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 removeParamsCheckBoxes[cb] = parameter
cb.setSelected(true) cb.setSelected(true)
@@ -33,6 +33,7 @@ import com.intellij.usageView.UsageInfo
import com.intellij.usageView.UsageViewDescriptor import com.intellij.usageView.UsageViewDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor 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.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester 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.*
import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils 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 import kotlin.test.fail
public data class IntroduceParameterDescriptor( public data class IntroduceParameterDescriptor(
@@ -70,9 +73,9 @@ public data class IntroduceParameterDescriptor(
val newParameterTypeText: String, val newParameterTypeText: String,
val newArgumentValue: JetExpression, val newArgumentValue: JetExpression,
val withDefaultValue: Boolean, val withDefaultValue: Boolean,
val parametersUsages: Map<JetParameter, List<PsiReference>>, val parametersUsages: Map<JetElement, List<JetElement>>,
val occurrencesToReplace: List<JetPsiRange>, val occurrencesToReplace: List<JetPsiRange>,
val parametersToRemove: List<JetParameter> = getParametersToRemove(withDefaultValue, parametersUsages, occurrencesToReplace), val parametersToRemove: List<JetElement> = getParametersToRemove(withDefaultValue, parametersUsages, occurrencesToReplace),
val occurrenceReplacer: IntroduceParameterDescriptor.(JetPsiRange) -> Unit = {} val occurrenceReplacer: IntroduceParameterDescriptor.(JetPsiRange) -> Unit = {}
) { ) {
val originalOccurrence: JetPsiRange val originalOccurrence: JetPsiRange
@@ -103,16 +106,16 @@ public data class IntroduceParameterDescriptor(
fun getParametersToRemove( fun getParametersToRemove(
withDefaultValue: Boolean, withDefaultValue: Boolean,
parametersUsages: Map<JetParameter, List<PsiReference>>, parametersUsages: Map<JetElement, List<JetElement>>,
occurrencesToReplace: List<JetPsiRange> occurrencesToReplace: List<JetPsiRange>
): List<JetParameter> { ): List<JetElement> {
if (withDefaultValue) return Collections.emptyList() if (withDefaultValue) return Collections.emptyList()
val occurrenceRanges = occurrencesToReplace.map { it.getTextRange() } val occurrenceRanges = occurrencesToReplace.map { it.getTextRange() }
return parametersUsages.entrySet() return parametersUsages.entrySet()
.filter { .filter {
it.value.all { paramRef -> it.value.all { paramUsage ->
occurrenceRanges.any { occurrenceRange -> occurrenceRange.contains(paramRef.getElement().getTextRange()) } occurrenceRanges.any { occurrenceRange -> occurrenceRange.contains(paramUsage.getTextRange()) }
} }
} }
.map { it.key } .map { it.key }
@@ -127,7 +130,11 @@ fun IntroduceParameterDescriptor.performRefactoring() {
val parameters = callable.getValueParameters() val parameters = callable.getValueParameters()
val withReceiver = methodDescriptor.receiver != null val withReceiver = methodDescriptor.receiver != null
parametersToRemove 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() .sortDescending()
.forEach { methodDescriptor.removeParameter(it) } .forEach { methodDescriptor.removeParameter(it) }
} }
@@ -227,7 +234,7 @@ public open class KotlinIntroduceParameterHandler(
val nameValidator = JetNameValidatorImpl(body, null, JetNameValidatorImpl.Target.PROPERTIES) val nameValidator = JetNameValidatorImpl(body, null, JetNameValidatorImpl.Target.PROPERTIES)
val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(replacementType, nameValidator, "p")) val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(replacementType, nameValidator, "p"))
val parametersUsages = findInternalParameterUsages(targetParent) val parametersUsages = findInternalUsagesOfParametersAndReceiver(targetParent, functionDescriptor)
val forbiddenRanges = val forbiddenRanges =
if (targetParent is JetClass) { if (targetParent is JetClass) {
@@ -331,17 +338,51 @@ public open class KotlinIntroduceParameterHandler(
} }
} }
private fun findInternalParameterUsages(targetParent: JetNamedDeclaration): Map<JetParameter, List<PsiReference>> { private fun findInternalUsagesOfParametersAndReceiver(
return targetParent.getValueParameters() targetParent: JetNamedDeclaration,
targetDescriptor: FunctionDescriptor
): Map<JetElement, List<JetElement>> {
val usages = ArrayList<Pair<JetElement, List<JetElement>>>()
targetParent.getValueParameters()
.filter { !it.hasValOrVar() } .filter { !it.hasValOrVar() }
.map { .map {
it to DefaultSearchHelper<JetParameter>() it to DefaultSearchHelper<JetParameter>()
.newRequest(UsagesSearchTarget(element = it)) .newRequest(UsagesSearchTarget(element = it))
.search() .search()
.toList() .map { it.getElement() as JetElement }
} }
.filter { it.second.isNotEmpty() } .filterTo(usages) { it.second.isNotEmpty() }
.toMap() val receiverTypeRef = (targetParent as? JetFunction)?.getReceiverTypeReference()
if (receiverTypeRef != null) {
val receiverUsages = ArrayList<JetElement>()
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 { trait KotlinIntroduceLambdaParameterHelper: KotlinIntroduceParameterHelper {
@@ -376,7 +417,7 @@ public open class KotlinIntroduceLambdaParameterHandler(
newParameterTypeText = "", // to be chosen in the dialog newParameterTypeText = "", // to be chosen in the dialog
newArgumentValue = JetPsiFactory(project).createExpression("{}"), // substituted later newArgumentValue = JetPsiFactory(project).createExpression("{}"), // substituted later
withDefaultValue = false, withDefaultValue = false,
parametersUsages = findInternalParameterUsages(callable), parametersUsages = findInternalUsagesOfParametersAndReceiver(callable, callableDescriptor),
occurrencesToReplace = listOf(originalRange), occurrencesToReplace = listOf(originalRange),
parametersToRemove = listOf() parametersToRemove = listOf()
) )
@@ -2,19 +2,19 @@
public inline fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f() public inline fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
class A(val a: Int) { class A(val a: Int) {
fun A.foo(i: Int): Int { fun foo(i: Int): Int {
return i / 2 return i / 2
} }
fun test() { fun test() {
val a1 = A(1) val a1 = A(1)
a1.foo(a + a1.a) foo(a + a1.a)
} }
} }
fun test() { fun test() {
val t = with(A(1)) { val t = with(A(1)) {
val a = A(2) val a = A(2)
a.foo(this.a + a.a) foo(this.a + a.a)
} }
} }
@@ -0,0 +1,14 @@
// WITH_RUNTIME
// WITH_DEFAULT_VALUE: false
class A(val n: Int) {
fun foo(): Int {
return <selection>n + 1</selection>
}
}
fun test() {
A(1).foo()
with(A(1)) {
foo()
}
}
@@ -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)
}
}
@@ -0,0 +1,14 @@
// WITH_RUNTIME
// WITH_DEFAULT_VALUE: false
class A(val n: Int)
fun A.foo(): Int {
return <selection>n + 1</selection>
}
fun test() {
A(1).foo()
with(A(1)) {
foo()
}
}
@@ -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)
}
}
@@ -2569,6 +2569,18 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
doIntroduceSimpleParameterTest(fileName); 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") @TestMetadata("valueAtCallSite.kt")
public void testValueAtCallSite() throws Exception { public void testValueAtCallSite() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/valueAtCallSite.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/valueAtCallSite.kt");