diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ToInfixCallIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ToInfixCallIntention.kt index ae9acca35bb..c39ff1facd4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ToInfixCallIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ToInfixCallIntention.kt @@ -17,8 +17,11 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.openapi.editor.Editor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode public class ToInfixCallIntention : JetSelfTargetingIntention(javaClass(), "Replace with infix function call") { override fun isApplicableTo(element: JetCallExpression, caretOffset: Int): Boolean { @@ -33,9 +36,13 @@ public class ToInfixCallIntention : JetSelfTargetingIntention if (argument.isNamed()) return false if (argument.getArgumentExpression() == null) return false + val bindingContext = element.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = element.getResolvedCall(bindingContext) ?: return false + val function = resolvedCall.resultingDescriptor as? FunctionDescriptor ?: return false + if (!function.isInfix) return false + // check that receiver has type to filter out calls with package/java class qualifier - val receiver = dotQualified.getReceiverExpression() - if (element.analyze().getType(receiver) == null) return false + if (bindingContext.getType(dotQualified.receiverExpression) == null) return false return true } diff --git a/idea/testData/intentions/toInfixCall/binaryExpressionArgument.kt b/idea/testData/intentions/toInfixCall/binaryExpressionArgument.kt index b7be6ef8cc6..ad4ee1d44f3 100644 --- a/idea/testData/intentions/toInfixCall/binaryExpressionArgument.kt +++ b/idea/testData/intentions/toInfixCall/binaryExpressionArgument.kt @@ -1,3 +1,7 @@ -fun foo(x: Int) { +interface X { + infix fun compareTo(p: Int): Int +} + +fun foo(x: X) { x.compareTo(1 + 2) } diff --git a/idea/testData/intentions/toInfixCall/binaryExpressionArgument.kt.after b/idea/testData/intentions/toInfixCall/binaryExpressionArgument.kt.after index ae8d6d9e685..35dd95a03e4 100644 --- a/idea/testData/intentions/toInfixCall/binaryExpressionArgument.kt.after +++ b/idea/testData/intentions/toInfixCall/binaryExpressionArgument.kt.after @@ -1,3 +1,7 @@ -fun foo(x: Int) { +interface X { + infix fun compareTo(p: Int): Int +} + +fun foo(x: X) { x compareTo 1 + 2 } diff --git a/idea/testData/intentions/toInfixCall/caretInsideCalleeExpr.kt b/idea/testData/intentions/toInfixCall/caretInsideCalleeExpr.kt index 7fc217bef6b..a01e660695c 100644 --- a/idea/testData/intentions/toInfixCall/caretInsideCalleeExpr.kt +++ b/idea/testData/intentions/toInfixCall/caretInsideCalleeExpr.kt @@ -1,3 +1,7 @@ -fun foo(x: Int) { - x.times(1) +interface X { + infix fun infix(p: Int): X +} + +fun foo(x: X) { + x.infix(1) } \ No newline at end of file diff --git a/idea/testData/intentions/toInfixCall/caretInsideCalleeExpr.kt.after b/idea/testData/intentions/toInfixCall/caretInsideCalleeExpr.kt.after index 1650135ae7b..e4647e320b6 100644 --- a/idea/testData/intentions/toInfixCall/caretInsideCalleeExpr.kt.after +++ b/idea/testData/intentions/toInfixCall/caretInsideCalleeExpr.kt.after @@ -1,3 +1,7 @@ -fun foo(x: Int) { - x times 1 +interface X { + infix fun infix(p: Int): X +} + +fun foo(x: X) { + x infix 1 } \ No newline at end of file diff --git a/idea/testData/intentions/toInfixCall/caretInsideReceiverExpr.kt b/idea/testData/intentions/toInfixCall/caretInsideReceiverExpr.kt index e1f7d36d272..487c85b36cf 100644 --- a/idea/testData/intentions/toInfixCall/caretInsideReceiverExpr.kt +++ b/idea/testData/intentions/toInfixCall/caretInsideReceiverExpr.kt @@ -1,4 +1,8 @@ // IS_APPLICABLE: false -fun foo(num: Int) { - num.times(1) +interface X { + infix fun infix(p: Int): X +} + +fun foo(num: X) { + num.infix(1) } \ No newline at end of file diff --git a/idea/testData/intentions/toInfixCall/doubleFunctionCall.kt b/idea/testData/intentions/toInfixCall/doubleFunctionCall.kt index a78b043e34e..fe88e999772 100644 --- a/idea/testData/intentions/toInfixCall/doubleFunctionCall.kt +++ b/idea/testData/intentions/toInfixCall/doubleFunctionCall.kt @@ -1,3 +1,7 @@ -fun foo(x: Int) { +interface X { + infix fun times(p: Int): Int +} + +fun foo(x: X) { (x.times(1)).div(2) } diff --git a/idea/testData/intentions/toInfixCall/doubleFunctionCall.kt.after b/idea/testData/intentions/toInfixCall/doubleFunctionCall.kt.after index f4708a05c4d..3295638001e 100644 --- a/idea/testData/intentions/toInfixCall/doubleFunctionCall.kt.after +++ b/idea/testData/intentions/toInfixCall/doubleFunctionCall.kt.after @@ -1,3 +1,7 @@ -fun foo(x: Int) { +interface X { + infix fun times(p: Int): Int +} + +fun foo(x: X) { (x times 1).div(2) } diff --git a/idea/testData/intentions/toInfixCall/doubleFunctionCallWithoutParentheses.kt b/idea/testData/intentions/toInfixCall/doubleFunctionCallWithoutParentheses.kt index 98d87e2bf7b..c567d148666 100644 --- a/idea/testData/intentions/toInfixCall/doubleFunctionCallWithoutParentheses.kt +++ b/idea/testData/intentions/toInfixCall/doubleFunctionCallWithoutParentheses.kt @@ -1,3 +1,7 @@ -fun foo(x: Int) { +interface X { + infix fun plus(p: Int): Int +} + +fun foo(x: X) { x.plus(1).minus(2) } diff --git a/idea/testData/intentions/toInfixCall/doubleFunctionCallWithoutParentheses.kt.after b/idea/testData/intentions/toInfixCall/doubleFunctionCallWithoutParentheses.kt.after index ef62e662dcc..6d555eedf3c 100644 --- a/idea/testData/intentions/toInfixCall/doubleFunctionCallWithoutParentheses.kt.after +++ b/idea/testData/intentions/toInfixCall/doubleFunctionCallWithoutParentheses.kt.after @@ -1,3 +1,7 @@ -fun foo(x: Int) { +interface X { + infix fun plus(p: Int): Int +} + +fun foo(x: X) { (x plus 1).minus(2) } diff --git a/idea/testData/intentions/toInfixCall/functionLiteralArgument.kt b/idea/testData/intentions/toInfixCall/functionLiteralArgument.kt index 321daf2389c..f9a57f7ad74 100644 --- a/idea/testData/intentions/toInfixCall/functionLiteralArgument.kt +++ b/idea/testData/intentions/toInfixCall/functionLiteralArgument.kt @@ -3,5 +3,5 @@ fun foo(x: Foo) { } interface Foo { - fun foo(f: (Int) -> Int) + infix fun foo(f: (Int) -> Int) } diff --git a/idea/testData/intentions/toInfixCall/functionLiteralArgument.kt.after b/idea/testData/intentions/toInfixCall/functionLiteralArgument.kt.after index 925a009a343..0ef43c08c04 100644 --- a/idea/testData/intentions/toInfixCall/functionLiteralArgument.kt.after +++ b/idea/testData/intentions/toInfixCall/functionLiteralArgument.kt.after @@ -3,5 +3,5 @@ fun foo(x: Foo) { } interface Foo { - fun foo(f: (Int) -> Int) + infix fun foo(f: (Int) -> Int) } diff --git a/idea/testData/intentions/toInfixCall/functionSafeCall.kt b/idea/testData/intentions/toInfixCall/functionSafeCall.kt index 1196583229e..ff814043687 100644 --- a/idea/testData/intentions/toInfixCall/functionSafeCall.kt +++ b/idea/testData/intentions/toInfixCall/functionSafeCall.kt @@ -1,4 +1,6 @@ // IS_APPLICABLE: false +fun String.xxx(p: String): Int = 0 + fun foo(x: String?) { - x?.compareTo("1") + x?.xxx("1") } diff --git a/idea/testData/intentions/toInfixCall/inapplicableCaretPosition.kt b/idea/testData/intentions/toInfixCall/inapplicableCaretPosition.kt index 06bc10f47e4..2df2b413e6c 100644 --- a/idea/testData/intentions/toInfixCall/inapplicableCaretPosition.kt +++ b/idea/testData/intentions/toInfixCall/inapplicableCaretPosition.kt @@ -1,4 +1,6 @@ // IS_APPLICABLE: false -fun foo(x: Int) { - x.times(1) +fun String.xxx(p: Int): Int = 0 + +fun foo(x: String) { + x.xxx(1) } \ No newline at end of file diff --git a/idea/testData/intentions/toInfixCall/multipleArguments.kt b/idea/testData/intentions/toInfixCall/multipleArguments.kt index 97af0733658..ca2d9cffa41 100644 --- a/idea/testData/intentions/toInfixCall/multipleArguments.kt +++ b/idea/testData/intentions/toInfixCall/multipleArguments.kt @@ -1,6 +1,7 @@ // IS_APPLICABLE: false +// ERROR: 'infix' modifier is inapplicable on this function interface Foo { - fun foo(a: Int, b: Int) + infix fun foo(a: Int, b: Int) } fun foo(x: Foo) { diff --git a/idea/testData/intentions/toInfixCall/namedArgument.kt b/idea/testData/intentions/toInfixCall/namedArgument.kt index 1d44d394302..cf80c489303 100644 --- a/idea/testData/intentions/toInfixCall/namedArgument.kt +++ b/idea/testData/intentions/toInfixCall/namedArgument.kt @@ -4,5 +4,5 @@ fun foo(x: Foo) { } interface Foo { - fun foo(baz: Int = 0, bar: Foo? = null) + infix fun foo(baz: Int = 0, bar: Foo? = null) } diff --git a/idea/testData/intentions/toInfixCall/notInfix.kt b/idea/testData/intentions/toInfixCall/notInfix.kt new file mode 100644 index 00000000000..133cc86e209 --- /dev/null +++ b/idea/testData/intentions/toInfixCall/notInfix.kt @@ -0,0 +1,6 @@ +// IS_APPLICABLE: false +fun Int.xxx(p: Int) = 1 + +fun foo(x: Int) { + x.xxx(1) +} diff --git a/idea/testData/intentions/toInfixCall/nullAssertedReceiver.kt b/idea/testData/intentions/toInfixCall/nullAssertedReceiver.kt index b43bd0879d1..31e851163d5 100644 --- a/idea/testData/intentions/toInfixCall/nullAssertedReceiver.kt +++ b/idea/testData/intentions/toInfixCall/nullAssertedReceiver.kt @@ -1,3 +1,5 @@ +infix fun String.xxx(p: Int): String = this + fun foo(x: String?) { - x!!.plus(1) + x!!.xxx(1) } diff --git a/idea/testData/intentions/toInfixCall/nullAssertedReceiver.kt.after b/idea/testData/intentions/toInfixCall/nullAssertedReceiver.kt.after index fed5bdadb77..6c5ed48b011 100644 --- a/idea/testData/intentions/toInfixCall/nullAssertedReceiver.kt.after +++ b/idea/testData/intentions/toInfixCall/nullAssertedReceiver.kt.after @@ -1,3 +1,5 @@ +infix fun String.xxx(p: Int): String = this + fun foo(x: String?) { - x!! plus 1 + x!! xxx 1 } diff --git a/idea/testData/intentions/toInfixCall/packageFunctionCall.kt b/idea/testData/intentions/toInfixCall/packageFunctionCall.kt index d1d3e674e10..5a54d511c7b 100644 --- a/idea/testData/intentions/toInfixCall/packageFunctionCall.kt +++ b/idea/testData/intentions/toInfixCall/packageFunctionCall.kt @@ -1,5 +1,9 @@ // IS_APPLICABLE: false -// WITH_RUNTIME +// ERROR: 'infix' modifier is inapplicable on this function +package ppp + +infix fun foo(p: String){} + fun main() { - kotlin.io.println("") + ppp.foo("") } \ No newline at end of file diff --git a/idea/testData/intentions/toInfixCall/secondParameterLabeled.kt b/idea/testData/intentions/toInfixCall/secondParameterLabeled.kt index 33045ba3433..c8314ab7586 100644 --- a/idea/testData/intentions/toInfixCall/secondParameterLabeled.kt +++ b/idea/testData/intentions/toInfixCall/secondParameterLabeled.kt @@ -1,6 +1,6 @@ // IS_APPLICABLE: false class Foo { - fun foo(x: Int = 0, y: Int = 0) { + infix fun foo(x: Int = 0, y: Int = 0) { } } diff --git a/idea/testData/intentions/toInfixCall/simpleArgumentAndFunctionLiteralArgument.kt b/idea/testData/intentions/toInfixCall/simpleArgumentAndFunctionLiteralArgument.kt index 24a7e4d2600..b653d66b18e 100644 --- a/idea/testData/intentions/toInfixCall/simpleArgumentAndFunctionLiteralArgument.kt +++ b/idea/testData/intentions/toInfixCall/simpleArgumentAndFunctionLiteralArgument.kt @@ -1,8 +1,10 @@ // IS_APPLICABLE: false +// ERROR: 'infix' modifier is inapplicable on this function + fun foo(x: Foo) { x.foo(1) { it * 2 } } interface Foo { - fun foo(a: Int, f: (Int) -> Int) + infix fun foo(a: Int, f: (Int) -> Int) } diff --git a/idea/testData/intentions/toInfixCall/simpleMethodCall.kt b/idea/testData/intentions/toInfixCall/simpleMethodCall.kt index fc6e49485d5..70a7afaab33 100644 --- a/idea/testData/intentions/toInfixCall/simpleMethodCall.kt +++ b/idea/testData/intentions/toInfixCall/simpleMethodCall.kt @@ -1,3 +1,7 @@ -fun foo(x: Int) { +interface X { + infix fun times(p: Int): Int +} + +fun foo(x: X) { x.times(1) } diff --git a/idea/testData/intentions/toInfixCall/simpleMethodCall.kt.after b/idea/testData/intentions/toInfixCall/simpleMethodCall.kt.after index c61e6841f3f..fe363ea2513 100644 --- a/idea/testData/intentions/toInfixCall/simpleMethodCall.kt.after +++ b/idea/testData/intentions/toInfixCall/simpleMethodCall.kt.after @@ -1,3 +1,7 @@ -fun foo(x: Int) { +interface X { + infix fun times(p: Int): Int +} + +fun foo(x: X) { x times 1 } diff --git a/idea/testData/intentions/toInfixCall/singlePackageFunctionCall.kt b/idea/testData/intentions/toInfixCall/singlePackageFunctionCall.kt index 5b63ec8b0f9..c2575dec383 100644 --- a/idea/testData/intentions/toInfixCall/singlePackageFunctionCall.kt +++ b/idea/testData/intentions/toInfixCall/singlePackageFunctionCall.kt @@ -1,8 +1,10 @@ // WITH_RUNTIME // IS_APPLICABLE: false +// ERROR: 'infix' modifier is inapplicable on this function + package demo -fun foo(str: String) = kotlin.io.println(str) +infix fun foo(str: String) = kotlin.io.println(str) fun main() { demo.foo("") diff --git a/idea/testData/intentions/toInfixCall/zeroArguments.kt b/idea/testData/intentions/toInfixCall/zeroArguments.kt index 91a1955a6ce..e66d46a45a5 100644 --- a/idea/testData/intentions/toInfixCall/zeroArguments.kt +++ b/idea/testData/intentions/toInfixCall/zeroArguments.kt @@ -1,4 +1,7 @@ // IS_APPLICABLE: false +// ERROR: No value passed for parameter p +infix fun Int.xxx(p: Int) = 1 + fun foo(x: Int) { - x.toString() + x.xxx() } diff --git a/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithLibClassQualifier.kt b/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithLibClassQualifier.kt index a68a9cf947d..ace3416079a 100644 --- a/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithLibClassQualifier.kt +++ b/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithLibClassQualifier.kt @@ -1,6 +1,5 @@ // "Create class 'Foo'" "false" // ACTION: Create extension function 'Foo' -// ACTION: Replace with infix function call // ERROR: Unresolved reference: Foo fun test() { diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/propertyOnUserType.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/propertyOnUserType.kt index f5cd8ad599f..d05f216af1a 100644 --- a/idea/testData/quickfix/createFromUsage/createFunction/call/propertyOnUserType.kt +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/propertyOnUserType.kt @@ -1,6 +1,5 @@ // "Create member function 'foo'" "false" // ACTION: Convert to expression body -// ACTION: Replace with infix function call // ERROR: Unresolved reference: x class A(val n: T) { diff --git a/idea/testData/quickfix/createFromUsage/createVariable/property/callOnUserType.kt b/idea/testData/quickfix/createFromUsage/createVariable/property/callOnUserType.kt index 24910ba26cb..b214f1b2c26 100644 --- a/idea/testData/quickfix/createFromUsage/createVariable/property/callOnUserType.kt +++ b/idea/testData/quickfix/createFromUsage/createVariable/property/callOnUserType.kt @@ -1,7 +1,6 @@ // "Create property 'foo'" "false" // ACTION: Create extension function 'bar' // ACTION: Create member function 'bar' -// ACTION: Replace with infix function call // ERROR: Unresolved reference: bar // ERROR: Unresolved reference: foo diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index f88d1f724d5..3230d619c7d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -7707,6 +7707,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("notInfix.kt") + public void testNotInfix() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/toInfixCall/notInfix.kt"); + doTest(fileName); + } + @TestMetadata("nullAssertedReceiver.kt") public void testNullAssertedReceiver() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/toInfixCall/nullAssertedReceiver.kt");