From a350ff045f285e6730a66aad9f616a9270a3e3d7 Mon Sep 17 00:00:00 2001 From: "andrey.matveev" Date: Tue, 2 Feb 2021 23:51:37 +0700 Subject: [PATCH] PY-46668 Impl multiple arguments completion contributor (cherry picked from commit 42efe6a20f52cfb3bd827a2b68d1fb6cc3949626) IJ-MR-5791 GitOrigin-RevId: e2a40572b0de4919a5049ff06e437b34d78c296c --- .../resources/META-INF/PythonPsiImpl.xml | 2 + ...yMultipleArgumentsCompletionContributor.kt | 129 ++++++++++++++++++ .../completion/PythonCompletionWeigher.java | 5 + .../jetbrains/python/extensions/CaptureExt.kt | 4 + .../mlcompletion/PyElementFeatureProvider.kt | 5 + .../argumentParameter.after.py | 7 + .../argumentParameter.py | 7 + .../classConstructor.after.py | 12 ++ .../classConstructor.py | 12 ++ .../classMember.after.py | 9 ++ .../classMember.py | 9 ++ ...functionWithThreeArgsTwoSuggested.after.py | 8 ++ .../functionWithThreeArgsTwoSuggested.py | 8 ++ .../functionWithTwoArgs.after.py | 7 + .../functionWithTwoArgs.py | 7 + .../listComprehension.after.py | 6 + .../listComprehension.py | 6 + .../notSuggestIfHaveArgumentsRight.py | 8 ++ .../notSuggestIfNotEnoughArguments.py | 7 + ...tSuggestIfNotEnoughArgumentsBeforeCaret.py | 8 ++ ...fNotEnoughArgumentsInTheScopeOfFunction.py | 9 ++ ...oughArgumentsInTheScopeOfNestedFunction.py | 10 ++ .../notSuggestIfOnlyOneVariable.py | 5 + .../notSuggestIfTargetOutOfScopeFunction.py | 7 + .../notSuggestInKeywordArgument.py | 8 ++ .../notSuggestKeywordContainer.py | 7 + .../notSuggestPositionalContainer.py | 7 + .../multipleArgumentsCompletion/overloads.py | 24 ++++ .../singleStarParameter.after.py | 8 ++ .../singleStarParameter.py | 8 ++ ...entsForParametersWithDefaultValue.after.py | 7 + ...pArgumentsForParametersWithDefaultValue.py | 7 + ...aultValueIfNotFromFunctionContext.after.py | 9 ++ ...kipDefaultValueIfNotFromFunctionContext.py | 9 ++ .../slashAndSingleStarParameter.after.py | 9 ++ .../slashAndSingleStarParameter.py | 9 ++ .../slashParameter.after.py | 8 ++ .../slashParameter.py | 8 ++ ...entsForParametersWithDefaultValue.after.py | 8 ++ ...tArgumentsForParametersWithDefaultValue.py | 8 ++ .../PyMultipleArgumentsCompletionTest.kt | 117 ++++++++++++++++ 41 files changed, 558 insertions(+) create mode 100644 python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyMultipleArgumentsCompletionContributor.kt create mode 100644 python/testData/multipleArgumentsCompletion/argumentParameter.after.py create mode 100644 python/testData/multipleArgumentsCompletion/argumentParameter.py create mode 100644 python/testData/multipleArgumentsCompletion/classConstructor.after.py create mode 100644 python/testData/multipleArgumentsCompletion/classConstructor.py create mode 100644 python/testData/multipleArgumentsCompletion/classMember.after.py create mode 100644 python/testData/multipleArgumentsCompletion/classMember.py create mode 100644 python/testData/multipleArgumentsCompletion/functionWithThreeArgsTwoSuggested.after.py create mode 100644 python/testData/multipleArgumentsCompletion/functionWithThreeArgsTwoSuggested.py create mode 100644 python/testData/multipleArgumentsCompletion/functionWithTwoArgs.after.py create mode 100644 python/testData/multipleArgumentsCompletion/functionWithTwoArgs.py create mode 100644 python/testData/multipleArgumentsCompletion/listComprehension.after.py create mode 100644 python/testData/multipleArgumentsCompletion/listComprehension.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestIfHaveArgumentsRight.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArguments.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsBeforeCaret.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsInTheScopeOfFunction.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsInTheScopeOfNestedFunction.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestIfOnlyOneVariable.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestIfTargetOutOfScopeFunction.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestInKeywordArgument.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestKeywordContainer.py create mode 100644 python/testData/multipleArgumentsCompletion/notSuggestPositionalContainer.py create mode 100644 python/testData/multipleArgumentsCompletion/overloads.py create mode 100644 python/testData/multipleArgumentsCompletion/singleStarParameter.after.py create mode 100644 python/testData/multipleArgumentsCompletion/singleStarParameter.py create mode 100644 python/testData/multipleArgumentsCompletion/skipArgumentsForParametersWithDefaultValue.after.py create mode 100644 python/testData/multipleArgumentsCompletion/skipArgumentsForParametersWithDefaultValue.py create mode 100644 python/testData/multipleArgumentsCompletion/skipDefaultValueIfNotFromFunctionContext.after.py create mode 100644 python/testData/multipleArgumentsCompletion/skipDefaultValueIfNotFromFunctionContext.py create mode 100644 python/testData/multipleArgumentsCompletion/slashAndSingleStarParameter.after.py create mode 100644 python/testData/multipleArgumentsCompletion/slashAndSingleStarParameter.py create mode 100644 python/testData/multipleArgumentsCompletion/slashParameter.after.py create mode 100644 python/testData/multipleArgumentsCompletion/slashParameter.py create mode 100644 python/testData/multipleArgumentsCompletion/suggestArgumentsForParametersWithDefaultValue.after.py create mode 100644 python/testData/multipleArgumentsCompletion/suggestArgumentsForParametersWithDefaultValue.py create mode 100644 python/testSrc/com/jetbrains/python/PyMultipleArgumentsCompletionTest.kt diff --git a/python/python-psi-impl/resources/META-INF/PythonPsiImpl.xml b/python/python-psi-impl/resources/META-INF/PythonPsiImpl.xml index 91d95c9b1f13..7ce64b0370e7 100644 --- a/python/python-psi-impl/resources/META-INF/PythonPsiImpl.xml +++ b/python/python-psi-impl/resources/META-INF/PythonPsiImpl.xml @@ -109,6 +109,8 @@ implementationClass="com.jetbrains.python.codeInsight.completion.PyModulePackageCompletionContributor"/> + diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyMultipleArgumentsCompletionContributor.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyMultipleArgumentsCompletionContributor.kt new file mode 100644 index 000000000000..019110c394ca --- /dev/null +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyMultipleArgumentsCompletionContributor.kt @@ -0,0 +1,129 @@ +package com.jetbrains.python.codeInsight.completion + +import com.intellij.codeInsight.completion.* +import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.openapi.util.Key +import com.intellij.patterns.PlatformPatterns +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.PlatformIcons +import com.intellij.util.ProcessingContext +import com.intellij.util.containers.ContainerUtil +import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil +import com.jetbrains.python.extensions.inArgumentList +import com.jetbrains.python.psi.* +import com.jetbrains.python.psi.impl.PyPsiUtils +import com.jetbrains.python.psi.resolve.PyResolveContext +import com.jetbrains.python.psi.types.PyCallableParameter + +class PyMultipleArgumentsCompletionContributor: CompletionContributor() { + init { + extend(CompletionType.BASIC, PlatformPatterns.psiElement().inArgumentList(), MyCompletionProvider) + } + + private object MyCompletionProvider : CompletionProvider() { + override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) { + val position = parameters.position + val argumentIndex = getArgumentIndex(position) ?: return + + val call = PsiTreeUtil.getParentOfType(position, PyCallExpression::class.java) ?: return + val typeEvalContext = parameters.getTypeEvalContext() + val resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext) + val callableTypes = call.multiResolveCallee(resolveContext) + if (callableTypes.isEmpty()) return + + val scopeOwner = ScopeUtil.getScopeOwner(position) ?: return + val names = collectNames(scopeOwner, position) + + callableTypes.forEach { callableType -> + val callableParameters = callableType.getParameters(typeEvalContext) + if (callableParameters == null || callableParameters.any { it.isKeywordContainer || it.isPositionalContainer }) return@forEach + + val unfilledParameters = ContainerUtil.subList(callableParameters, argumentIndex + callableType.implicitOffset) + val variables = collectVariablesToComplete(unfilledParameters, names) + if (variables.size > 1) { + result.addElement(createParametersLookupElement(variables, call)) + } + } + } + } + + companion object { + val MULTIPLE_ARGUMENTS_VARIANT_KEY: Key = Key.create("py.multiple.arguments.completion.variant") + + private fun getArgumentIndex(position: PsiElement): Int? { + val argumentList = PsiTreeUtil.getParentOfType(position, PyArgumentList::class.java) ?: return null + if (argumentList.arguments.any { it is PyKeywordArgument || it is PyStarArgument }) return null + if (!PsiTreeUtil.isAncestor(argumentList.arguments.last(), position, false)) return null + return argumentList.arguments.size - 1 + } + + private fun createParametersLookupElement(variables: List, call: PyCallExpression): LookupElement { + return LookupElementBuilder.create(variables.joinToString(", ")) + .withIcon(PlatformIcons.VARIABLE_ICON) + .withInsertHandler(PyMultipleArgumentsInsertHandler(call)) + .apply { + putUserData(MULTIPLE_ARGUMENTS_VARIANT_KEY, true) + } + } + + private fun collectVariablesToComplete(parameters: List, argumentsNames: Set): List { + val variables = mutableListOf() + var keywordsOnlyFlag = false + + for (parameter in parameters) { + if (parameter.parameter is PySlashParameter) continue + if (parameter.parameter is PySingleStarParameter) { + keywordsOnlyFlag = true + continue + } + + val paramName = parameter.name ?: return emptyList() + if (paramName in argumentsNames) { + if (!keywordsOnlyFlag) { + variables.add(paramName) + } + else { + variables.add("$paramName=$paramName") + } + } + else { + if (!parameter.hasDefaultValue()) return emptyList() + } + } + + return variables + } + + private fun collectNames(scope: ScopeOwner, position: PsiElement): Set = + ControlFlowCache.getScope(scope).namedElements + .asSequence() + .filter { element -> + PsiTreeUtil.getParentOfType(element, PyListCompExpression::class.java) ?.let { listComp -> + PsiTreeUtil.isAncestor(listComp.resultExpression, position, false) + } ?: PyPsiUtils.isBefore(element, position) + } + .mapNotNull { it.name } + .toSet() + } +} + +class PyMultipleArgumentsInsertHandler(private val call: PyCallExpression): ParenthesesInsertHandler() { + override fun placeCaretInsideParentheses(context: InsertionContext?, item: LookupElement?): Boolean = false + + override fun handleInsert(context: InsertionContext, item: LookupElement) { + val editor = context.editor + context.commitDocument() + if (call.argumentList?.closingParen == null) { + editor.document.insertString(context.tailOffset, ")") + editor.caretModel.moveToOffset(context.tailOffset) + } + else { + editor.caretModel.moveToOffset(context.tailOffset + 1) + } + } +} \ No newline at end of file diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PythonCompletionWeigher.java b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PythonCompletionWeigher.java index a1bfd7cec747..0481471196a7 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PythonCompletionWeigher.java +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PythonCompletionWeigher.java @@ -37,6 +37,7 @@ import org.jetbrains.annotations.NotNull; public class PythonCompletionWeigher extends CompletionWeigher { public static final int PRIORITY_WEIGHT = 5; + public static final int WEIGHT_FOR_MULTIPLE_ARGUMENTS = 5; public static final int WEIGHT_FOR_KEYWORDS = 0; private static final Logger LOG = Logger.getInstance(PythonCompletionWeigher.class); public static final String COLLECTION_KEY = "dict key"; @@ -71,6 +72,10 @@ public class PythonCompletionWeigher extends CompletionWeigher { return WEIGHT_FOR_KEYWORDS; } + if (element.getUserData(PyMultipleArgumentsCompletionContributor.Companion.getMULTIPLE_ARGUMENTS_VARIANT_KEY()) != null) { + return WEIGHT_FOR_MULTIPLE_ARGUMENTS; + } + return PyCompletionUtilsKt.FALLBACK_WEIGHT; } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/extensions/CaptureExt.kt b/python/python-psi-impl/src/com/jetbrains/python/extensions/CaptureExt.kt index 1ce57662eda6..0f5ab057bdbd 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/extensions/CaptureExt.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/extensions/CaptureExt.kt @@ -5,6 +5,7 @@ import com.intellij.patterns.PlatformPatterns.psiElement import com.intellij.patterns.PsiElementPattern import com.intellij.psi.PsiElement import com.jetbrains.python.PythonLanguage +import com.jetbrains.python.psi.PyArgumentList import com.jetbrains.python.psi.PyClass import com.jetbrains.python.psi.PyFunction import com.jetbrains.python.psi.PyParameterList @@ -28,3 +29,6 @@ fun PsiElementPattern.Capture<*>.afterDefInFunction(): PsiElementPattern.Capture fun PsiElementPattern.Capture<*>.inParameterList(): PsiElementPattern.Capture = withLanguage(PythonLanguage.getInstance()).and(psiElement().inside(PyParameterList::class.java)) + +fun PsiElementPattern.Capture<*>.inArgumentList(): PsiElementPattern.Capture = + withLanguage(PythonLanguage.getInstance()).and(psiElement().inside(PyArgumentList::class.java)) diff --git a/python/src/com/jetbrains/python/codeInsight/mlcompletion/PyElementFeatureProvider.kt b/python/src/com/jetbrains/python/codeInsight/mlcompletion/PyElementFeatureProvider.kt index 383d6c223f88..63b1c5b41766 100644 --- a/python/src/com/jetbrains/python/codeInsight/mlcompletion/PyElementFeatureProvider.kt +++ b/python/src/com/jetbrains/python/codeInsight/mlcompletion/PyElementFeatureProvider.kt @@ -6,6 +6,7 @@ import com.intellij.codeInsight.completion.ml.ContextFeatures import com.intellij.codeInsight.completion.ml.ElementFeatureProvider import com.intellij.codeInsight.completion.ml.MLFeatureValue import com.intellij.codeInsight.lookup.LookupElement +import com.jetbrains.python.codeInsight.completion.PyMultipleArgumentsCompletionContributor import com.jetbrains.python.codeInsight.mlcompletion.prev2calls.PyPrevCallsCompletionFeatures import com.jetbrains.python.psi.PyParameter @@ -100,6 +101,10 @@ class PyElementFeatureProvider : ElementFeatureProvider { }} } + element.getUserData(PyMultipleArgumentsCompletionContributor.MULTIPLE_ARGUMENTS_VARIANT_KEY)?.let { + result["is_multiple_arguments"] = MLFeatureValue.binary(true) + } + return result } } \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/argumentParameter.after.py b/python/testData/multipleArgumentsCompletion/argumentParameter.after.py new file mode 100644 index 000000000000..2735ed0ef575 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/argumentParameter.after.py @@ -0,0 +1,7 @@ +def foo(bar, baz): + pass + + +def main(bar): + for baz in []: + foo(bar, baz) \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/argumentParameter.py b/python/testData/multipleArgumentsCompletion/argumentParameter.py new file mode 100644 index 000000000000..ae4a4d2fd7d8 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/argumentParameter.py @@ -0,0 +1,7 @@ +def foo(bar, baz): + pass + + +def main(bar): + for baz in []: + foo() \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/classConstructor.after.py b/python/testData/multipleArgumentsCompletion/classConstructor.after.py new file mode 100644 index 000000000000..dea59f8a1779 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/classConstructor.after.py @@ -0,0 +1,12 @@ +class Foo: + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + + +def foo(): + x = 1 + y = 2 + z = 3 + return Foo(x, y, z) \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/classConstructor.py b/python/testData/multipleArgumentsCompletion/classConstructor.py new file mode 100644 index 000000000000..0f4aced7003c --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/classConstructor.py @@ -0,0 +1,12 @@ +class Foo: + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + + +def foo(): + x = 1 + y = 2 + z = 3 + return Foo() \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/classMember.after.py b/python/testData/multipleArgumentsCompletion/classMember.after.py new file mode 100644 index 000000000000..239ba80905f7 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/classMember.after.py @@ -0,0 +1,9 @@ +class Foo: + def foo(self, x, y, z): + pass + + def bar(self): + x = 1 + y = 2 + z = 3 + self.foo(x, y, z) \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/classMember.py b/python/testData/multipleArgumentsCompletion/classMember.py new file mode 100644 index 000000000000..645510aedb47 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/classMember.py @@ -0,0 +1,9 @@ +class Foo: + def foo(self, x, y, z): + pass + + def bar(self): + x = 1 + y = 2 + z = 3 + self.foo() \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/functionWithThreeArgsTwoSuggested.after.py b/python/testData/multipleArgumentsCompletion/functionWithThreeArgsTwoSuggested.after.py new file mode 100644 index 000000000000..fd0dd07a7d1e --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/functionWithThreeArgsTwoSuggested.after.py @@ -0,0 +1,8 @@ +def foo(x, y, z): + pass + + +z = 22 +x = 42 +y = 100500 +foo(x, y, z) diff --git a/python/testData/multipleArgumentsCompletion/functionWithThreeArgsTwoSuggested.py b/python/testData/multipleArgumentsCompletion/functionWithThreeArgsTwoSuggested.py new file mode 100644 index 000000000000..19210da25cfd --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/functionWithThreeArgsTwoSuggested.py @@ -0,0 +1,8 @@ +def foo(x, y, z): + pass + + +z = 22 +x = 42 +y = 100500 +foo(x, diff --git a/python/testData/multipleArgumentsCompletion/functionWithTwoArgs.after.py b/python/testData/multipleArgumentsCompletion/functionWithTwoArgs.after.py new file mode 100644 index 000000000000..27e173dfef95 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/functionWithTwoArgs.after.py @@ -0,0 +1,7 @@ +def foo(x, y): + pass + + +x = 42 +y = 100500 +foo(x, y) diff --git a/python/testData/multipleArgumentsCompletion/functionWithTwoArgs.py b/python/testData/multipleArgumentsCompletion/functionWithTwoArgs.py new file mode 100644 index 000000000000..612a78d1616f --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/functionWithTwoArgs.py @@ -0,0 +1,7 @@ +def foo(x, y): + pass + + +x = 42 +y = 100500 +foo() diff --git a/python/testData/multipleArgumentsCompletion/listComprehension.after.py b/python/testData/multipleArgumentsCompletion/listComprehension.after.py new file mode 100644 index 000000000000..0fb7408e2231 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/listComprehension.after.py @@ -0,0 +1,6 @@ +def foo(bar, baz): + pass + + +def main(): + return [foo(bar, baz) for bar, baz in []] \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/listComprehension.py b/python/testData/multipleArgumentsCompletion/listComprehension.py new file mode 100644 index 000000000000..a7703deb8e41 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/listComprehension.py @@ -0,0 +1,6 @@ +def foo(bar, baz): + pass + + +def main(): + return [foo() for bar, baz in []] \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/notSuggestIfHaveArgumentsRight.py b/python/testData/multipleArgumentsCompletion/notSuggestIfHaveArgumentsRight.py new file mode 100644 index 000000000000..7d322e1d6c74 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestIfHaveArgumentsRight.py @@ -0,0 +1,8 @@ +def foo(x, y, z): + pass + + +x = 42 +y = 100500 +z = 42 +foo(x, , z) diff --git a/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArguments.py b/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArguments.py new file mode 100644 index 000000000000..acf8218852c2 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArguments.py @@ -0,0 +1,7 @@ +def foo(x, y, z): + pass + + +x = 42 +y = 100500 +foo() diff --git a/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsBeforeCaret.py b/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsBeforeCaret.py new file mode 100644 index 000000000000..5bac79e403af --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsBeforeCaret.py @@ -0,0 +1,8 @@ +def foo(x, y, z): + pass + + +x = 42 +y = 100500 +foo() +z = 33 diff --git a/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsInTheScopeOfFunction.py b/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsInTheScopeOfFunction.py new file mode 100644 index 000000000000..7b547e28301c --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsInTheScopeOfFunction.py @@ -0,0 +1,9 @@ +def foo(x, y, z): + pass + + +z = 33 +def bar(): + x = 42 + y = 100500 + foo() diff --git a/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsInTheScopeOfNestedFunction.py b/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsInTheScopeOfNestedFunction.py new file mode 100644 index 000000000000..217e4e23b637 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestIfNotEnoughArgumentsInTheScopeOfNestedFunction.py @@ -0,0 +1,10 @@ +def foo(x, y, z): + pass + + +def bar(): + z = 33 + def baz(): + x = 42 + y = 100500 + foo() diff --git a/python/testData/multipleArgumentsCompletion/notSuggestIfOnlyOneVariable.py b/python/testData/multipleArgumentsCompletion/notSuggestIfOnlyOneVariable.py new file mode 100644 index 000000000000..e5a43300c799 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestIfOnlyOneVariable.py @@ -0,0 +1,5 @@ +def foo(x, y=42) + pass + +x = 42 +foo() \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/notSuggestIfTargetOutOfScopeFunction.py b/python/testData/multipleArgumentsCompletion/notSuggestIfTargetOutOfScopeFunction.py new file mode 100644 index 000000000000..fd8ebb0da99e --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestIfTargetOutOfScopeFunction.py @@ -0,0 +1,7 @@ +def foo(x, y, z): + z = 42 + + +x = 42 +y = 100500 +foo() diff --git a/python/testData/multipleArgumentsCompletion/notSuggestInKeywordArgument.py b/python/testData/multipleArgumentsCompletion/notSuggestInKeywordArgument.py new file mode 100644 index 000000000000..ee61f9896421 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestInKeywordArgument.py @@ -0,0 +1,8 @@ +def foo(x, y, z): + pass + + +x = 42 +y = 100500 +z = 42 +foo(x= diff --git a/python/testData/multipleArgumentsCompletion/notSuggestKeywordContainer.py b/python/testData/multipleArgumentsCompletion/notSuggestKeywordContainer.py new file mode 100644 index 000000000000..88876b7af2b7 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestKeywordContainer.py @@ -0,0 +1,7 @@ +def foo(x, **y): + pass + + +x = 42 +y = 22 +foo( \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/notSuggestPositionalContainer.py b/python/testData/multipleArgumentsCompletion/notSuggestPositionalContainer.py new file mode 100644 index 000000000000..6730acf9a3e4 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/notSuggestPositionalContainer.py @@ -0,0 +1,7 @@ +def foo(x, *y): + pass + + +x = 42 +y = 22 +foo( \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/overloads.py b/python/testData/multipleArgumentsCompletion/overloads.py new file mode 100644 index 000000000000..04d70c90ed5d --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/overloads.py @@ -0,0 +1,24 @@ +from typing import overload, Any + + +@overload +def bar(a: int, b: int) -> None: + ... + + +@overload +def bar(c: str, d: str) -> None: + ... + + +def bar(*args: Any, **kwargs: Any) -> None: + ... + + +def foo(): + a = 1 + b = 2 + c = 3 + d = 4 + + bar() \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/singleStarParameter.after.py b/python/testData/multipleArgumentsCompletion/singleStarParameter.after.py new file mode 100644 index 000000000000..f93b1bda6b5c --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/singleStarParameter.after.py @@ -0,0 +1,8 @@ +def foo(a, *, b): + print(a, b) + + +def egg(): + a = 1 + b = 2 + foo(a, b=b) \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/singleStarParameter.py b/python/testData/multipleArgumentsCompletion/singleStarParameter.py new file mode 100644 index 000000000000..b09b26891bcd --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/singleStarParameter.py @@ -0,0 +1,8 @@ +def foo(a, *, b): + print(a, b) + + +def egg(): + a = 1 + b = 2 + foo() \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/skipArgumentsForParametersWithDefaultValue.after.py b/python/testData/multipleArgumentsCompletion/skipArgumentsForParametersWithDefaultValue.after.py new file mode 100644 index 000000000000..45a3c78a6620 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/skipArgumentsForParametersWithDefaultValue.after.py @@ -0,0 +1,7 @@ +def foo(x, y, z=42): + pass + + +x = 42 +y = 100500 +foo(x, y) diff --git a/python/testData/multipleArgumentsCompletion/skipArgumentsForParametersWithDefaultValue.py b/python/testData/multipleArgumentsCompletion/skipArgumentsForParametersWithDefaultValue.py new file mode 100644 index 000000000000..f189dcde6b06 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/skipArgumentsForParametersWithDefaultValue.py @@ -0,0 +1,7 @@ +def foo(x, y, z=42): + pass + + +x = 42 +y = 100500 +foo( diff --git a/python/testData/multipleArgumentsCompletion/skipDefaultValueIfNotFromFunctionContext.after.py b/python/testData/multipleArgumentsCompletion/skipDefaultValueIfNotFromFunctionContext.after.py new file mode 100644 index 000000000000..06246035485f --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/skipDefaultValueIfNotFromFunctionContext.after.py @@ -0,0 +1,9 @@ +def foo(x, y, z=42): + pass + + +z = 42 +def bar(): + x = 42 + y = 100500 + foo(x, y) diff --git a/python/testData/multipleArgumentsCompletion/skipDefaultValueIfNotFromFunctionContext.py b/python/testData/multipleArgumentsCompletion/skipDefaultValueIfNotFromFunctionContext.py new file mode 100644 index 000000000000..1e177b9d460c --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/skipDefaultValueIfNotFromFunctionContext.py @@ -0,0 +1,9 @@ +def foo(x, y, z=42): + pass + + +z = 42 +def bar(): + x = 42 + y = 100500 + foo( diff --git a/python/testData/multipleArgumentsCompletion/slashAndSingleStarParameter.after.py b/python/testData/multipleArgumentsCompletion/slashAndSingleStarParameter.after.py new file mode 100644 index 000000000000..adb3678b9743 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/slashAndSingleStarParameter.after.py @@ -0,0 +1,9 @@ +def foo(a, /, b, *, c): + print(a, b, c) + + +def egg(): + a = 1 + b = 2 + c = 3 + foo(a, b, c=c) \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/slashAndSingleStarParameter.py b/python/testData/multipleArgumentsCompletion/slashAndSingleStarParameter.py new file mode 100644 index 000000000000..5ac9e113fd39 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/slashAndSingleStarParameter.py @@ -0,0 +1,9 @@ +def foo(a, /, b, *, c): + print(a, b, c) + + +def egg(): + a = 1 + b = 2 + c = 3 + foo( \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/slashParameter.after.py b/python/testData/multipleArgumentsCompletion/slashParameter.after.py new file mode 100644 index 000000000000..6d216f6e46ef --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/slashParameter.after.py @@ -0,0 +1,8 @@ +def foo(a, /, b): + print(a, b) + + +def egg(): + a = 1 + b = 2 + foo(a, b) \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/slashParameter.py b/python/testData/multipleArgumentsCompletion/slashParameter.py new file mode 100644 index 000000000000..5df3ee05acc8 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/slashParameter.py @@ -0,0 +1,8 @@ +def foo(a, /, b): + print(a, b) + + +def egg(): + a = 1 + b = 2 + foo( \ No newline at end of file diff --git a/python/testData/multipleArgumentsCompletion/suggestArgumentsForParametersWithDefaultValue.after.py b/python/testData/multipleArgumentsCompletion/suggestArgumentsForParametersWithDefaultValue.after.py new file mode 100644 index 000000000000..8b61c27337f5 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/suggestArgumentsForParametersWithDefaultValue.after.py @@ -0,0 +1,8 @@ +def foo(x, y, z=42): + pass + + +x = 42 +y = 100500 +z = 42 +foo(x, y, z) diff --git a/python/testData/multipleArgumentsCompletion/suggestArgumentsForParametersWithDefaultValue.py b/python/testData/multipleArgumentsCompletion/suggestArgumentsForParametersWithDefaultValue.py new file mode 100644 index 000000000000..647e7e1f4f15 --- /dev/null +++ b/python/testData/multipleArgumentsCompletion/suggestArgumentsForParametersWithDefaultValue.py @@ -0,0 +1,8 @@ +def foo(x, y, z=42): + pass + + +x = 42 +y = 100500 +z = 42 +foo( diff --git a/python/testSrc/com/jetbrains/python/PyMultipleArgumentsCompletionTest.kt b/python/testSrc/com/jetbrains/python/PyMultipleArgumentsCompletionTest.kt new file mode 100644 index 000000000000..dab8c39d7b08 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/PyMultipleArgumentsCompletionTest.kt @@ -0,0 +1,117 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python + +import com.intellij.codeInsight.lookup.Lookup +import com.jetbrains.python.fixtures.PyTestCase + +class PyMultipleArgumentsCompletionTest: PyTestCase() { + fun testFunctionWithTwoArgs() { + doTestVariantsContainFinished("x, y") + } + + fun testFunctionWithThreeArgsTwoSuggested() { + doTestVariantsContainFinished("y, z") + } + + fun testSuggestArgumentsForParametersWithDefaultValue() { + doTestVariantsContainFinished("x, y, z") + } + + fun testSkipArgumentsForParametersWithDefaultValue() { + doTestVariantsContainFinished("x, y") + } + + fun testSkipDefaultValueIfNotFromFunctionContext() { + doTestVariantsContainFinished("x, y") + } + + fun testClassConstructor() { + doTestVariantsContainFinished("x, y, z") + } + + fun testClassMember() { + doTestVariantsContainFinished("x, y, z") + } + + fun testArgumentParameter() { + doTestVariantsContainFinished("bar, baz") + } + + fun testListComprehension() { + doTestVariantsContainFinished("bar, baz") + } + + fun testOverloads() { + doTestVariantsContain("a, b", "c, d") + } + + fun testSingleStarParameter() { + doTestVariantsContainFinished("a, b=b") + } + + fun testSlashParameter() { + doTestVariantsContainFinished("a, b") + } + + fun testSlashAndSingleStarParameter() { + doTestVariantsContainFinished("a, b, c=c") + } + + fun testNotSuggestIfNotEnoughArguments() { + doTestVariantsNotContain("x, y", "x, y, z") + } + + fun testNotSuggestIfNotEnoughArgumentsBeforeCaret() { + doTestVariantsNotContain("x, y", "x, y, z") + } + + fun testNotSuggestIfNotEnoughArgumentsInTheScopeOfFunction() { + doTestVariantsNotContain("x, y", "x, y, z") + } + + fun testNotSuggestIfNotEnoughArgumentsInTheScopeOfNestedFunction() { + doTestVariantsNotContain("x, y", "x, y, z") + } + + fun testNotSuggestIfTargetOutOfScopeFunction() { + doTestVariantsNotContain("x, y", "x, y, z") + } + + fun testNotSuggestIfHaveArgumentsRight() { + doTestVariantsNotContain("y, z") + } + + fun testNotSuggestInKeywordArgument() { + doTestVariantsNotContain("x, y, z", "y, z") + } + + fun testNotSuggestKeywordContainer() { + doTestVariantsNotContain("x, y") + } + + fun testNotSuggestPositionalContainer() { + doTestVariantsNotContain("x, y") + } + + fun testNotSuggestIfOnlyOneVariable() { + assertEquals(1, doTestByTestName().count { it == "x" }) + } + + private fun doTestByTestName(): List { + val testName = "multipleArgumentsCompletion/${getTestName(true)}" + myFixture.configureByFile("$testName.py") + myFixture.completeBasic() + return myFixture.lookupElementStrings!! + } + + private fun doTestVariantsContain(vararg elements: String) = assertContainsElements(doTestByTestName(), *elements) + + private fun doTestVariantsContainFinished(element: String) { + doTestVariantsContain(element) + myFixture.lookup.currentItem = myFixture.lookupElements.find { it.lookupString == element } + myFixture.finishLookup(Lookup.NORMAL_SELECT_CHAR) + myFixture.checkResultByFile("multipleArgumentsCompletion/${getTestName(true)}.after.py") + } + + private fun doTestVariantsNotContain(vararg elements: String) = assertDoesntContain(doTestByTestName(), *elements) +} \ No newline at end of file