diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetTypeReference.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetTypeReference.java index 739f6d123aa..ee8da89b986 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetTypeReference.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetTypeReference.java @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.psi; import com.intellij.lang.ASTNode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.lexer.JetTokens; import org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub; import org.jetbrains.kotlin.psi.stubs.elements.JetStubElementTypes; @@ -65,4 +66,8 @@ public class JetTypeReference extends JetElementImplStubemptyList(); } + + public boolean hasParentheses() { + return findChildByType(JetTokens.LPAR) != null && findChildByType(JetTokens.LPAR) != null; + } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/TypeRefHelpers.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/TypeRefHelpers.kt index c73bec46ead..26916e5d3f0 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/TypeRefHelpers.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/TypeRefHelpers.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.psi.JetPsiFactory import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.PsiErrorElement import org.jetbrains.kotlin.psi.JetCallableDeclaration +import org.jetbrains.kotlin.psi.JetFunctionType import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull fun getTypeReference(declaration: JetCallableDeclaration): JetTypeReference? { @@ -56,17 +57,25 @@ fun setTypeReference(declaration: JetCallableDeclaration, addAfter: PsiElement?, } fun JetCallableDeclaration.setReceiverTypeReference(typeRef: JetTypeReference?): JetTypeReference? { + val needParentheses = typeRef != null && typeRef.getTypeElement() is JetFunctionType && !typeRef.hasParentheses() val oldTypeRef = getReceiverTypeReference() if (typeRef != null) { - if (oldTypeRef != null) { - return oldTypeRef.replace(typeRef) as JetTypeReference - } - else { - val anchor = getNameIdentifier() - val newTypeRef = addBefore(typeRef, anchor) as JetTypeReference - addAfter(JetPsiFactory(getProject()).createDot(), newTypeRef) - return newTypeRef + val newTypeRef = + if (oldTypeRef != null) { + oldTypeRef.replace(typeRef) as JetTypeReference + } + else { + val anchor = getNameIdentifier() + val newTypeRef = addBefore(typeRef, anchor) as JetTypeReference + addAfter(JetPsiFactory(getProject()).createDot(), newTypeRef) + newTypeRef + } + if (needParentheses) { + val argList = JetPsiFactory(getProject()).createCallArguments("()") + newTypeRef.addBefore(argList.getLeftParenthesis()!!, newTypeRef.getFirstChild()) + newTypeRef.add(argList.getRightParenthesis()!!) } + return newTypeRef } else { if (oldTypeRef != null) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/callUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/callUtil.kt index 5053568fc00..b9b7cfc7c95 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/callUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/callUtil.kt @@ -76,7 +76,7 @@ public fun > Call.hasUnresolvedArguments(context: Resolu public fun Call.getValueArgumentsInParentheses(): List = getValueArguments().filterArgsInParentheses() -public fun JetCallExpression.getValueArgumentsInParentheses(): List = getValueArguments().filterArgsInParentheses() +public fun JetCallElement.getValueArgumentsInParentheses(): List = getValueArguments().filterArgsInParentheses() public fun Call.getValueArgumentListOrElement(): JetElement = getValueArgumentList() ?: getCalleeExpression() ?: getCallElement() diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt index 85b4e0fe5a8..07229237285 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.openapi.editor.Editor +import org.jetbrains.kotlin.idea.util.psiModificationUtil.moveLambdaOutsideParentheses import org.jetbrains.kotlin.psi.JetCallExpression import org.jetbrains.kotlin.psi.JetFunctionLiteralExpression import org.jetbrains.kotlin.psi.JetPsiFactory @@ -37,19 +38,6 @@ public class MoveLambdaOutsideParenthesesIntention : JetSelfTargetingIntention { private void updateArgumentsAndReceiver(JetChangeInfo changeInfo, JetCallElement element) { JetValueArgumentList arguments = element.getValueArgumentList(); assert arguments != null : "Argument list is expected: " + element.getText(); - List oldArguments = arguments.getArguments(); + List oldArguments = element.getValueArguments(); boolean isNamedCall = oldArguments.size() > 1 && oldArguments.get(0).getArgumentName() != null; StringBuilder parametersBuilder = new StringBuilder("("); @@ -99,7 +101,7 @@ public class JetFunctionCallUsage extends JetUsageInfo { parametersBuilder.append(')'); JetValueArgumentList newArguments = JetPsiFactory(getProject()).createCallArguments(parametersBuilder.toString()); - Map argumentMap = getParamIndexToArgumentMap(changeInfo, oldArguments); + Map argumentMap = getParamIndexToArgumentMap(changeInfo, oldArguments); int argIndex = 0; JetParameterInfo newReceiverInfo = changeInfo.getReceiverParameterInfo(); @@ -131,37 +133,58 @@ public class JetFunctionCallUsage extends JetUsageInfo { continue; } - JetValueArgument oldArgument = argumentMap.get(parameterInfo.getOldIndex()); + ValueArgument oldArgument = argumentMap.get(parameterInfo.getOldIndex()); if (oldArgument != null) { JetValueArgumentName argumentName = oldArgument.getArgumentName(); JetSimpleNameExpression argumentNameExpression = argumentName != null ? argumentName.getReferenceExpression() : null; changeArgumentName(argumentNameExpression, parameterInfo); - newArgument.replace(oldArgument); + //noinspection ConstantConditions + newArgument.replace(oldArgument instanceof JetFunctionLiteralArgument + ? psiFactory.createArgument(oldArgument.getArgumentExpression()) + : oldArgument.asElement()); } else if (parameterInfo.getDefaultValueForCall().isEmpty()) newArgument.delete(); } - arguments.replace(newArguments); - - if (newReceiverInfo == originalReceiverInfo) return; - - PsiElement replacingElement = element; - if (newReceiverInfo != null) { - JetValueArgument receiverArgument = argumentMap.get(newReceiverInfo.getOldIndex()); - JetExpression extensionReceiverExpression = receiverArgument != null ? receiverArgument.getArgumentExpression() : null; - String receiverText = extensionReceiverExpression != null - ? extensionReceiverExpression.getText() - : newReceiverInfo.getDefaultValueForCall(); - if (receiverText.isEmpty()) { - receiverText = "_"; - } - - replacingElement = psiFactory.createExpression(receiverText + "." + element.getText()); + List lambdaArguments = element.getFunctionLiteralArguments(); + boolean hasLambdaArgumentsBefore = !lambdaArguments.isEmpty(); + if (hasLambdaArgumentsBefore) { + element.deleteChildRange(KotlinPackage.first(lambdaArguments), KotlinPackage.last(lambdaArguments)); } - elementToReplace.replace(replacingElement); + JetValueArgument lastArgument = KotlinPackage.lastOrNull(newArguments.getArguments()); + boolean hasTrailingLambdaInArgumentListAfter = lastArgument != null && lastArgument.getArgumentExpression() instanceof JetFunctionLiteralExpression; + + arguments.replace(newArguments); + + JetElement newElement = element; + if (newReceiverInfo != originalReceiverInfo) { + PsiElement replacingElement = element; + if (newReceiverInfo != null) { + ValueArgument receiverArgument = argumentMap.get(newReceiverInfo.getOldIndex()); + JetExpression extensionReceiverExpression = receiverArgument != null ? receiverArgument.getArgumentExpression() : null; + String receiverText = extensionReceiverExpression != null + ? extensionReceiverExpression.getText() + : newReceiverInfo.getDefaultValueForCall(); + if (receiverText.isEmpty()) { + receiverText = "_"; + } + + replacingElement = psiFactory.createExpression(receiverText + "." + element.getText()); + } + + newElement = (JetElement) elementToReplace.replace(replacingElement); + } + + if (hasLambdaArgumentsBefore && hasTrailingLambdaInArgumentListAfter) { + JetCallElement newCallElement = + (JetCallElement) (newElement instanceof JetQualifiedExpression + ? ((JetQualifiedExpression) newElement).getSelectorExpression() + : newElement); + PsiModificationUtilPackage.moveLambdaOutsideParentheses(newCallElement); + } } @Nullable @@ -177,11 +200,11 @@ public class JetFunctionCallUsage extends JetUsageInfo { return null; } - private static Map getParamIndexToArgumentMap(JetChangeInfo changeInfo, List oldArguments) { - Map argumentMap = new HashMap(); + private static Map getParamIndexToArgumentMap(JetChangeInfo changeInfo, List oldArguments) { + Map argumentMap = new HashMap(); for (int i = 0; i < oldArguments.size(); i++) { - JetValueArgument argument = oldArguments.get(i); + ValueArgument argument = oldArguments.get(i); JetValueArgumentName argumentName = argument.getArgumentName(); JetSimpleNameExpression argumentNameExpression = argumentName != null ? argumentName.getReferenceExpression() : null; String oldParameterName = argumentNameExpression != null ? argumentNameExpression.getReferencedName() : null; diff --git a/idea/src/org/jetbrains/kotlin/idea/util/psiModificationUtil.kt b/idea/src/org/jetbrains/kotlin/idea/util/psiModificationUtil.kt index 3d337a71665..99d8e6e4f17 100644 --- a/idea/src/org/jetbrains/kotlin/idea/util/psiModificationUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/util/psiModificationUtil.kt @@ -18,10 +18,7 @@ package org.jetbrains.kotlin.idea.util.psiModificationUtil import com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.JetCallExpression -import org.jetbrains.kotlin.psi.JetExpression -import org.jetbrains.kotlin.psi.JetFunctionLiteralArgument -import org.jetbrains.kotlin.psi.JetPsiFactory +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses @@ -77,3 +74,20 @@ fun JetFunctionLiteralArgument.moveInsideParenthesesAndReplaceWith( return oldCallExpression.replace(newCallExpression) as JetCallExpression } +fun JetCallElement.moveLambdaOutsideParentheses() { + val args = getValueArgumentsInParentheses() + val functionLiteral = args.last!!.getArgumentExpression()?.getText() + val calleeText = getCalleeExpression()?.getText() + if (calleeText == null || functionLiteral == null) return + + val params = args.subList(0, args.size - 1).map { it.asElement().getText() ?: "" }.joinToString(", ", "(", ")") + + val newCall = + if (params == "()") { + "$calleeText $functionLiteral" + } + else { + "$calleeText$params $functionLiteral" + } + replace(JetPsiFactory(this).createExpression(newCall)) +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/ConvertLambdaParameterToReceiverAfter.kt b/idea/testData/refactoring/changeSignature/ConvertLambdaParameterToReceiverAfter.kt new file mode 100644 index 00000000000..3cdfd8c0cd2 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/ConvertLambdaParameterToReceiverAfter.kt @@ -0,0 +1,9 @@ +fun (() -> Int).foo(x: Int, y: Int): Int { + return x + this() +} + +fun bar() { + { + 3 + }.foo(1, 2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/ConvertLambdaParameterToReceiverBefore.kt b/idea/testData/refactoring/changeSignature/ConvertLambdaParameterToReceiverBefore.kt new file mode 100644 index 00000000000..397a006fe4d --- /dev/null +++ b/idea/testData/refactoring/changeSignature/ConvertLambdaParameterToReceiverBefore.kt @@ -0,0 +1,9 @@ +fun foo(x: Int, y: Int, cl: () -> Int): Int { + return x + cl() +} + +fun bar() { + foo(1, 2) { + 3 + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/MoveLambdaParameterAfter.kt b/idea/testData/refactoring/changeSignature/MoveLambdaParameterAfter.kt new file mode 100644 index 00000000000..e344318959f --- /dev/null +++ b/idea/testData/refactoring/changeSignature/MoveLambdaParameterAfter.kt @@ -0,0 +1,9 @@ +fun foo(x: Int, cl: () -> Int, y: Int): Int { + return x + cl() +} + +fun bar() { + foo(1, { + 3 + }, 2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/MoveLambdaParameterBefore.kt b/idea/testData/refactoring/changeSignature/MoveLambdaParameterBefore.kt new file mode 100644 index 00000000000..397a006fe4d --- /dev/null +++ b/idea/testData/refactoring/changeSignature/MoveLambdaParameterBefore.kt @@ -0,0 +1,9 @@ +fun foo(x: Int, y: Int, cl: () -> Int): Int { + return x + cl() +} + +fun bar() { + foo(1, 2) { + 3 + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveLambdaParameterAfter.kt b/idea/testData/refactoring/changeSignature/RemoveLambdaParameterAfter.kt new file mode 100644 index 00000000000..f1bbc0e0051 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveLambdaParameterAfter.kt @@ -0,0 +1,7 @@ +fun foo(x: Int, y: Int): Int { + return x + cl() +} + +fun bar() { + foo(1, 2) +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveLambdaParameterBefore.kt b/idea/testData/refactoring/changeSignature/RemoveLambdaParameterBefore.kt new file mode 100644 index 00000000000..397a006fe4d --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveLambdaParameterBefore.kt @@ -0,0 +1,9 @@ +fun foo(x: Int, y: Int, cl: () -> Int): Int { + return x + cl() +} + +fun bar() { + foo(1, 2) { + 3 + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveLastNonLambdaParameterAfter.kt b/idea/testData/refactoring/changeSignature/RemoveLastNonLambdaParameterAfter.kt new file mode 100644 index 00000000000..8147130d7a4 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveLastNonLambdaParameterAfter.kt @@ -0,0 +1,9 @@ +fun foo(cl: () -> Int): Int { + return x + cl() +} + +fun bar() { + foo { + 2 + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveLastNonLambdaParameterBefore.kt b/idea/testData/refactoring/changeSignature/RemoveLastNonLambdaParameterBefore.kt new file mode 100644 index 00000000000..665b6af0371 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveLastNonLambdaParameterBefore.kt @@ -0,0 +1,9 @@ +fun foo(x: Int, cl: () -> Int): Int { + return x + cl() +} + +fun bar() { + foo(1) { + 2 + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveParameterBeforeLambdaAfter.kt b/idea/testData/refactoring/changeSignature/RemoveParameterBeforeLambdaAfter.kt new file mode 100644 index 00000000000..8eb40bcd406 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveParameterBeforeLambdaAfter.kt @@ -0,0 +1,9 @@ +fun foo(x: Int, cl: () -> Int): Int { + return x + cl() +} + +fun bar() { + foo(1) { + 3 + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/RemoveParameterBeforeLambdaBefore.kt b/idea/testData/refactoring/changeSignature/RemoveParameterBeforeLambdaBefore.kt new file mode 100644 index 00000000000..397a006fe4d --- /dev/null +++ b/idea/testData/refactoring/changeSignature/RemoveParameterBeforeLambdaBefore.kt @@ -0,0 +1,9 @@ +fun foo(x: Int, y: Int, cl: () -> Int): Int { + return x + cl() +} + +fun bar() { + foo(1, 2) { + 3 + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureTest.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureTest.java index 0859b897722..4e0dab76d81 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureTest.java @@ -800,6 +800,32 @@ public class JetChangeSignatureTest extends KotlinCodeInsightTestCase { doTest(changeInfo); } + public void testRemoveParameterBeforeLambda() throws Exception { + JetChangeInfo changeInfo = getChangeInfo(); + changeInfo.removeParameter(1); + doTest(changeInfo); + } + + public void testMoveLambdaParameter() throws Exception { + JetChangeInfo changeInfo = getChangeInfo(); + JetParameterInfo[] newParameters = changeInfo.getNewParameters(); + changeInfo.setNewParameter(1, newParameters[2]); + changeInfo.setNewParameter(2, newParameters[1]); + doTest(changeInfo); + } + + public void testConvertLambdaParameterToReceiver() throws Exception { + JetChangeInfo changeInfo = getChangeInfo(); + changeInfo.setReceiverParameterInfo(changeInfo.getNewParameters()[2]); + doTest(changeInfo); + } + + public void testRemoveLambdaParameter() throws Exception { + JetChangeInfo changeInfo = getChangeInfo(); + changeInfo.removeParameter(2); + doTest(changeInfo); + } + @NotNull @Override protected String getTestDataPath() {