diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeMemberFunctionSignatureFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeMemberFunctionSignatureFix.kt index 081d3ca1fcb..2f86cfc2750 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeMemberFunctionSignatureFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeMemberFunctionSignatureFix.kt @@ -27,7 +27,6 @@ import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.PopupStep import com.intellij.openapi.ui.popup.util.BaseListPopupStep import com.intellij.psi.PsiDocumentManager -import com.intellij.psi.PsiFile import com.intellij.util.PlatformIcons import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.Diagnostic @@ -41,7 +40,6 @@ import org.jetbrains.kotlin.psi.JetNamedFunction import org.jetbrains.kotlin.psi.JetParameterList import org.jetbrains.kotlin.psi.JetPsiFactory import org.jetbrains.kotlin.renderer.DescriptorRenderer -import org.jetbrains.kotlin.renderer.DescriptorRendererModifier import org.jetbrains.kotlin.renderer.NameShortness import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil import org.jetbrains.kotlin.resolve.VisibilityUtil @@ -53,31 +51,47 @@ import java.util.* /** * Fix that changes member function's signature to match one of super functions' signatures. */ -class ChangeMemberFunctionSignatureFix(element: JetNamedFunction) : JetHintAction(element) { +class ChangeMemberFunctionSignatureFix private constructor( + element: JetNamedFunction, + private val signatures: List +) : JetHintAction(element) { + + init { + assert(signatures.isNotEmpty()) + } + + private class Signature(function: FunctionDescriptor) { + val sourceCode = SIGNATURE_SOURCE_RENDERER.render(function) + val preview = SIGNATURE_PREVIEW_RENDERER.render(function) + + companion object { + private val SIGNATURE_SOURCE_RENDERER = IdeDescriptorRenderers.SOURCE_CODE.withOptions { + renderDefaultValues = false + } + + private val SIGNATURE_PREVIEW_RENDERER = DescriptorRenderer.withOptions { + typeNormalizer = IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES + withDefinedIn = false + modifiers = emptySet() + nameShortness = NameShortness.SHORT + unitReturnType = false + renderDefaultValues = false + } + } + } companion object : JetSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val function = diagnostic.psiElement as? JetNamedFunction ?: return null - return ChangeMemberFunctionSignatureFix(function) - } - - private val SIGNATURE_SOURCE_RENDERER = IdeDescriptorRenderers.SOURCE_CODE.withOptions { - renderDefaultValues = false - } - - private val SIGNATURE_PREVIEW_RENDERER = DescriptorRenderer.withOptions { - typeNormalizer = IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES - withDefinedIn = false - modifiers = emptySet() - nameShortness = NameShortness.SHORT - unitReturnType = false - renderDefaultValues = false + val signatures = computePossibleSignatures(function) + if (signatures.isEmpty()) return null + return ChangeMemberFunctionSignatureFix(function, signatures) } /** * Computes all the signatures a 'functionElement' could be changed to in order to remove NOTHING_TO_OVERRIDE error. */ - private fun computePossibleSignatures(functionElement: JetNamedFunction): List { + private fun computePossibleSignatures(functionElement: JetNamedFunction): List { if (functionElement.valueParameterList == null) { // we won't be able to modify its signature return emptyList() @@ -86,20 +100,17 @@ class ChangeMemberFunctionSignatureFix(element: JetNamedFunction) : JetHintActio val functionDescriptor = functionElement.resolveToDescriptor() as FunctionDescriptor val superFunctions = getPossibleSuperFunctionsDescriptors(functionDescriptor) - val possibleSignatures: Map = superFunctions + return superFunctions .filter { it.kind.isReal } - .map { changeSignatureToMatch(functionDescriptor, it) } - .toMapBy { SIGNATURE_PREVIEW_RENDERER.render(it) } - - return possibleSignatures.keys - .sorted() - .map { possibleSignatures[it]!! } + .map { signatureToMatch(functionDescriptor, it) } + .distinctBy { it.sourceCode } + .sortedBy { it.preview } } /** * Changes function's signature to match superFunction's signature. Returns new descriptor. */ - private fun changeSignatureToMatch(function: FunctionDescriptor, superFunction: FunctionDescriptor): FunctionDescriptor { + private fun signatureToMatch(function: FunctionDescriptor, superFunction: FunctionDescriptor): Signature { val superParameters = superFunction.valueParameters val parameters = function.valueParameters val newParameters = Lists.newArrayList(superParameters) @@ -121,7 +132,8 @@ class ChangeMemberFunctionSignatureFix(element: JetNamedFunction) : JetHintActio /* copyOverrides = */ true), newParameters) newFunction.addOverriddenDescriptor(superFunction) - return newFunction + + return Signature(newFunction) } /** @@ -182,27 +194,19 @@ class ChangeMemberFunctionSignatureFix(element: JetNamedFunction) : JetHintActio } } - private val possibleSignatures = computePossibleSignatures(element) - - override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean { - return super.isAvailable(project, editor, file) && !possibleSignatures.isEmpty() - } - override fun getText(): String { - val single = possibleSignatures.singleOrNull() - if (single != null) { - return "Change function signature to '${SIGNATURE_PREVIEW_RENDERER.render(single)}'" - } - else { - return "Change function signature..." - } + val single = signatures.singleOrNull() + return if (single != null) + "Change function signature to '${single.preview}'" + else + "Change function signature..." } override fun getFamilyName() = "Change function signature" override fun invoke(project: Project, editor: Editor?, file: JetFile) { CommandProcessor.getInstance().runUndoTransparentAction { - MyAction(project, editor, element, possibleSignatures).execute() + MyAction(project, editor, element, signatures).execute() } } @@ -235,15 +239,13 @@ class ChangeMemberFunctionSignatureFix(element: JetNamedFunction) : JetHintActio } - override fun showHint(editor: Editor): Boolean { - return possibleSignatures.isNotEmpty() && !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true) - } + override fun showHint(editor: Editor) = !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true) private class MyAction( private val project: Project, private val editor: Editor?, private val function: JetNamedFunction, - private val signatures: List + private val signatures: List ) { fun execute() { PsiDocumentManager.getInstance(project).commitAllDocuments() @@ -258,31 +260,29 @@ class ChangeMemberFunctionSignatureFix(element: JetNamedFunction) : JetHintActio } } - private val signaturePopup: BaseListPopupStep + private val signaturePopup: BaseListPopupStep get() { - return object : BaseListPopupStep("Choose Signature", signatures) { + return object : BaseListPopupStep("Choose Signature", signatures) { override fun isAutoSelectionEnabled() = false - override fun onChosen(selectedValue: FunctionDescriptor, finalChoice: Boolean): PopupStep? { + override fun onChosen(selectedValue: Signature, finalChoice: Boolean): PopupStep? { if (finalChoice) { changeSignature(selectedValue) } return PopupStep.FINAL_CHOICE } - override fun getIconFor(aValue: FunctionDescriptor) = PlatformIcons.FUNCTION_ICON + override fun getIconFor(aValue: Signature) = PlatformIcons.FUNCTION_ICON - override fun getTextFor(aValue: FunctionDescriptor) = SIGNATURE_PREVIEW_RENDERER.render(aValue) + override fun getTextFor(aValue: Signature) = aValue.preview } } - private fun changeSignature(patternDescriptor: FunctionDescriptor) { - val signatureString = SIGNATURE_SOURCE_RENDERER.render(patternDescriptor) - + private fun changeSignature(signature: Signature) { PsiDocumentManager.getInstance(project).commitAllDocuments() project.executeWriteCommand("Change Function Signature") { - val patternFunction = JetPsiFactory(project).createFunction(signatureString) + val patternFunction = JetPsiFactory(project).createFunction(signature.sourceCode) val newTypeRef = function.setTypeReference(patternFunction.typeReference) if (newTypeRef != null) {