From 4b50fb18fb662d87302dec6bc19e510c168046c0 Mon Sep 17 00:00:00 2001 From: Roman Golyshev Date: Wed, 25 Dec 2019 19:40:10 +0300 Subject: [PATCH] KT-35258 Add better completion for `MixedNamedArgumentsInTheirOwnPosition` feature - When named parameter is on its own position, the completion of the next argument will not be limited by named arguments only - The completion will not suggest already used arguments names - ^KT-35258 Fixed --- .../idea/completion/BasicCompletionSession.kt | 2 +- .../completion/NamedArgumentCompletion.kt | 38 ++++++++++++++++++- .../smart/SmartCompletionSession.kt | 2 +- .../NamedArgumentOnIncorrectPosition.kt | 10 +++++ .../NamedArgumentOnItsOwnPosition.kt | 11 ++++++ .../NamedArgumentOnItsOwnPosition2.kt | 13 +++++++ .../test/JSBasicCompletionTestGenerated.java | 15 ++++++++ .../test/JvmBasicCompletionTestGenerated.java | 15 ++++++++ .../org/jetbrains/kotlin/idea/core/Utils.kt | 4 ++ 9 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnIncorrectPosition.kt create mode 100644 idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition.kt create mode 100644 idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition2.kt diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index 367e90f8afc..e4eaf7b8983 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -105,7 +105,7 @@ class BasicCompletionSession( if (OPERATOR_NAME.isApplicable()) return OPERATOR_NAME - if (NamedArgumentCompletion.isOnlyNamedArgumentExpected(nameExpression)) { + if (NamedArgumentCompletion.isOnlyNamedArgumentExpected(nameExpression, resolutionFacade)) { return NAMED_ARGUMENTS_ONLY } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt index 12c18233c38..c5d99e8db66 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/NamedArgumentCompletion.kt @@ -9,28 +9,42 @@ import com.intellij.codeInsight.completion.InsertHandler import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.idea.KotlinIcons +import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler import org.jetbrains.kotlin.idea.core.ArgumentPositionData import org.jetbrains.kotlin.idea.core.ExpectedInfo +import org.jetbrains.kotlin.idea.project.languageVersionSettings +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.util.CallType import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtCallElement import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.renderer.render +import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.types.KotlinType import java.util.* object NamedArgumentCompletion { - fun isOnlyNamedArgumentExpected(nameExpression: KtSimpleNameExpression): Boolean { + fun isOnlyNamedArgumentExpected(nameExpression: KtSimpleNameExpression, resolutionFacade: ResolutionFacade): Boolean { val thisArgument = nameExpression.parent as? KtValueArgument ?: return false if (thisArgument.isNamed()) return false val callElement = thisArgument.getStrictParentOfType() ?: return false + val argumentsBeforeThis = callElement.valueArguments.takeWhile { it != thisArgument } - return callElement.valueArguments.takeWhile { it != thisArgument }.any { it.isNamed() } + if (!nameExpression.languageVersionSettings.supportsFeature(LanguageFeature.MixedNamedArgumentsInTheirOwnPosition)) { + return argumentsBeforeThis.any { it.isNamed() } + } + + val resolvedCall = callElement.resolveToCall(resolutionFacade) ?: return false + return argumentsBeforeThis.any { it.isNamed() && !it.placedOnItsOwnPositionInCall(resolvedCall) } } fun complete(collector: LookupElementsCollector, expectedInfos: Collection, callType: CallType<*>) { @@ -68,3 +82,23 @@ object NamedArgumentCompletion { } } } + +/** + * Checks whether argument in the [resolvedCall] is on the same position as it listed in the callable definition. + * + * It is always true for the positional arguments, but may be untrue for the named arguments. + * + * ``` + * fun foo(a: Int, b: Int, c: Int, d: Int) {} + * + * foo( + * 10, // true + * b = 10, // true, possible since Kotlin 1.4 with `MixedNamedArgumentsInTheirOwnPosition` feature + * d = 30, // false, 3 vs 4 + * c = 40 // false, 4 vs 3 + * ) + * ``` + */ +private fun ValueArgument.placedOnItsOwnPositionInCall(resolvedCall: ResolvedCall): Boolean { + return resolvedCall.getParameterForArgument(this)?.index == resolvedCall.call.valueArguments.indexOf(this) +} diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt index 1f2e6c42584..20dc62670a6 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt @@ -63,7 +63,7 @@ class SmartCompletionSession( get() = smartCompletion?.expectedInfos ?: emptyList() override fun doComplete() { - if (nameExpression != null && NamedArgumentCompletion.isOnlyNamedArgumentExpected(nameExpression)) { + if (nameExpression != null && NamedArgumentCompletion.isOnlyNamedArgumentExpected(nameExpression, resolutionFacade)) { NamedArgumentCompletion.complete(collector, expectedInfos, callTypeAndReceiver.callType) return } diff --git a/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnIncorrectPosition.kt b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnIncorrectPosition.kt new file mode 100644 index 00000000000..d7a8e5015e9 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnIncorrectPosition.kt @@ -0,0 +1,10 @@ +fun foo(p1: Int, p2: Int, p3: Long) {} + +fun usage(param: Long) { + foo(p2 = 10, ) +} + +// LANGUAGE_VERSION: 1.4 +// EXIST: { itemText: "p1 =" } +// EXIST: { itemText: "p3 =" } +// NOTHING_ELSE diff --git a/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition.kt b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition.kt new file mode 100644 index 00000000000..0c6577f2fd0 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition.kt @@ -0,0 +1,11 @@ +fun foo(p1: Int, p2: Int, p3: Long) {} + +fun usage(param: Long) { + foo(p1 = 10, ) +} + +// LANGUAGE_VERSION: 1.4 +// EXIST: param +// EXIST: { itemText: "p2 =" } +// EXIST: { itemText: "p3 =" } +// ABSENT: { itemText: "p1 =" } diff --git a/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition2.kt b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition2.kt new file mode 100644 index 00000000000..f4dda4880f3 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition2.kt @@ -0,0 +1,13 @@ +fun foo(p1: Int, p2: Int, p3: Int, p4: Int, p5: Int) {} + +fun usage(param: Int) { + foo(0, p2 = 10, 20, p4 = 30, ) +} + +// LANGUAGE_VERSION: 1.4 +// EXIST: param +// EXIST: { itemText: "p5 =" } +// ABSENT: { itemText: "p1 =" } +// ABSENT: { itemText: "p2 =" } +// ABSENT: { itemText: "p3 =" } +// ABSENT: { itemText: "p4 =" } diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index 89d6e1ab1e7..f8e7be44472 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -1799,6 +1799,21 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes runTest("idea/idea-completion/testData/basic/common/namedArguments/InParameterExpression.kt"); } + @TestMetadata("NamedArgumentOnIncorrectPosition.kt") + public void testNamedArgumentOnIncorrectPosition() throws Exception { + runTest("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnIncorrectPosition.kt"); + } + + @TestMetadata("NamedArgumentOnItsOwnPosition.kt") + public void testNamedArgumentOnItsOwnPosition() throws Exception { + runTest("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition.kt"); + } + + @TestMetadata("NamedArgumentOnItsOwnPosition2.kt") + public void testNamedArgumentOnItsOwnPosition2() throws Exception { + runTest("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition2.kt"); + } + @TestMetadata("NamedArgumentsFromOverloads.kt") public void testNamedArgumentsFromOverloads() throws Exception { runTest("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index b69aa1648f4..7add807232a 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -1799,6 +1799,21 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT runTest("idea/idea-completion/testData/basic/common/namedArguments/InParameterExpression.kt"); } + @TestMetadata("NamedArgumentOnIncorrectPosition.kt") + public void testNamedArgumentOnIncorrectPosition() throws Exception { + runTest("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnIncorrectPosition.kt"); + } + + @TestMetadata("NamedArgumentOnItsOwnPosition.kt") + public void testNamedArgumentOnItsOwnPosition() throws Exception { + runTest("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition.kt"); + } + + @TestMetadata("NamedArgumentOnItsOwnPosition2.kt") + public void testNamedArgumentOnItsOwnPosition2() throws Exception { + runTest("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentOnItsOwnPosition2.kt"); + } + @TestMetadata("NamedArgumentsFromOverloads.kt") public void testNamedArgumentsFromOverloads() throws Exception { runTest("idea/idea-completion/testData/basic/common/namedArguments/NamedArgumentsFromOverloads.kt"); diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt index 8ec420e3bae..aa90b79afd9 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt @@ -62,6 +62,10 @@ fun Call.mapArgumentsToParameters(targetDescriptor: CallableDescriptor): Map