From 1db7195e9496b2e77a0af69d0eac78c67427321b Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 17 Aug 2015 15:18:46 +0300 Subject: [PATCH] Extract Function: Fix callee replacement in operation expressions #KT-8294 Fixed --- .../idea/codeInsight/CodeInsightUtils.java | 10 ++++---- .../InfixCallToOrdinaryIntention.kt | 18 +++++++++----- .../extractableAnalysisUtil.kt | 12 +++++++++- .../extractionEngine/extractorUtil.kt | 24 ++++++++++++++----- .../KotlinIntroduceParameterHandler.kt | 7 +++++- .../basic/convertBinaryExpression.kt | 3 +++ .../convertBinaryExpression.kt.conflicts | 1 + .../basic/convertInfixExpression.kt | 6 +++++ .../basic/convertInfixExpression.kt.after | 8 +++++++ .../basic/convertUnaryExpression.kt | 6 +++++ .../basic/convertUnaryExpression.kt.after | 8 +++++++ .../introduce/JetExtractionTestGenerated.java | 18 ++++++++++++++ 12 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 idea/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt create mode 100644 idea/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt.conflicts create mode 100644 idea/testData/refactoring/extractFunction/basic/convertInfixExpression.kt create mode 100644 idea/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after create mode 100644 idea/testData/refactoring/extractFunction/basic/convertUnaryExpression.kt create mode 100644 idea/testData/refactoring/extractFunction/basic/convertUnaryExpression.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java b/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java index 6754ca54599..8e8f0d3ff94 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/CodeInsightUtils.java @@ -42,10 +42,12 @@ public class CodeInsightUtils { @Nullable public static JetExpression findExpression(@NotNull PsiFile file, int startOffset, int endOffset) { PsiElement element = findElementOfClassAtRange(file, startOffset, endOffset, JetExpression.class); - if (element == null) { - return null; - } - else if (!(element instanceof JetExpression)) { + if (element == null) return null; + + // TODO: Support binary operations in "Introduce..." refactorings + if (element instanceof JetOperationReferenceExpression + && ((JetOperationReferenceExpression) element).getReferencedNameElementType() != JetTokens.IDENTIFIER + && element.getParent() instanceof JetBinaryExpression) { return null; } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/InfixCallToOrdinaryIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/InfixCallToOrdinaryIntention.kt index 3351e942871..71246bfd53e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/InfixCallToOrdinaryIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/InfixCallToOrdinaryIntention.kt @@ -27,12 +27,18 @@ public class InfixCallToOrdinaryIntention : JetSelfTargetingIntention " $2:'{}'" - else -> "($2)" + convert(element) + } + + companion object { + public fun convert(element: JetBinaryExpression): JetExpression { + val argument = JetPsiUtil.safeDeparenthesize(element.getRight()!!) + val pattern = "$0.$1" + when (argument) { + is JetFunctionLiteralExpression -> " $2:'{}'" + else -> "($2)" + } + val replacement = JetPsiFactory(element).createExpressionByPattern(pattern, element.getLeft()!!, element.getOperationReference().getText(), argument) + return element.replace(replacement) as JetExpression } - val replacement = JetPsiFactory(element).createExpressionByPattern(pattern, element.getLeft()!!, element.getOperationReference().getText(), argument) - element.replace(replacement) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt index eea4ee265a9..1113226c695 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractableAnalysisUtil.kt @@ -59,6 +59,7 @@ import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.approximateWithResolvableType import org.jetbrains.kotlin.idea.util.isResolvableInScope +import org.jetbrains.kotlin.lexer.JetToken import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* @@ -79,6 +80,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver import org.jetbrains.kotlin.resolve.scopes.utils.asJetScope import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.checker.JetTypeChecker +import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.types.typeUtil.makeNullable import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.DFS.CollectingNodeHandler @@ -703,7 +705,15 @@ private fun ExtractionData.inferParametersInfo( } else { val argumentExpr = (thisExpr ?: ref).getQualifiedExpressionForSelectorOrThis() - argumentExpr.getText() ?: throw AssertionError("'this' reference shouldn't be empty: code fragment = $codeFragmentText") + if (argumentExpr is JetOperationReferenceExpression) { + val nameElement = argumentExpr.getReferencedNameElement() + val nameElementType = nameElement.node.elementType + (nameElementType as? JetToken)?.let { + OperatorConventions.getNameForOperationSymbol(it)?.asString() + } ?: nameElement.getText() + } + else argumentExpr.getText() + ?: throw AssertionError("reference shouldn't be empty: code fragment = $codeFragmentText") } if (extractFunctionRef) { val receiverTypeText = (originalDeclaration as JetCallableDeclaration).getReceiverTypeReference()?.getText() ?: "" diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index 245e8137df1..b5347ea5964 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -19,12 +19,11 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.idea.core.appendElement -import org.jetbrains.kotlin.idea.core.KotlinNameSuggester +import org.jetbrains.kotlin.idea.core.* import org.jetbrains.kotlin.idea.core.refactoring.isMultiLine -import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyIntention -import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator +import org.jetbrains.kotlin.idea.intentions.InfixCallToOrdinaryIntention +import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Initializer import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Jump @@ -38,7 +37,6 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiUnifier import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnificationResult.StronglyMatched import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnificationResult.WeaklyMatched import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnifierParameter -import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.JetPsiFactory.CallableBuilder import org.jetbrains.kotlin.psi.codeFragmentUtil.DEBUG_TYPE_REFERENCE_STRING @@ -46,9 +44,9 @@ import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.isFlexible -import org.jetbrains.kotlin.types.isNullabilityFlexible import java.util.ArrayList import java.util.Collections import java.util.HashMap @@ -226,6 +224,20 @@ private fun makeCall( functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, extractableDescriptor.originalContext) return } + + if (anchor is JetOperationReferenceExpression) { + val operationExpression = anchor.parent as? JetOperationExpression ?: return + val newNameExpression = when (operationExpression) { + is JetUnaryExpression -> OperatorToFunctionIntention.convert(operationExpression).second + is JetBinaryExpression -> { + InfixCallToOrdinaryIntention.convert(operationExpression).getCalleeExpressionIfAny() + } + else -> null + } + newNameExpression?.replace(wrappedCall) + return + } + anchor.replace(wrappedCall) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt index 5440d5f6988..b2e9e34c2bc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt @@ -209,7 +209,12 @@ public open class KotlinIntroduceParameterHandler( open fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) { val context = expression.analyze() - val expressionType = context.getType(expression)!! + val expressionType = context.getType(expression) + if (expressionType == null) { + showErrorHint(project, editor, "Expression has no type", INTRODUCE_PARAMETER) + return + } + if (expressionType.isUnit() || expressionType.isNothing()) { val message = JetRefactoringBundle.message( "cannot.introduce.parameter.of.0.type", diff --git a/idea/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt b/idea/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt new file mode 100644 index 00000000000..c3c3312c5f3 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt @@ -0,0 +1,3 @@ +fun foo(minus : String.(String) -> Unit) { + "A" - "B" +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt.conflicts b/idea/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt.conflicts new file mode 100644 index 00000000000..21617cc617d --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt.conflicts @@ -0,0 +1 @@ +Cannot perform refactoring without an expression \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/basic/convertInfixExpression.kt b/idea/testData/refactoring/extractFunction/basic/convertInfixExpression.kt new file mode 100644 index 00000000000..4a3f2bdfc0d --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/convertInfixExpression.kt @@ -0,0 +1,6 @@ +// PARAM_DESCRIPTOR: value-parameter val x: kotlin.String.(kotlin.String) -> kotlin.Unit defined in foo +// PARAM_TYPES: kotlin.String.(kotlin.String) -> kotlin.Unit + +fun foo(x : String.(String) -> Unit) { + "A" x "B" +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after b/idea/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after new file mode 100644 index 00000000000..9c532e2ee91 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/convertInfixExpression.kt.after @@ -0,0 +1,8 @@ +// PARAM_DESCRIPTOR: value-parameter val x: kotlin.String.(kotlin.String) -> kotlin.Unit defined in foo +// PARAM_TYPES: kotlin.String.(kotlin.String) -> kotlin.Unit + +fun foo(x : String.(String) -> Unit) { + "A".(function(x))("B") +} + +private fun function(x: String.(String) -> Unit) = x \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/basic/convertUnaryExpression.kt b/idea/testData/refactoring/extractFunction/basic/convertUnaryExpression.kt new file mode 100644 index 00000000000..882a1e6ef20 --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/convertUnaryExpression.kt @@ -0,0 +1,6 @@ +// PARAM_DESCRIPTOR: value-parameter val plus: kotlin.String.() -> kotlin.Unit defined in foo +// PARAM_TYPES: kotlin.String.() -> kotlin.Unit + +fun foo(plus: String.() -> Unit) { + + "A" +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractFunction/basic/convertUnaryExpression.kt.after b/idea/testData/refactoring/extractFunction/basic/convertUnaryExpression.kt.after new file mode 100644 index 00000000000..bcea3f951df --- /dev/null +++ b/idea/testData/refactoring/extractFunction/basic/convertUnaryExpression.kt.after @@ -0,0 +1,8 @@ +// PARAM_DESCRIPTOR: value-parameter val plus: kotlin.String.() -> kotlin.Unit defined in foo +// PARAM_TYPES: kotlin.String.() -> kotlin.Unit + +fun foo(plus: String.() -> Unit) { + "A".(function(plus))() +} + +private fun function(plus: String.() -> Unit) = plus \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java index 355adfe345f..f5bc57fc919 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java @@ -456,6 +456,24 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doExtractFunctionTest(fileName); } + @TestMetadata("convertBinaryExpression.kt") + public void testConvertBinaryExpression() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/basic/convertBinaryExpression.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("convertInfixExpression.kt") + public void testConvertInfixExpression() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/basic/convertInfixExpression.kt"); + doExtractFunctionTest(fileName); + } + + @TestMetadata("convertUnaryExpression.kt") + public void testConvertUnaryExpression() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/basic/convertUnaryExpression.kt"); + doExtractFunctionTest(fileName); + } + @TestMetadata("delegatingFunction.kt") public void testDelegatingFunction() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/basic/delegatingFunction.kt");