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
This commit is contained in:
Toshiaki Kameyama
2018-02-17 20:09:44 +09:00
committed by Dmitry Jemerov
parent ea6582dd7d
commit a36e8c86f1
4 changed files with 58 additions and 32 deletions
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable package org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable
import com.intellij.codeInsight.intention.LowPriorityAction import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.codeInsight.navigation.NavigationUtil
import com.intellij.ide.util.EditorHelper import com.intellij.ide.util.EditorHelper
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileEditorManager 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.idea.util.isAbstract
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import java.util.* import java.util.*
open class CreateCallableFromUsageFix<E : KtElement>( open class CreateCallableFromUsageFix<E : KtElement>(
originalExpression: E, originalExpression: E,
callableInfos: List<CallableInfo> callableInfos: List<CallableInfo>
) : CreateCallableFromUsageFixBase<E>(originalExpression, callableInfos, false) ) : CreateCallableFromUsageFixBase<E>(originalExpression, callableInfos, false)
class CreateExtensionCallableFromUsageFix<E : KtElement>( class CreateExtensionCallableFromUsageFix<E : KtElement>(
originalExpression: E, originalExpression: E,
callableInfos: List<CallableInfo> callableInfos: List<CallableInfo>
) : CreateCallableFromUsageFixBase<E>(originalExpression, callableInfos, true), LowPriorityAction ) : CreateCallableFromUsageFixBase<E>(originalExpression, callableInfos, true), LowPriorityAction
abstract class CreateCallableFromUsageFixBase<E : KtElement>( abstract class CreateCallableFromUsageFixBase<E : KtElement>(
originalExpression: E, originalExpression: E,
protected val callableInfos: List<CallableInfo>, protected val callableInfos: List<CallableInfo>,
val isExtension: Boolean val isExtension: Boolean
) : KotlinCrossLanguageQuickFixAction<E>(originalExpression) { ) : KotlinCrossLanguageQuickFixAction<E>(originalExpression) {
init { init {
assert (callableInfos.isNotEmpty()) { "No CallableInfos: ${originalExpression.getElementTextWithContext()}" } assert(callableInfos.isNotEmpty()) { "No CallableInfos: ${originalExpression.getElementTextWithContext()}" }
if (callableInfos.size > 1) { if (callableInfos.size > 1) {
val receiverSet = callableInfos.mapTo(HashSet<TypeInfo>()) { it.receiverTypeInfo } val receiverSet = callableInfos.mapTo(HashSet()) { it.receiverTypeInfo }
if (receiverSet.size > 1) throw AssertionError("All functions must have common receiver: $receiverSet") if (receiverSet.size > 1) throw AssertionError("All functions must have common receiver: $receiverSet")
val possibleContainerSet = callableInfos.mapTo(HashSet<List<KtElement>>()) { it.possibleContainers } val possibleContainerSet = callableInfos.mapTo(HashSet()) { it.possibleContainers }
if (possibleContainerSet.size > 1) throw AssertionError("All functions must have common containers: $possibleContainerSet") if (possibleContainerSet.size > 1) throw AssertionError("All functions must have common containers: $possibleContainerSet")
} }
} }
@@ -106,20 +106,18 @@ abstract class CreateCallableFromUsageFixBase<E : KtElement>(
val receiverType = if (!receiverTypeInfo.isOfThis) { val receiverType = if (!receiverTypeInfo.isOfThis) {
CallableBuilderConfiguration(callableInfos, element, isExtension = isExtension) CallableBuilderConfiguration(callableInfos, element, isExtension = isExtension)
.createBuilder() .createBuilder()
.computeTypeCandidates(receiverTypeInfo) .computeTypeCandidates(receiverTypeInfo)
.firstOrNull() .firstOrNull()
?.theType ?.theType
} } else null
else null
if (receiverType != null) { if (receiverType != null) {
if (isExtension) { if (isExtension) {
val receiverTypeText = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType) val receiverTypeText = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType)
val isFunctionType = receiverType.constructor.declarationDescriptor is FunctionClassDescriptor val isFunctionType = receiverType.constructor.declarationDescriptor is FunctionClassDescriptor
append(if (isFunctionType) "($receiverTypeText)" else receiverTypeText).append('.') append(if (isFunctionType) "($receiverTypeText)" else receiverTypeText).append('.')
} } else {
else {
receiverType.constructor.declarationDescriptor?.let { receiverType.constructor.declarationDescriptor?.let {
append(IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderClassifierName(it)).append('.') append(IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderClassifierName(it)).append('.')
} }
@@ -134,12 +132,10 @@ abstract class CreateCallableFromUsageFixBase<E : KtElement>(
return StringBuilder().apply { return StringBuilder().apply {
append("Create ") append("Create ")
val receiverInfo = receiverTypeInfo
if (!callableInfos.any { it.isAbstract }) { if (!callableInfos.any { it.isAbstract }) {
if (isExtension) { if (isExtension) {
append("extension ") append("extension ")
} } else if (receiverTypeInfo != TypeInfo.Empty) {
else if (receiverInfo !is TypeInfo.Empty) {
append("member ") append("member ")
} }
} }
@@ -153,7 +149,7 @@ abstract class CreateCallableFromUsageFixBase<E : KtElement>(
val receiverInfo = callableInfos.first().receiverTypeInfo 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 if (callableInfos.any { it is PropertyInfo && it.possibleContainers.isEmpty() }) return false
return !isExtension return !isExtension
} }
@@ -191,15 +187,14 @@ abstract class CreateCallableFromUsageFixBase<E : KtElement>(
if (file is KtFile) { if (file is KtFile) {
fileForBuilder = file fileForBuilder = file
editorForBuilder = editor editorForBuilder = editor
} } else {
else {
fileForBuilder = element.containingKtFile fileForBuilder = element.containingKtFile
EditorHelper.openInEditor(element) EditorHelper.openInEditor(element)
editorForBuilder = FileEditorManager.getInstance(project).selectedTextEditor!! editorForBuilder = FileEditorManager.getInstance(project).selectedTextEditor!!
} }
val callableBuilder = val callableBuilder =
CallableBuilderConfiguration(callableInfos, element as KtElement, fileForBuilder, editorForBuilder, isExtension).createBuilder() CallableBuilderConfiguration(callableInfos, element as KtElement, fileForBuilder, editorForBuilder, isExtension).createBuilder()
fun runBuilder(placement: CallablePlacement) { fun runBuilder(placement: CallablePlacement) {
callableBuilder.placement = placement callableBuilder.placement = placement
@@ -212,19 +207,24 @@ abstract class CreateCallableFromUsageFixBase<E : KtElement>(
} }
val popupTitle = "Choose target class or interface" val popupTitle = "Choose target class or interface"
val receiverTypeCandidates = callableBuilder.computeTypeCandidates(callableInfo.receiverTypeInfo).let { val receiverTypeInfo = callableInfo.receiverTypeInfo
if (callableInfo.isAbstract) it.filter { it.theType.isAbstract() } else it 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()) { if (receiverTypeCandidates.isNotEmpty()) {
val containers = receiverTypeCandidates 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 }) { chooseContainerElementIfNecessary(containers, editor, popupTitle, false, { it.second }) {
runBuilder(CallablePlacement.WithReceiver(it.first)) runBuilder(CallablePlacement.WithReceiver(it.first))
} }
} } else {
else { assert(receiverTypeInfo == TypeInfo.Empty) {
assert(callableInfo.receiverTypeInfo is TypeInfo.Empty) {
"No receiver type candidates: ${element.text} in ${file.text}" "No receiver type candidates: ${element.text} in ${file.text}"
} }
@@ -0,0 +1,8 @@
// "Create member function 'T.bar'" "true"
open class X
fun <T : X> foo(t: T, f: T.() -> Unit = {}) {}
class Text<T : X>(private val t: T) {
fun f() = foo(t) { <caret>bar() }
}
@@ -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 <T : X> foo(t: T, f: T.() -> Unit = {}) {}
class Text<T : X>(private val t: T) {
fun f() = foo(t) { bar() }
}
@@ -3345,6 +3345,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName); 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") @TestMetadata("classMemberPartialSubstitution.kt")
public void testClassMemberPartialSubstitution() throws Exception { public void testClassMemberPartialSubstitution() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberPartialSubstitution.kt"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/createFromUsage/createFunction/call/typeArguments/classMemberPartialSubstitution.kt");