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 {
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)
}
@@ -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) {
@@ -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<PsiReference, DeclarationDescriptor>()
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 }
@@ -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)
}
@@ -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<JCheckBox, JetParameter>(descriptor.parametersToRemove.size())
private val removeParamsCheckBoxes = LinkedHashMap<JCheckBox, JetElement>(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)
@@ -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<JetParameter, List<PsiReference>>,
val parametersUsages: Map<JetElement, List<JetElement>>,
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 originalOccurrence: JetPsiRange
@@ -103,16 +106,16 @@ public data class IntroduceParameterDescriptor(
fun getParametersToRemove(
withDefaultValue: Boolean,
parametersUsages: Map<JetParameter, List<PsiReference>>,
parametersUsages: Map<JetElement, List<JetElement>>,
occurrencesToReplace: List<JetPsiRange>
): List<JetParameter> {
): List<JetElement> {
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<JetParameter, List<PsiReference>> {
return targetParent.getValueParameters()
private fun findInternalUsagesOfParametersAndReceiver(
targetParent: JetNamedDeclaration,
targetDescriptor: FunctionDescriptor
): Map<JetElement, List<JetElement>> {
val usages = ArrayList<Pair<JetElement, List<JetElement>>>()
targetParent.getValueParameters()
.filter { !it.hasValOrVar() }
.map {
it to DefaultSearchHelper<JetParameter>()
.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<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 {
@@ -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()
)
@@ -2,19 +2,19 @@
public inline fun <T, R> 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)
}
}
@@ -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);
}
@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");