diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtClass.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtClass.kt index a869198cc52..f526810106c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtClass.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtClass.kt @@ -45,6 +45,7 @@ public open class KtClass : KtClassOrObject { _stub?.isInterface() ?: (findChildByType(KtTokens.INTERFACE_KEYWORD) != null) public fun isEnum(): Boolean = hasModifier(KtTokens.ENUM_KEYWORD) + public fun isData(): Boolean = hasModifier(KtTokens.DATA_KEYWORD) public fun isSealed(): Boolean = hasModifier(KtTokens.SEALED_KEYWORD) public fun isInner(): Boolean = hasModifier(KtTokens.INNER_KEYWORD) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt index 55e985b0ba6..0206fa02e1a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt @@ -36,6 +36,9 @@ public fun KtPsiFactory.createExpressionByPattern(pattern: String, vararg args: public fun KtPsiFactory.createDeclarationByPattern(pattern: String, vararg args: Any): TDeclaration = createByPattern(pattern, *args) { createDeclaration(it) } +public fun KtPsiFactory.createDestructuringDeclarationByPattern(pattern: String, vararg args: Any): KtDestructuringDeclaration + = createByPattern(pattern, *args) { createDestructuringDeclaration(it) } + private abstract class ArgumentType(val klass: Class) private class PlainTextArgumentType(klass: Class, val toPlainText: (T) -> String) : ArgumentType(klass) @@ -315,6 +318,10 @@ public fun KtPsiFactory.buildDeclaration(build: BuilderByPattern. return buildByPattern({ pattern, args -> this.createDeclarationByPattern(pattern, *args) }, build) } +public fun KtPsiFactory.buildDestructuringDeclaration(build: BuilderByPattern.() -> Unit): KtDestructuringDeclaration { + return buildByPattern({ pattern, args -> this.createDestructuringDeclarationByPattern(pattern, *args) }, build) +} + public fun buildByPattern(factory: (String, Array) -> TElement, build: BuilderByPattern.() -> Unit): TElement { val builder = BuilderByPattern() builder.build() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt index 1bb88398437..6c2c1296b42 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt @@ -64,10 +64,7 @@ import org.jetbrains.kotlin.kdoc.psi.impl.KDocName import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch -import org.jetbrains.kotlin.psi.psiUtil.getValueParameters -import org.jetbrains.kotlin.psi.psiUtil.isAncestor +import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.resolve.BindingContext @@ -77,6 +74,7 @@ import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.util.* @@ -268,17 +266,28 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor { val newReceiverInfo = changeInfo.receiverParameterInfo - for (parameterInfo in changeInfo.newParameters) { + val isDataClass = functionPsi is KtPrimaryConstructor && (functionPsi.getContainingClassOrObject() as? KtClass)?.isData() ?: false + + for ((i, parameterInfo) in changeInfo.newParameters.withIndex()) { if (parameterInfo.oldIndex >= 0 && parameterInfo.oldIndex < oldParameters.size) { val oldParam = oldParameters[parameterInfo.oldIndex] val oldParamName = oldParam.name - if (parameterInfo == newReceiverInfo || (oldParamName != null && oldParamName != parameterInfo.name)) { + if (parameterInfo == newReceiverInfo || + (oldParamName != null && oldParamName != parameterInfo.name) || + isDataClass && i != parameterInfo.oldIndex) { for (reference in ReferencesSearch.search(oldParam, oldParam.useScope)) { val element = reference.element + if (isDataClass && + element is KtSimpleNameExpression && + (element.parent as? KtCallExpression)?.calleeExpression == element && + element.getReferencedName() != parameterInfo.name && + OperatorNameConventions.COMPONENT_REGEX.matches(element.getReferencedName())) { + result.add(KotlinDataClassComponentUsage(element, "component${i + 1}")) + } // Usages in named arguments of the calls usage will be changed when the function call is changed - if ((element is KtSimpleNameExpression || element is KDocName) && element.parent !is KtValueArgumentName) { + else if ((element is KtSimpleNameExpression || element is KDocName) && element.parent !is KtValueArgumentName) { result.add(KotlinParameterUsage(element as KtElement, parameterInfo, functionUsageInfo)) } } @@ -286,6 +295,15 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor { } } + if (isDataClass) { + (functionPsi as KtPrimaryConstructor).valueParameters.firstOrNull()?.let { + ReferencesSearch.search(it).mapNotNullTo(result) { + val destructuringDeclaration = it.element as? KtDestructuringDeclaration ?: return@mapNotNullTo null + KotlinComponentUsageInDestructuring(destructuringDeclaration) + } + } + } + if (functionPsi is KtFunction && newReceiverInfo != changeInfo.methodDescriptor.receiver) { findOriginalReceiversUsages(functionUsageInfo, result, changeInfo) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinComponentUsageInDestructuring.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinComponentUsageInDestructuring.kt new file mode 100644 index 00000000000..ab29959b02f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinComponentUsageInDestructuring.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.changeSignature.usages + +import com.intellij.usageView.UsageInfo +import org.jetbrains.kotlin.idea.core.CollectingNameValidator +import org.jetbrains.kotlin.idea.core.KotlinNameSuggester +import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator +import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator.Target +import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeInfo +import org.jetbrains.kotlin.idea.refactoring.replaceListPsiAndKeepDelimiters +import org.jetbrains.kotlin.psi.KtDestructuringDeclaration +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.buildDestructuringDeclaration +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange +import org.jetbrains.kotlin.utils.ifEmpty + +class KotlinComponentUsageInDestructuring(element: KtDestructuringDeclaration) : KotlinUsageInfo(element) { + override fun processUsage(changeInfo: KotlinChangeInfo, element: KtDestructuringDeclaration, allUsages: Array): Boolean { + if (!changeInfo.isParameterSetOrOrderChanged) return true + + val currentEntries = element.entries + val newParameterInfos = changeInfo.getNonReceiverParameters() + + val newDestructuring = KtPsiFactory(element).buildDestructuringDeclaration { + val lastIndex = newParameterInfos.indexOfLast { it.oldIndex in currentEntries.indices } + val nameValidator = CollectingNameValidator(filter = NewDeclarationNameValidator(element.parent, null, Target.VARIABLES)) + + appendFixedText("val (") + for (i in 0..lastIndex) { + if (i > 0) { + appendFixedText(", ") + } + + val paramInfo = newParameterInfos[i] + val oldIndex = paramInfo.oldIndex + if (oldIndex >= 0 && oldIndex < currentEntries.size) { + appendChildRange(PsiChildRange.singleElement(currentEntries[oldIndex])) + } + else { + appendFixedText(KotlinNameSuggester.suggestNameByName(paramInfo.name, nameValidator)) + } + } + appendFixedText(")") + } + replaceListPsiAndKeepDelimiters( + element, + newDestructuring, + { + apply { + val oldEntries = entries.ifEmpty { return@apply } + val firstOldEntry = oldEntries.first() + val lastOldEntry = oldEntries.last() + val newEntries = it.entries + if (newEntries.isNotEmpty()) { + addRangeBefore(newEntries.first(), newEntries.last(), firstOldEntry) + } + deleteChildRange(firstOldEntry, lastOldEntry) + } + }, + { entries } + ) + + return true + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinDataClassComponentUsage.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinDataClassComponentUsage.kt new file mode 100644 index 00000000000..e3d39d45292 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinDataClassComponentUsage.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.changeSignature.usages + +import com.intellij.usageView.UsageInfo +import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeInfo +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtSimpleNameExpression + +class KotlinDataClassComponentUsage( + calleeExpression: KtSimpleNameExpression, + private val newName: String +) : KotlinUsageInfo(calleeExpression) { + override fun processUsage(changeInfo: KotlinChangeInfo, element: KtSimpleNameExpression, allUsages: Array): Boolean { + element.replace(KtPsiFactory(element).createExpression(newName)) + return true + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt index 72916a6374c..a6859360fd5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt @@ -722,6 +722,7 @@ fun PsiNamedElement.isInterfaceClass(): Boolean = this is KtClass && isInterface fun replaceListPsiAndKeepDelimiters( originalList: ListType, newList: ListType, + @Suppress("UNCHECKED_CAST") listReplacer: ListType.(ListType) -> ListType = { replace(it) as ListType }, itemsFun: ListType.() -> List ): ListType { originalList.children.takeWhile { it is PsiErrorElement }.forEach { it.delete() } @@ -736,17 +737,13 @@ fun replaceListPsiAndKeepDelimiters( oldParameters[i] = oldParameters[i].replace(newParameters[i]) as KtElement } - @Suppress("UNCHECKED_CAST") - if (commonCount == 0) return originalList.replace(newList) as ListType + if (commonCount == 0) return originalList.listReplacer(newList) if (oldCount > commonCount) { originalList.deleteChildRange(oldParameters[commonCount - 1].nextSibling, oldParameters.last()) } else if (newCount > commonCount) { - originalList.addRangeAfter(newParameters[commonCount - 1].nextSibling, - newList.lastChild.prevSibling, - PsiTreeUtil.skipSiblingsBackward(originalList.lastChild, - PsiWhiteSpace::class.java, PsiComment::class.java)) + originalList.addRangeAfter(newParameters[commonCount - 1].nextSibling, newParameters.last(), oldParameters.last()) } return originalList diff --git a/idea/testData/refactoring/changeSignature/AddDataClassParameterAfter.kt b/idea/testData/refactoring/changeSignature/AddDataClassParameterAfter.kt new file mode 100644 index 00000000000..5d82cce2123 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/AddDataClassParameterAfter.kt @@ -0,0 +1,10 @@ +data class X(val a: Int, val c: Int, val b: Int) { + +} + +fun test() { + val (a, c1, b) = X(1, 3, 2) + val aa = X(1, 3, 2).component1() + val bb = X(1, 3, 2).component3() + for ((a, c, b) in listOf(X(1, 3, 2))) {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/AddDataClassParameterBefore.kt b/idea/testData/refactoring/changeSignature/AddDataClassParameterBefore.kt new file mode 100644 index 00000000000..6d1e2f83c79 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/AddDataClassParameterBefore.kt @@ -0,0 +1,10 @@ +data class X(val a: Int, val b: Int) { + +} + +fun test() { + val (a, b) = X(1, 2) + val aa = X(1, 2).component1() + val bb = X(1, 2).component2() + for ((a, b) in listOf(X(1, 2))) {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveAllOriginalDataClassParametersAfter.kt b/idea/testData/refactoring/changeSignature/RemoveAllOriginalDataClassParametersAfter.kt new file mode 100644 index 00000000000..261b3361098 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveAllOriginalDataClassParametersAfter.kt @@ -0,0 +1,11 @@ +data class X(val d: Int, val c: Int, val e: Int) { + +} + +fun test() { + val () = X(4, 3, 5) + val aa = X(4, 3, 5).component1() + val bb = X(4, 3, 5).component2() + val cc = X(4, 3, 5).component2() + for (() in listOf(X(4, 3, 5))) {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveAllOriginalDataClassParametersBefore.kt b/idea/testData/refactoring/changeSignature/RemoveAllOriginalDataClassParametersBefore.kt new file mode 100644 index 00000000000..b0a1e718bd5 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveAllOriginalDataClassParametersBefore.kt @@ -0,0 +1,11 @@ +data class X(val a: Int, val b: Int, val c: Int) { + +} + +fun test() { + val (a, b) = X(1, 2, 3) + val aa = X(1, 2, 3).component1() + val bb = X(1, 2, 3).component2() + val cc = X(1, 2, 3).component3() + for ((a, b) in listOf(X(1, 2, 3))) {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveDataClassParameterAfter.kt b/idea/testData/refactoring/changeSignature/RemoveDataClassParameterAfter.kt new file mode 100644 index 00000000000..a483ada9f70 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveDataClassParameterAfter.kt @@ -0,0 +1,11 @@ +data class X(val a: Int, val c: Int) { + +} + +fun test() { + val (a, c) = X(1, 3) + val aa = X(1, 3).component1() + val bb = X(1, 3).component2() + val cc = X(1, 3).component2() + for ((a, c) in listOf(X(1, 3))) {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveDataClassParameterBefore.kt b/idea/testData/refactoring/changeSignature/RemoveDataClassParameterBefore.kt new file mode 100644 index 00000000000..c393c05d163 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveDataClassParameterBefore.kt @@ -0,0 +1,11 @@ +data class X(val a: Int, val b: Int, val c: Int) { + +} + +fun test() { + val (a, b, c) = X(1, 2, 3) + val aa = X(1, 2, 3).component1() + val bb = X(1, 2, 3).component2() + val cc = X(1, 2, 3).component3() + for ((a, b, c) in listOf(X(1, 2, 3))) {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/SwapDataClassParametersAfter.kt b/idea/testData/refactoring/changeSignature/SwapDataClassParametersAfter.kt new file mode 100644 index 00000000000..b96bd1f7e35 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/SwapDataClassParametersAfter.kt @@ -0,0 +1,11 @@ +data class X(val c: Int, val a: Int, val b: Int) { + +} + +fun test() { + val (c, a, b) = X(3, 1, 2) + val aa = X(3, 1, 2).component2() + val bb = X(3, 1, 2).component3() + val cc = X(3, 1, 2).component1() + for ((c, a, b) in listOf(X(3, 1, 2))) {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/SwapDataClassParametersBefore.kt b/idea/testData/refactoring/changeSignature/SwapDataClassParametersBefore.kt new file mode 100644 index 00000000000..c393c05d163 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/SwapDataClassParametersBefore.kt @@ -0,0 +1,11 @@ +data class X(val a: Int, val b: Int, val c: Int) { + +} + +fun test() { + val (a, b, c) = X(1, 2, 3) + val aa = X(1, 2, 3).component1() + val bb = X(1, 2, 3).component2() + val cc = X(1, 2, 3).component3() + for ((a, b, c) in listOf(X(1, 2, 3))) {} +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureTest.kt index 6a9ab788f94..d9a19e2c25f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureTest.kt @@ -903,4 +903,45 @@ class KotlinChangeSignatureTest : KotlinCodeInsightTestCase() { fun testSetErrorReceiverType() = doTest { receiverParameterInfo!!.currentTypeInfo = KotlinTypeInfo(true, null, "XYZ") } fun testSetErrorParameterType() = doTest { newParameters[1].currentTypeInfo = KotlinTypeInfo(true, null, "XYZ") } + + fun testSwapDataClassParameters() = doTest { + swapParameters(0, 2) + swapParameters(1, 2) + } + + fun testAddDataClassParameter() = doTest { + addParameter(KotlinParameterInfo(originalBaseFunctionDescriptor, + -1, + "c", + KotlinTypeInfo(false, BUILT_INS.intType), + null, + KtPsiFactory(project).createExpression("3"), + KotlinValVar.Val), + 1) + } + + fun testRemoveDataClassParameter() = doTest { removeParameter(1) } + + fun testRemoveAllOriginalDataClassParameters() = doTest { + val psiFactory = KtPsiFactory(project) + + swapParameters(1, 2) + setNewParameter(0, + KotlinParameterInfo(originalBaseFunctionDescriptor, + -1, + "d", + KotlinTypeInfo(false, BUILT_INS.intType), + null, + psiFactory.createExpression("4"), + KotlinValVar.Val)) + + setNewParameter(2, + KotlinParameterInfo(originalBaseFunctionDescriptor, + -1, + "e", + KotlinTypeInfo(false, BUILT_INS.intType), + null, + psiFactory.createExpression("5"), + KotlinValVar.Val)) + } } \ No newline at end of file