From a36e8c86f1ccdb3fe03ef0ead16dcd8d7c1e779f Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Sat, 17 Feb 2018 20:09:44 +0900 Subject: [PATCH] KT-22428 Create member function from usage shouldn't present type parameters as options (#1509) * KT-22428 Create member function from usage shouldn't present type parameters as options * Reformat & cleanup --- .../CreateCallableFromUsageFix.kt | 64 +++++++++---------- ...lassMemberInFunctionLiteralWithReceiver.kt | 8 +++ ...mberInFunctionLiteralWithReceiver.kt.after | 12 ++++ .../idea/quickfix/QuickFixTestGenerated.java | 6 ++ 4 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberInFunctionLiteralWithReceiver.kt create mode 100644 idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberInFunctionLiteralWithReceiver.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt index 6f147f7fb53..3922421b7ed 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createFromUsage/createCallable/CreateCallableFromUsageFix.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable import com.intellij.codeInsight.intention.LowPriorityAction -import com.intellij.codeInsight.navigation.NavigationUtil import com.intellij.ide.util.EditorHelper import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManager @@ -38,30 +37,31 @@ import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.isAbstract import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext +import org.jetbrains.kotlin.types.typeUtil.isTypeParameter import java.util.* open class CreateCallableFromUsageFix( - originalExpression: E, - callableInfos: List + originalExpression: E, + callableInfos: List ) : CreateCallableFromUsageFixBase(originalExpression, callableInfos, false) class CreateExtensionCallableFromUsageFix( - originalExpression: E, - callableInfos: List + originalExpression: E, + callableInfos: List ) : CreateCallableFromUsageFixBase(originalExpression, callableInfos, true), LowPriorityAction abstract class CreateCallableFromUsageFixBase( - originalExpression: E, - protected val callableInfos: List, - val isExtension: Boolean + originalExpression: E, + protected val callableInfos: List, + val isExtension: Boolean ) : KotlinCrossLanguageQuickFixAction(originalExpression) { init { - assert (callableInfos.isNotEmpty()) { "No CallableInfos: ${originalExpression.getElementTextWithContext()}" } + assert(callableInfos.isNotEmpty()) { "No CallableInfos: ${originalExpression.getElementTextWithContext()}" } if (callableInfos.size > 1) { - val receiverSet = callableInfos.mapTo(HashSet()) { it.receiverTypeInfo } + val receiverSet = callableInfos.mapTo(HashSet()) { it.receiverTypeInfo } if (receiverSet.size > 1) throw AssertionError("All functions must have common receiver: $receiverSet") - val possibleContainerSet = callableInfos.mapTo(HashSet>()) { it.possibleContainers } + val possibleContainerSet = callableInfos.mapTo(HashSet()) { it.possibleContainers } if (possibleContainerSet.size > 1) throw AssertionError("All functions must have common containers: $possibleContainerSet") } } @@ -106,20 +106,18 @@ abstract class CreateCallableFromUsageFixBase( val receiverType = if (!receiverTypeInfo.isOfThis) { CallableBuilderConfiguration(callableInfos, element, isExtension = isExtension) - .createBuilder() - .computeTypeCandidates(receiverTypeInfo) - .firstOrNull() - ?.theType - } - else null + .createBuilder() + .computeTypeCandidates(receiverTypeInfo) + .firstOrNull() + ?.theType + } else null if (receiverType != null) { if (isExtension) { val receiverTypeText = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType) val isFunctionType = receiverType.constructor.declarationDescriptor is FunctionClassDescriptor append(if (isFunctionType) "($receiverTypeText)" else receiverTypeText).append('.') - } - else { + } else { receiverType.constructor.declarationDescriptor?.let { append(IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderClassifierName(it)).append('.') } @@ -134,12 +132,10 @@ abstract class CreateCallableFromUsageFixBase( return StringBuilder().apply { append("Create ") - val receiverInfo = receiverTypeInfo if (!callableInfos.any { it.isAbstract }) { if (isExtension) { append("extension ") - } - else if (receiverInfo !is TypeInfo.Empty) { + } else if (receiverTypeInfo != TypeInfo.Empty) { append("member ") } } @@ -153,7 +149,7 @@ abstract class CreateCallableFromUsageFixBase( val receiverInfo = callableInfos.first().receiverTypeInfo - if (receiverInfo is TypeInfo.Empty) { + if (receiverInfo == TypeInfo.Empty) { if (callableInfos.any { it is PropertyInfo && it.possibleContainers.isEmpty() }) return false return !isExtension } @@ -191,15 +187,14 @@ abstract class CreateCallableFromUsageFixBase( if (file is KtFile) { fileForBuilder = file editorForBuilder = editor - } - else { + } else { fileForBuilder = element.containingKtFile EditorHelper.openInEditor(element) editorForBuilder = FileEditorManager.getInstance(project).selectedTextEditor!! } val callableBuilder = - CallableBuilderConfiguration(callableInfos, element as KtElement, fileForBuilder, editorForBuilder, isExtension).createBuilder() + CallableBuilderConfiguration(callableInfos, element as KtElement, fileForBuilder, editorForBuilder, isExtension).createBuilder() fun runBuilder(placement: CallablePlacement) { callableBuilder.placement = placement @@ -212,19 +207,24 @@ abstract class CreateCallableFromUsageFixBase( } val popupTitle = "Choose target class or interface" - val receiverTypeCandidates = callableBuilder.computeTypeCandidates(callableInfo.receiverTypeInfo).let { - if (callableInfo.isAbstract) it.filter { it.theType.isAbstract() } else it + val receiverTypeInfo = callableInfo.receiverTypeInfo + val receiverTypeCandidates = callableBuilder.computeTypeCandidates(receiverTypeInfo).let { + if (callableInfo.isAbstract) + it.filter { it.theType.isAbstract() } + else if (!isExtension && receiverTypeInfo != TypeInfo.Empty) + it.filter { !it.theType.isTypeParameter() } + else + it } if (receiverTypeCandidates.isNotEmpty()) { val containers = receiverTypeCandidates - .mapNotNull { candidate -> getDeclarationIfApplicable(project, candidate)?.let { candidate to it } } + .mapNotNull { candidate -> getDeclarationIfApplicable(project, candidate)?.let { candidate to it } } chooseContainerElementIfNecessary(containers, editor, popupTitle, false, { it.second }) { runBuilder(CallablePlacement.WithReceiver(it.first)) } - } - else { - assert(callableInfo.receiverTypeInfo is TypeInfo.Empty) { + } else { + assert(receiverTypeInfo == TypeInfo.Empty) { "No receiver type candidates: ${element.text} in ${file.text}" } diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberInFunctionLiteralWithReceiver.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberInFunctionLiteralWithReceiver.kt new file mode 100644 index 00000000000..f85568eb892 --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberInFunctionLiteralWithReceiver.kt @@ -0,0 +1,8 @@ +// "Create member function 'T.bar'" "true" +open class X + +fun foo(t: T, f: T.() -> Unit = {}) {} + +class Text(private val t: T) { + fun f() = foo(t) { bar() } +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberInFunctionLiteralWithReceiver.kt.after b/idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberInFunctionLiteralWithReceiver.kt.after new file mode 100644 index 00000000000..3b9b10c2ffd --- /dev/null +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberInFunctionLiteralWithReceiver.kt.after @@ -0,0 +1,12 @@ +// "Create member function 'T.bar'" "true" +open class X { + fun bar() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} + +fun foo(t: T, f: T.() -> Unit = {}) {} + +class Text(private val t: T) { + fun f() = foo(t) { bar() } +} \ 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 95eecd98fcb..1526923f013 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -3345,6 +3345,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } + @TestMetadata("classMemberInFunctionLiteralWithReceiver.kt") + public void testClassMemberInFunctionLiteralWithReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberInFunctionLiteralWithReceiver.kt"); + doTest(fileName); + } + @TestMetadata("classMemberPartialSubstitution.kt") public void testClassMemberPartialSubstitution() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberPartialSubstitution.kt");