Extract Function: Fix callee replacement in operation expressions

#KT-8294 Fixed
This commit is contained in:
Alexey Sedunov
2015-08-17 15:18:46 +03:00
parent 1ca3c5e3b8
commit 1db7195e94
12 changed files with 103 additions and 18 deletions
@@ -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;
}
@@ -27,12 +27,18 @@ public class InfixCallToOrdinaryIntention : JetSelfTargetingIntention<JetBinaryE
}
override fun applyTo(element: JetBinaryExpression, editor: Editor) {
val argument = JetPsiUtil.safeDeparenthesize(element.getRight()!!)
val pattern = "$0.$1" + when (argument) {
is JetFunctionLiteralExpression -> " $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)
}
}
@@ -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() ?: ""
@@ -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)
}
@@ -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",
@@ -0,0 +1,3 @@
fun foo(minus : String.(String) -> Unit) {
"A" <selection>-</selection> "B"
}
@@ -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"
}
@@ -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"
}
@@ -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
@@ -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");