From 6ad3c2af623d0491d94dc161c4b9f6f76dac631d Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 4 Aug 2025 11:58:08 +0300 Subject: [PATCH] PY-83039 Don't trigger PyLiteralTypeCompletionContributor in cases like `x = y.foo` It should be activated only for the simplest cases when the caret is either inside an immediate string literal or its prefix is an unqualified reference expression. It makes little sense trying to detect if something like the literal string `"y.foobar"` is a possible value for `x`. It's a relatively heavy completion contributor. It starts evaluating the type of `x` flow-sensitively, analyzing all preceding function calls to take into account `NoReturn`. It affects common workflows like typing out `df = pd.` to create a new Pandas dataframe. GitOrigin-RevId: f17fe2ce86ee100a3480a574c7f57a1bd67ec2d8 --- .../PyLiteralTypeCompletionContributor.kt | 19 +++++++++++++++++++ ...tsOnQualifiedReferenceInAssignmentValue.py | 5 +++++ ...iantsOnQualifiedReferenceInCallArgument.py | 8 ++++++++ .../python/PyLiteralTypeCompletionTest.kt | 16 ++++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 python/testData/completion/literalType/noLiteralVariantsOnQualifiedReferenceInAssignmentValue.py create mode 100644 python/testData/completion/literalType/noLiteralVariantsOnQualifiedReferenceInCallArgument.py diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyLiteralTypeCompletionContributor.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyLiteralTypeCompletionContributor.kt index bd8f511fbff9..0556134416ba 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyLiteralTypeCompletionContributor.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyLiteralTypeCompletionContributor.kt @@ -18,6 +18,24 @@ import com.jetbrains.python.psi.types.PyType import com.jetbrains.python.psi.types.PyTypeUtil import com.jetbrains.python.psi.types.TypeEvalContext +/** + * Provides literal type variants in the following cases: + * ```python + * x: Literal["foo", "bar"] + * x = + * x = fo + * x = "fo" + * ``` + * + * or + * + * ```python + * def f(x: Literal["foo", "bar"]): ... + * f() + * f(fo) + * f("fo") + * ``` + */ class PyLiteralTypeCompletionContributor : CompletionContributor() { init { extend(CompletionType.BASIC, psiElement(), PyLiteralTypeCompletionProvider()) @@ -27,6 +45,7 @@ class PyLiteralTypeCompletionContributor : CompletionContributor() { private class PyLiteralTypeCompletionProvider : CompletionProvider() { override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) { val position = parameters.position.parent as? PyExpression ?: return + if (!(position is PyStringLiteralExpression || position is PyReferenceExpression && !position.isQualified)) return val typeEvalContext = TypeEvalContext.codeCompletion(position.project, position.containingFile) val mappedParameters = position.getMappedParameters(PyResolveContext.defaultContext(typeEvalContext)) diff --git a/python/testData/completion/literalType/noLiteralVariantsOnQualifiedReferenceInAssignmentValue.py b/python/testData/completion/literalType/noLiteralVariantsOnQualifiedReferenceInAssignmentValue.py new file mode 100644 index 000000000000..e7e1dba15dc6 --- /dev/null +++ b/python/testData/completion/literalType/noLiteralVariantsOnQualifiedReferenceInAssignmentValue.py @@ -0,0 +1,5 @@ +from typing import Literal + +x: Literal["upper", "lower"] +y = "" +x = y.upp \ No newline at end of file diff --git a/python/testData/completion/literalType/noLiteralVariantsOnQualifiedReferenceInCallArgument.py b/python/testData/completion/literalType/noLiteralVariantsOnQualifiedReferenceInCallArgument.py new file mode 100644 index 000000000000..1c45366b3f9d --- /dev/null +++ b/python/testData/completion/literalType/noLiteralVariantsOnQualifiedReferenceInCallArgument.py @@ -0,0 +1,8 @@ +from typing import Literal + + +def f(x: Literal["upper", "lower"]): + pass + +y = "" +f(y.up) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyLiteralTypeCompletionTest.kt b/python/testSrc/com/jetbrains/python/PyLiteralTypeCompletionTest.kt index 0810b1281ae4..94a408639216 100644 --- a/python/testSrc/com/jetbrains/python/PyLiteralTypeCompletionTest.kt +++ b/python/testSrc/com/jetbrains/python/PyLiteralTypeCompletionTest.kt @@ -75,6 +75,16 @@ class PyLiteralTypeCompletionTest : PyTestCase() { myFixture.testCompletionVariants("nestedArgumentLists.py") } + // PY-83039 + fun testNoLiteralVariantsOnQualifiedReferenceInAssignmentValue() { + doTestCompletionVariantsDoesNotContain("noLiteralVariantsOnQualifiedReferenceInAssignmentValue.py", "\"upper\"") + } + + // PY-83039 + fun testNoLiteralVariantsOnQualifiedReferenceInCallArgument() { + doTestCompletionVariantsDoesNotContain("noLiteralVariantsOnQualifiedReferenceInCallArgument.py", "\"upper\"") + } + override fun getTestDataPath(): String { return super.getTestDataPath() + "/completion/literalType" } @@ -84,4 +94,10 @@ class PyLiteralTypeCompletionTest : PyTestCase() { assertNotNull(result) assertContainsElements(result!!, *items) } + + private fun doTestCompletionVariantsDoesNotContain(fileBefore: String, vararg items: String) { + val result = myFixture.getCompletionVariants(fileBefore) + assertNotNull(result) + assertDoesntContain(result!!, *items) + } } \ No newline at end of file