Create from Usage: Create data class property from destructuring entry

#KT-18540 Fixed
This commit is contained in:
Alexey Sedunov
2017-07-04 19:33:50 +03:00
parent a212a1bf72
commit b48feb257c
15 changed files with 182 additions and 31 deletions
@@ -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)
@@ -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)
}
}
@@ -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<KtDestructuringDeclaration>() {
override fun getElementOfInterest(diagnostic: Diagnostic) = CreateComponentFunctionActionFactory.getElementOfInterest(diagnostic)
override fun extractFixData(element: KtDestructuringDeclaration, diagnostic: Diagnostic): CreateParameterData<KtDestructuringDeclaration>? {
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)
}
}
}
}
@@ -65,6 +65,6 @@ object CreateParameterByNamedArgumentActionFactory: CreateParameterFromUsageFact
defaultValueForCall = argumentExpression
)
return CreateParameterData(context, parameterInfo, element)
return CreateParameterData(parameterInfo, element)
}
}
@@ -114,7 +114,6 @@ object CreateParameterByRefActionFactory : CreateParameterFromUsageFactory<KtSim
if (paramType.hasTypeParametersToAdd(functionDescriptor, context)) return null
return CreateParameterData(
context,
KotlinParameterInfo(callableDescriptor = functionDescriptor,
name = element.getReferencedName(),
originalTypeInfo = KotlinTypeInfo(false, paramType),
@@ -16,24 +16,18 @@
package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable
import com.intellij.codeInsight.intention.IntentionAction
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactoryWithDelegate
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinParameterInfo
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.BindingContext
data class CreateParameterData<out E : KtElement>(
val context: BindingContext,
val parameterInfo: KotlinParameterInfo,
val originalExpression: E
val originalExpression: E,
val createSilently: Boolean = false,
val onComplete: ((Editor?) -> Unit)? = null
)
abstract class CreateParameterFromUsageFactory<E : KtElement>: KotlinSingleIntentionActionFactoryWithDelegate<E, CreateParameterData<E>>() {
override fun createFix(originalElement: E, data: CreateParameterData<E>): IntentionAction? {
return CreateParameterFromUsageFix(
data.parameterInfo.callableDescriptor as FunctionDescriptor,
data.parameterInfo,
data.originalExpression)
}
override fun createFix(originalElement: E, data: CreateParameterData<E>) = CreateParameterFromUsageFix(data)
}
@@ -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<E : KtElement>(
val functionDescriptor: FunctionDescriptor,
val parameterInfo: KotlinParameterInfo,
val defaultValueContext: E
) : CreateFromUsageFixBase<E>(defaultValueContext) {
val data: CreateParameterData<E>
) : CreateFromUsageFixBase<E>(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<PsiElement>): Boolean = false
override fun performSilently(affectedFunctions: Collection<PsiElement>): 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<E : KtElement>(
valOrVar = if (info.writable) KotlinValVar.Var else KotlinValVar.Val
)
return CreateParameterFromUsageFix(constructorDescriptor, paramInfo, element)
return CreateParameterFromUsageFix(CreateParameterData(paramInfo, element))
}
}
}
@@ -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<KotlinParameterInfo> = newParameters.toTypedArray()
fun getNonReceiverParametersCount(): Int = newParameters.size - (if (receiverParameterInfo != null) 1 else 0)
@@ -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
@@ -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<String>) {
val (name, age, address) = <caret>person()
}
@@ -0,0 +1,8 @@
// "Create property 'address' as constructor parameter" "true"
data class Person(val name: String, val age: Int, val address: Any<caret>)
fun person(): Person = TODO()
fun main(args: Array<String>) {
val (name, age, address) = person()
}
@@ -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<String>) {
val (name, age, address, address2) = <caret>person()
}
@@ -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<String>) {
val (name, age, address: String) = <caret>person()
}
@@ -0,0 +1,8 @@
// "Create property 'address' as constructor parameter" "true"
data class Person(val name: String, val age: Int, val address: String<caret>)
fun person(): Person = TODO()
fun main(args: Array<String>) {
val (name, age, address: String) = person()
}
@@ -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");