diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 2793ee9f6b7..dfb5543977c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -366,7 +366,7 @@ class QuickFixRegistrar : QuickFixContributor { NEXT_NONE_APPLICABLE.registerFactory(CreateNextFunctionActionFactory) ITERATOR_MISSING.registerFactory(CreateIteratorFunctionActionFactory) ITERATOR_ON_NULLABLE.registerFactory(MissingIteratorExclExclFixFactory) - COMPONENT_FUNCTION_MISSING.registerFactory(CreateComponentFunctionActionFactory) + COMPONENT_FUNCTION_MISSING.registerFactory(CreateComponentFunctionActionFactory, CreateDataClassPropertyFromDestructuringActionFactory) DELEGATE_SPECIAL_FUNCTION_MISSING.registerFactory(CreatePropertyDelegateAccessorsActionFactory) DELEGATE_SPECIAL_FUNCTION_NONE_APPLICABLE.registerFactory(CreatePropertyDelegateAccessorsActionFactory) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt index cf52f91766f..a70222f7991 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateComponentFunctionActionFactory.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.core.quickfix.QuickFixUtil @@ -41,13 +42,16 @@ object CreateComponentFunctionActionFactory : CreateCallableMemberFromUsageFacto val componentNumber = DataClassDescriptorResolver.getComponentIndex(name.asString()) - 1 - val ownerType = element.initializer?.let { TypeInfo(it, Variance.IN_VARIANCE) } - ?: TypeInfo(diagnosticWithParameters.b, Variance.IN_VARIANCE) + val targetType = diagnosticWithParameters.b + val targetClassDescriptor = targetType.constructor.declarationDescriptor as? ClassDescriptor + if (targetClassDescriptor != null && targetClassDescriptor.isData) return null + + val ownerTypeInfo = TypeInfo(targetType, Variance.IN_VARIANCE) val entries = element.entries val entry = entries[componentNumber] - val returnType = TypeInfo(entry, Variance.OUT_VARIANCE) + val returnTypeInfo = TypeInfo(entry, Variance.OUT_VARIANCE) - return FunctionInfo(name.identifier, ownerType, returnType, isOperator = true) + return FunctionInfo(name.identifier, ownerTypeInfo, returnTypeInfo, isOperator = true) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateDataClassPropertyFromDestructuringActionFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateDataClassPropertyFromDestructuringActionFactory.kt new file mode 100644 index 00000000000..0ed3466f2c4 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateDataClassPropertyFromDestructuringActionFactory.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2017 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.quickfix.createFromUsage.createCallable + +import com.intellij.codeInsight.CodeInsightUtilCore +import com.intellij.codeInsight.template.TemplateBuilderImpl +import com.intellij.codeInsight.template.impl.ConstantNode +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterData +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterFromUsageFactory +import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinParameterInfo +import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinTypeInfo +import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtDestructuringDeclaration +import org.jetbrains.kotlin.resolve.DataClassDescriptorResolver +import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns +import org.jetbrains.kotlin.resolve.source.getPsi + +object CreateDataClassPropertyFromDestructuringActionFactory : CreateParameterFromUsageFactory() { + override fun getElementOfInterest(diagnostic: Diagnostic) = CreateComponentFunctionActionFactory.getElementOfInterest(diagnostic) + + override fun extractFixData(element: KtDestructuringDeclaration, diagnostic: Diagnostic): CreateParameterData? { + val diagnosticWithParameters = Errors.COMPONENT_FUNCTION_MISSING.cast(diagnostic) + + val functionName = diagnosticWithParameters.a + if (!DataClassDescriptorResolver.isComponentLike(functionName)) return null + val componentNumber = DataClassDescriptorResolver.getComponentIndex(functionName.asString()) - 1 + + val targetClassDescriptor = diagnosticWithParameters.b.constructor.declarationDescriptor as? ClassDescriptor ?: return null + if (!targetClassDescriptor.isData) return null + val targetClass = targetClassDescriptor.source.getPsi() as? KtClass ?: return null + val valueParameterCount = targetClass.primaryConstructor?.valueParameters?.size ?: 0 + if (valueParameterCount != componentNumber) return null // TODO: Support addition of multiple parameters + val constructorDescriptor = targetClassDescriptor.unsubstitutedPrimaryConstructor ?: return null + + val entry = element.entries[componentNumber] + val paramName = entry.name ?: functionName.asString() + val paramType = entry.typeReference?.getAbbreviatedTypeOrType(entry.analyze()) ?: targetClassDescriptor.builtIns.anyType + + val parameterInfo = KotlinParameterInfo( + callableDescriptor = constructorDescriptor, + name = paramName, + originalTypeInfo = KotlinTypeInfo(false, paramType), + valOrVar = KotlinValVar.Val + ) + + return CreateParameterData(parameterInfo, element, createSilently = true) { editor -> + if (editor == null) return@CreateParameterData + CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(targetClass)?.let { + val constructor = it.primaryConstructor ?: return@let + val newParameter = constructor.valueParameters.lastOrNull() ?: return@let + val typeReference = newParameter.typeReference ?: return@let + val templateBuilder = TemplateBuilderImpl(typeReference) + templateBuilder.replaceElement(typeReference, ConstantNode(typeReference.text)) + templateBuilder.run(editor, true) + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterByNamedArgumentActionFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterByNamedArgumentActionFactory.kt index 8ad23f99d4f..613e3a4d243 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterByNamedArgumentActionFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterByNamedArgumentActionFactory.kt @@ -65,6 +65,6 @@ object CreateParameterByNamedArgumentActionFactory: CreateParameterFromUsageFact defaultValueForCall = argumentExpression ) - return CreateParameterData(context, parameterInfo, element) + return CreateParameterData(parameterInfo, element) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterByRefActionFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterByRefActionFactory.kt index 24e36cd951b..59035e9047c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterByRefActionFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterByRefActionFactory.kt @@ -114,7 +114,6 @@ object CreateParameterByRefActionFactory : CreateParameterFromUsageFactory( - val context: BindingContext, val parameterInfo: KotlinParameterInfo, - val originalExpression: E + val originalExpression: E, + val createSilently: Boolean = false, + val onComplete: ((Editor?) -> Unit)? = null ) abstract class CreateParameterFromUsageFactory: KotlinSingleIntentionActionFactoryWithDelegate>() { - override fun createFix(originalElement: E, data: CreateParameterData): IntentionAction? { - return CreateParameterFromUsageFix( - data.parameterInfo.callableDescriptor as FunctionDescriptor, - data.parameterInfo, - data.originalExpression) - } + override fun createFix(originalElement: E, data: CreateParameterData) = CreateParameterFromUsageFix(data) } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterFromUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterFromUsageFix.kt index 5405d88f7c0..f8e722fcd09 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterFromUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createVariable/CreateParameterFromUsageFix.kt @@ -21,42 +21,52 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny -import org.jetbrains.kotlin.idea.refactoring.canRefactor import org.jetbrains.kotlin.idea.quickfix.createFromUsage.CreateFromUsageFixBase import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallableBuilderConfiguration import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.CallablePlacement import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.PropertyInfo import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.createBuilder +import org.jetbrains.kotlin.idea.refactoring.canRefactor import org.jetbrains.kotlin.idea.refactoring.changeSignature.* +import org.jetbrains.kotlin.idea.refactoring.runRefactoringWithPostprocessing import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.source.getPsi open class CreateParameterFromUsageFix( - val functionDescriptor: FunctionDescriptor, - val parameterInfo: KotlinParameterInfo, - val defaultValueContext: E -) : CreateFromUsageFixBase(defaultValueContext) { + val data: CreateParameterData +) : CreateFromUsageFixBase(data.originalExpression) { override fun getText(): String { - return with(parameterInfo) { + return with(data.parameterInfo) { if (valOrVar != KotlinValVar.None) "Create property '$name' as constructor parameter" else "Create parameter '$name'" } } override fun startInWriteAction() = false - override fun invoke(project: Project, editor: Editor?, file: KtFile) { + private fun runChangeSignature(project: Project) { val config = object : KotlinChangeSignatureConfiguration { override fun configure(originalDescriptor: KotlinMethodDescriptor): KotlinMethodDescriptor { - return originalDescriptor.modify { it.addParameter(parameterInfo) } + return originalDescriptor.modify { it.addParameter(data.parameterInfo) } } - override fun performSilently(affectedFunctions: Collection): Boolean = false + override fun performSilently(affectedFunctions: Collection): Boolean = data.createSilently } - runChangeSignature(project, functionDescriptor, config, defaultValueContext, text) + runChangeSignature(project, data.parameterInfo.callableDescriptor, config, data.originalExpression, text) + } + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val onComplete = data.onComplete + if (onComplete == null) { + runChangeSignature(project) + } + else { + { runChangeSignature(project) }.runRefactoringWithPostprocessing(project, "refactoring.changeSignature") { + onComplete(editor) + } + } } companion object { @@ -97,7 +107,7 @@ open class CreateParameterFromUsageFix( valOrVar = if (info.writable) KotlinValVar.Var else KotlinValVar.Val ) - return CreateParameterFromUsageFix(constructorDescriptor, paramInfo, element) + return CreateParameterFromUsageFix(CreateParameterData(paramInfo, element)) } } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeInfo.kt index a3a7a7d8080..35379711b90 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeInfo.kt @@ -122,6 +122,11 @@ open class KotlinChangeInfo( fun getNewParametersCount(): Int = newParameters.size + fun hasAppendedParametersOnly(): Boolean { + val oldParamCount = originalBaseFunctionDescriptor.valueParameters.size + return newParameters.withIndex().all { (i, p) -> if (i < oldParamCount) p.oldIndex == i else p.isNewParameter } + } + override fun getNewParameters(): Array = newParameters.toTypedArray() fun getNonReceiverParametersCount(): Int = newParameters.size - (if (receiverParameterInfo != null) 1 else 0) 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 373c5a8db32..4e8b5486c12 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/KotlinChangeSignatureUsageProcessor.kt @@ -290,7 +290,7 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor { } } - if (isDataClass) { + if (isDataClass && !changeInfo.hasAppendedParametersOnly()) { (functionPsi as KtPrimaryConstructor).valueParameters.firstOrNull()?.let { ReferencesSearch.search(it).mapNotNullTo(result) { val destructuringEntry = it.element as? KtDestructuringDeclarationEntry ?: return@mapNotNullTo null diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntry.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntry.kt new file mode 100644 index 00000000000..25caaff2bcf --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntry.kt @@ -0,0 +1,8 @@ +// "Create property 'address' as constructor parameter" "true" +data class Person(val name: String, val age: Int) + +fun person(): Person = TODO() + +fun main(args: Array) { + val (name, age, address) = person() +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntry.kt.after b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntry.kt.after new file mode 100644 index 00000000000..54022194d6e --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntry.kt.after @@ -0,0 +1,8 @@ +// "Create property 'address' as constructor parameter" "true" +data class Person(val name: String, val age: Int, val address: Any) + +fun person(): Person = TODO() + +fun main(args: Array) { + val (name, age, address) = person() +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithSkippedIndex.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithSkippedIndex.kt new file mode 100644 index 00000000000..b4af5a79a02 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithSkippedIndex.kt @@ -0,0 +1,11 @@ +// "Create property 'address2' as constructor parameter" "false" +// ACTION: Create property 'address' as constructor parameter +// ERROR: Destructuring declaration initializer of type Person must have a 'component3()' function +// ERROR: Destructuring declaration initializer of type Person must have a 'component4()' function +data class Person(val name: String, val age: Int) + +fun person(): Person = TODO() + +fun main(args: Array) { + val (name, age, address, address2) = person() +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithType.kt b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithType.kt new file mode 100644 index 00000000000..2dce16b2c9d --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithType.kt @@ -0,0 +1,8 @@ +// "Create property 'address' as constructor parameter" "true" +data class Person(val name: String, val age: Int) + +fun person(): Person = TODO() + +fun main(args: Array) { + val (name, age, address: String) = person() +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithType.kt.after b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithType.kt.after new file mode 100644 index 00000000000..0919bf57579 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithType.kt.after @@ -0,0 +1,8 @@ +// "Create property 'address' as constructor parameter" "true" +data class Person(val name: String, val age: Int, val address: String) + +fun person(): Person = TODO() + +fun main(args: Array) { + val (name, age, address: String) = person() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 127b02365bf..ab41bf7eb3f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -3741,6 +3741,24 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("dataClassPropertyByDestructuringEntry.kt") + public void testDataClassPropertyByDestructuringEntry() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntry.kt"); + doTest(fileName); + } + + @TestMetadata("dataClassPropertyByDestructuringEntryWithSkippedIndex.kt") + public void testDataClassPropertyByDestructuringEntryWithSkippedIndex() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithSkippedIndex.kt"); + doTest(fileName); + } + + @TestMetadata("dataClassPropertyByDestructuringEntryWithType.kt") + public void testDataClassPropertyByDestructuringEntryWithType() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/dataClassPropertyByDestructuringEntryWithType.kt"); + doTest(fileName); + } + @TestMetadata("inAccessorInClass.kt") public void testInAccessorInClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createVariable/parameter/inAccessorInClass.kt");