Create from Usage: Create data class property from destructuring entry
#KT-18540 Fixed
This commit is contained in:
@@ -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)
|
||||
|
||||
+8
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+78
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -65,6 +65,6 @@ object CreateParameterByNamedArgumentActionFactory: CreateParameterFromUsageFact
|
||||
defaultValueForCall = argumentExpression
|
||||
)
|
||||
|
||||
return CreateParameterData(context, parameterInfo, element)
|
||||
return CreateParameterData(parameterInfo, element)
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -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),
|
||||
|
||||
+5
-11
@@ -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)
|
||||
}
|
||||
+22
-12
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+8
@@ -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()
|
||||
}
|
||||
+8
@@ -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()
|
||||
}
|
||||
+11
@@ -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()
|
||||
}
|
||||
+8
@@ -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()
|
||||
}
|
||||
+8
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user