Extract Function: Fix callee replacement in operation expressions
#KT-8294 Fixed
This commit is contained in:
@@ -42,10 +42,12 @@ public class CodeInsightUtils {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public static JetExpression findExpression(@NotNull PsiFile file, int startOffset, int endOffset) {
|
public static JetExpression findExpression(@NotNull PsiFile file, int startOffset, int endOffset) {
|
||||||
PsiElement element = findElementOfClassAtRange(file, startOffset, endOffset, JetExpression.class);
|
PsiElement element = findElementOfClassAtRange(file, startOffset, endOffset, JetExpression.class);
|
||||||
if (element == null) {
|
if (element == null) return null;
|
||||||
return null;
|
|
||||||
}
|
// TODO: Support binary operations in "Introduce..." refactorings
|
||||||
else if (!(element instanceof JetExpression)) {
|
if (element instanceof JetOperationReferenceExpression
|
||||||
|
&& ((JetOperationReferenceExpression) element).getReferencedNameElementType() != JetTokens.IDENTIFIER
|
||||||
|
&& element.getParent() instanceof JetBinaryExpression) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,18 @@ public class InfixCallToOrdinaryIntention : JetSelfTargetingIntention<JetBinaryE
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun applyTo(element: JetBinaryExpression, editor: Editor) {
|
override fun applyTo(element: JetBinaryExpression, editor: Editor) {
|
||||||
val argument = JetPsiUtil.safeDeparenthesize(element.getRight()!!)
|
convert(element)
|
||||||
val pattern = "$0.$1" + when (argument) {
|
}
|
||||||
is JetFunctionLiteralExpression -> " $2:'{}'"
|
|
||||||
else -> "($2)"
|
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-1
@@ -59,6 +59,7 @@ import org.jetbrains.kotlin.idea.references.mainReference
|
|||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
|
import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
|
||||||
import org.jetbrains.kotlin.idea.util.isResolvableInScope
|
import org.jetbrains.kotlin.idea.util.isResolvableInScope
|
||||||
|
import org.jetbrains.kotlin.lexer.JetToken
|
||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
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.resolve.scopes.utils.asJetScope
|
||||||
import org.jetbrains.kotlin.types.*
|
import org.jetbrains.kotlin.types.*
|
||||||
import org.jetbrains.kotlin.types.checker.JetTypeChecker
|
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.types.typeUtil.makeNullable
|
||||||
import org.jetbrains.kotlin.utils.DFS
|
import org.jetbrains.kotlin.utils.DFS
|
||||||
import org.jetbrains.kotlin.utils.DFS.CollectingNodeHandler
|
import org.jetbrains.kotlin.utils.DFS.CollectingNodeHandler
|
||||||
@@ -703,7 +705,15 @@ private fun ExtractionData.inferParametersInfo(
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val argumentExpr = (thisExpr ?: ref).getQualifiedExpressionForSelectorOrThis()
|
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) {
|
if (extractFunctionRef) {
|
||||||
val receiverTypeText = (originalDeclaration as JetCallableDeclaration).getReceiverTypeReference()?.getText() ?: ""
|
val receiverTypeText = (originalDeclaration as JetCallableDeclaration).getReceiverTypeReference()?.getText() ?: ""
|
||||||
|
|||||||
+18
-6
@@ -19,12 +19,11 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
|||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.idea.core.appendElement
|
import org.jetbrains.kotlin.idea.core.*
|
||||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
|
||||||
import org.jetbrains.kotlin.idea.core.refactoring.isMultiLine
|
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.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.ExpressionValue
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Initializer
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Initializer
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Jump
|
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.StronglyMatched
|
||||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnificationResult.WeaklyMatched
|
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnificationResult.WeaklyMatched
|
||||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.UnifierParameter
|
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.*
|
||||||
import org.jetbrains.kotlin.psi.JetPsiFactory.CallableBuilder
|
import org.jetbrains.kotlin.psi.JetPsiFactory.CallableBuilder
|
||||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.DEBUG_TYPE_REFERENCE_STRING
|
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.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
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.JetType
|
||||||
import org.jetbrains.kotlin.types.isFlexible
|
import org.jetbrains.kotlin.types.isFlexible
|
||||||
import org.jetbrains.kotlin.types.isNullabilityFlexible
|
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
@@ -226,6 +224,20 @@ private fun makeCall(
|
|||||||
functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, extractableDescriptor.originalContext)
|
functionLiteralArgument.moveInsideParenthesesAndReplaceWith(wrappedCall, extractableDescriptor.originalContext)
|
||||||
return
|
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)
|
anchor.replace(wrappedCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -209,7 +209,12 @@ public open class KotlinIntroduceParameterHandler(
|
|||||||
open fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) {
|
open fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) {
|
||||||
val context = expression.analyze()
|
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()) {
|
if (expressionType.isUnit() || expressionType.isNothing()) {
|
||||||
val message = JetRefactoringBundle.message(
|
val message = JetRefactoringBundle.message(
|
||||||
"cannot.introduce.parameter.of.0.type",
|
"cannot.introduce.parameter.of.0.type",
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(minus : String.(String) -> Unit) {
|
||||||
|
"A" <selection>-</selection> "B"
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
Cannot perform refactoring without an expression
|
||||||
@@ -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" <selection>x</selection> "B"
|
||||||
|
}
|
||||||
+8
@@ -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
|
||||||
@@ -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) {
|
||||||
|
<selection>+</selection> "A"
|
||||||
|
}
|
||||||
+8
@@ -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
|
||||||
+18
@@ -456,6 +456,24 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
|||||||
doExtractFunctionTest(fileName);
|
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")
|
@TestMetadata("delegatingFunction.kt")
|
||||||
public void testDelegatingFunction() throws Exception {
|
public void testDelegatingFunction() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/basic/delegatingFunction.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/basic/delegatingFunction.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user