From 4d95ecde4e978c623bf978c7056327c1cf839b7a Mon Sep 17 00:00:00 2001 From: Morgan Bartholomew Date: Tue, 3 Mar 2026 13:51:51 +1000 Subject: [PATCH] PY-57621 type inference: infer tuples with literal types (cherry picked from commit d3f6711e4d187fac8b426eb1d5ed75def4932b3b) GitOrigin-RevId: cc327f405e9a133425bcfbb091e22f1e267b6383 --- .../stdlib/PyStdlibTypeProvider.java | 7 +++ .../python/psi/impl/PyTupleExpressionImpl.kt | 3 +- .../python/psi/types/PyLiteralType.kt | 2 +- .../python/psi/types/PyTypeChecker.kt | 7 ++- .../jetbrains/python/psi/types/PyTypeUtil.kt | 5 +++ .../tupleNonCallable.py | 2 +- .../PyStringFormatInspection/Basic.py | 6 +-- ...ypeMismatchOnElementsOfTupleDeclaration.py | 2 +- .../TupleMultiplication.py | 2 +- .../ChainedComparisons.py | 2 +- .../HomogeneousTuples.py | 2 +- .../identifyUnhashable.py | 2 +- .../annotationGenericBuiltinTuple_after.py | 4 +- ...tionLocalUnpackedAssignmentTarget_after.py | 5 ++- .../annotationTupleType_after.py | 4 +- ...ntComplexUnpackedAssignmentTarget_after.py | 5 ++- ...mentLocalUnpackedAssignmentTarget_after.py | 5 ++- .../TupleTypeIsRenderedLowercased.html | 2 +- .../com/jetbrains/python/Py3TypeTest.java | 9 +++- .../python/PyTypeConversionTest.java | 4 +- .../com/jetbrains/python/PyTypeTest.java | 44 ++++++++++--------- .../com/jetbrains/python/PyTypingTest.java | 13 ++++++ .../Py3TypeCheckerInspectionTest.java | 26 ++++++++--- .../inspections/PyPatternInspectionTest.java | 6 +-- .../testing/PyTestFixtureResolvingTest.kt | 4 +- 25 files changed, 121 insertions(+), 52 deletions(-) diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index b42a1404fcd2..eef647287e74 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -244,6 +244,13 @@ public final class PyStdlibTypeProvider extends PyTypeProviderBase { if (value == null) return null; PyType type = context.getType(value); + if (type instanceof PyTupleType tupleType) { + // until heterogeneous enums are supported, we must widen tuple types + type = PyTupleType.create( + tupleType.getDeclarationElement(), + ContainerUtil.map(tupleType.getElementTypes(), t -> PyLiteralType.upcastLiteralToClass(t)) + ); + } return getEnumAttributeInfo(enumClass, type, context); } else { diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyTupleExpressionImpl.kt b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyTupleExpressionImpl.kt index bee323ae99b4..b1874d928786 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyTupleExpressionImpl.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyTupleExpressionImpl.kt @@ -10,6 +10,7 @@ import com.jetbrains.python.psi.PyTupleExpression import com.jetbrains.python.psi.types.PyTupleType import com.jetbrains.python.psi.types.PyType import com.jetbrains.python.psi.types.TypeEvalContext +import com.jetbrains.python.psi.types.getLiteralType class PyTupleExpressionImpl(astNode: ASTNode) : PySequenceExpressionImpl(astNode), PyTupleExpression { override fun acceptPyVisitor(pyVisitor: PyElementVisitor) { @@ -19,7 +20,7 @@ class PyTupleExpressionImpl(astNode: ASTNode) : PySequenceExpressionImpl(astNode override fun getType(context: TypeEvalContext, key: TypeEvalContext.Key): PyType? { return PyTupleType.create( this, - elements.map { context.getType(it) } + elements.map { it.getLiteralType(context) ?: context.getType(it) } ) } diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyLiteralType.kt b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyLiteralType.kt index bf603722d608..fb0b0e6c4121 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyLiteralType.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyLiteralType.kt @@ -257,7 +257,7 @@ class PyLiteralType private constructor(cls: PyClass, val expression: PyExpressi return PyLiteralStringType.create(expression) } } - return getLiteralType(expression, context) + return expression.getLiteralType(context) } private fun literalType(expression: PyExpression, context: TypeEvalContext, index: Boolean): PyLiteralType? { diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeChecker.kt b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeChecker.kt index 2bd4ea2735a5..daa76020e425 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeChecker.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeChecker.kt @@ -436,7 +436,12 @@ object PyTypeChecker { context ) } - context.mySubstitutions.putTypeVarTuple(expected as PyTypeVarTupleType, actual, KeyImpl) + val normalizedActual = + if (actual is PyUnpackedTupleType) + // TODO: consider how widening should work with more complex types like: `tuple[Sequence[Literal[1]]` + PyUnpackedTupleTypeImpl(actual.elementTypes.map { PyLiteralType.upcastLiteralToClass(it) }, actual.isUnbound) + else actual + context.mySubstitutions.putTypeVarTuple(expected as PyTypeVarTupleType, normalizedActual, KeyImpl) } return true } diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeUtil.kt b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeUtil.kt index b32b600dbee0..67047b29288a 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeUtil.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeUtil.kt @@ -19,6 +19,7 @@ import com.intellij.openapi.util.Key import com.intellij.openapi.util.Ref import com.intellij.openapi.util.UserDataHolder import com.intellij.psi.PsiElement +import com.jetbrains.python.psi.PyExpression import com.jetbrains.python.psi.PyPsiFacade import com.jetbrains.python.psi.impl.PyBuiltinCache import com.jetbrains.python.psi.types.PyRecursiveTypeVisitor.PyTypeTraverser @@ -368,3 +369,7 @@ val PyType?.isUnknown: Boolean PyAnyType.validate(this) return if (PyAnyType.isEnabled) this is PyAnyType.Unknown else this == null } + +@ApiStatus.Internal +fun PyExpression.getLiteralType(context: TypeEvalContext): PyType? = + PyLiteralType.getLiteralType(this, context) diff --git a/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py b/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py index 3433394d67c6..e885793e7f27 100644 --- a/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py +++ b/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py @@ -1 +1 @@ -(1,2)() +(1,2)() diff --git a/python/testData/inspections/PyStringFormatInspection/Basic.py b/python/testData/inspections/PyStringFormatInspection/Basic.py index 1ede559eeaaf..4eceff766a07 100644 --- a/python/testData/inspections/PyStringFormatInspection/Basic.py +++ b/python/testData/inspections/PyStringFormatInspection/Basic.py @@ -100,9 +100,9 @@ print '%d, %d, %d, %d' % my # PY-12801 print '%d %s' % ((42,) + ('spam',)) -print '%d %s' % (('ham',) + ('spam',)) -print '%d %s' % ((42,) + ()) -print '%d' % ((42,) + ('spam',)) +print '%d %s' % (('ham',) + ('spam',)) +print '%d %s' % ((42,) + ()) +print '%d' % ((42,) + ('spam',)) # PY-11274 import collections diff --git a/python/testData/inspections/PyStringFormatInspection/NoTypeMismatchOnElementsOfTupleDeclaration.py b/python/testData/inspections/PyStringFormatInspection/NoTypeMismatchOnElementsOfTupleDeclaration.py index 02f6f17e074b..b0aa11b76a36 100644 --- a/python/testData/inspections/PyStringFormatInspection/NoTypeMismatchOnElementsOfTupleDeclaration.py +++ b/python/testData/inspections/PyStringFormatInspection/NoTypeMismatchOnElementsOfTupleDeclaration.py @@ -1,2 +1,2 @@ args = ('foo', 'bar') -s = '%d %d' % args \ No newline at end of file +s = '%d %d' % args diff --git a/python/testData/inspections/PyStringFormatInspection/TupleMultiplication.py b/python/testData/inspections/PyStringFormatInspection/TupleMultiplication.py index c1926917f912..339aa4c98a90 100644 --- a/python/testData/inspections/PyStringFormatInspection/TupleMultiplication.py +++ b/python/testData/inspections/PyStringFormatInspection/TupleMultiplication.py @@ -2,4 +2,4 @@ argument_pattern = re.compile(r'(%s)\s*(\(\s*(%s)\s*\)\s*)?$' % ((states.Inliner.simplename,) * 2)) t, num = ('foo',), 2 -res = '%d %d' % (t * num) +res = '%d %d' % (t * num) diff --git a/python/testData/inspections/PyTypeCheckerInspection/ChainedComparisons.py b/python/testData/inspections/PyTypeCheckerInspection/ChainedComparisons.py index 45422150a6a6..df656dcf03ba 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ChainedComparisons.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ChainedComparisons.py @@ -1,6 +1,6 @@ print('a' < 'b' < 'c' < 'd') print(('a' < 'b') < 'c') -print((1, 1) < (1, 2) < (1, 3) < (1, 4)) +print((1, 1) < (1, 2) < (1, 3) < (1, 4)) print(((1, 1) < (1, 2)) < (1, 3)) print(1.0 < 4.5 < 9.3 < 10.0) print((1.0 < 4.5) < 9.3) diff --git a/python/testData/inspections/PyTypeCheckerInspection/HomogeneousTuples.py b/python/testData/inspections/PyTypeCheckerInspection/HomogeneousTuples.py index 75bf017666b8..915e3421ec35 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/HomogeneousTuples.py +++ b/python/testData/inspections/PyTypeCheckerInspection/HomogeneousTuples.py @@ -9,7 +9,7 @@ int_and_bool = (42, True) expects_many_ints(int_and_bool) int_and_str = (42, 'foo') -expects_many_ints(int_and_str) +expects_many_ints(int_and_str) booleans = (True, False) # type: Tuple[bool, ...] expects_many_ints(booleans) diff --git a/python/testData/inspections/PyUnhashableInspection/identifyUnhashable.py b/python/testData/inspections/PyUnhashableInspection/identifyUnhashable.py index 4e44b24a5698..99b6fca4736b 100644 --- a/python/testData/inspections/PyUnhashableInspection/identifyUnhashable.py +++ b/python/testData/inspections/PyUnhashableInspection/identifyUnhashable.py @@ -29,7 +29,7 @@ d[frozenset([1, 2])] = 0 d[object()] = 0 d[(1, (2, 3))] = 0 -d[(1, (2, []))] = 0 +d[(1, (2, []))] = 0 unhashable_union: int | list = 5 d[unhashable_union] = 0 diff --git a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationGenericBuiltinTuple_after.py b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationGenericBuiltinTuple_after.py index eb755fffe11d..08a5e09f86ee 100644 --- a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationGenericBuiltinTuple_after.py +++ b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationGenericBuiltinTuple_after.py @@ -1 +1,3 @@ -var: [tuple[int, int]] = (1, 2) \ No newline at end of file +from typing import Literal + +var: [tuple[Literal[1], Literal[2]]] = (1, 2) \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationLocalUnpackedAssignmentTarget_after.py b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationLocalUnpackedAssignmentTarget_after.py index 64f4008aad8f..dca42ed1f31f 100644 --- a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationLocalUnpackedAssignmentTarget_after.py +++ b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationLocalUnpackedAssignmentTarget_after.py @@ -1,4 +1,7 @@ +from typing import Literal + + def func(): - var: [str] + var: [Literal['spam']] var, _ = 'spam', 42 var diff --git a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationTupleType_after.py b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationTupleType_after.py index 31d092d4bdb8..80a32f530101 100644 --- a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationTupleType_after.py +++ b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/annotationTupleType_after.py @@ -1 +1,3 @@ -var: [tuple[int, str, None]] = (1, 'foo', None) \ No newline at end of file +from typing import Literal + +var: [tuple[Literal[1], Literal['foo'], None]] = (1, 'foo', None) \ No newline at end of file diff --git a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/typeCommentComplexUnpackedAssignmentTarget_after.py b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/typeCommentComplexUnpackedAssignmentTarget_after.py index 95734ea1f9a6..09c0eb51547a 100644 --- a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/typeCommentComplexUnpackedAssignmentTarget_after.py +++ b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/typeCommentComplexUnpackedAssignmentTarget_after.py @@ -1,3 +1,6 @@ +from typing import Literal + + def func(): - ((var, _), _) = ('foo', 1), 2 # type: (([str], [int]), [int]) + ((var, _), _) = ('foo', 1), 2 # type: (([Literal['foo']], [Literal[1]]), [Literal[2]]) var diff --git a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/typeCommentLocalUnpackedAssignmentTarget_after.py b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/typeCommentLocalUnpackedAssignmentTarget_after.py index f2503b04b4dc..8e3fb09551b8 100644 --- a/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/typeCommentLocalUnpackedAssignmentTarget_after.py +++ b/python/testData/intentions/PyAnnotateVariableTypeIntentionTest/typeCommentLocalUnpackedAssignmentTarget_after.py @@ -1,3 +1,6 @@ +from typing import Literal + + def func(): - var, _ = 'spam', 42 # type: ([str], [int]) + var, _ = 'spam', 42 # type: ([Literal['spam']], [Literal[42]]) var diff --git a/python/testData/quickdoc/TupleTypeIsRenderedLowercased.html b/python/testData/quickdoc/TupleTypeIsRenderedLowercased.html index e32b57e825bf..fb3e96949bac 100644 --- a/python/testData/quickdoc/TupleTypeIsRenderedLowercased.html +++ b/python/testData/quickdoc/TupleTypeIsRenderedLowercased.html @@ -1 +1 @@ -
items: tuple[int, str] = (42, 'foo')
\ No newline at end of file +
items: tuple[Literal[42], Literal['foo']] = (42, 'foo')
\ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/Py3TypeTest.java b/python/testSrc/com/jetbrains/python/Py3TypeTest.java index a5d10806c2c6..88593b8cddca 100644 --- a/python/testSrc/com/jetbrains/python/Py3TypeTest.java +++ b/python/testSrc/com/jetbrains/python/Py3TypeTest.java @@ -3646,7 +3646,7 @@ public class Py3TypeTest extends PyTestCase { // PY-64474 public void testTupleElementAccessedWithNegativeIndex() { - doTest("bool", + doTest("Literal[True]", """ xs = (1, True, "foo") expr = xs[-2] @@ -5151,6 +5151,13 @@ public class Py3TypeTest extends PyTestCase { """); } + @TestFor(issues = "PY-57621") + public void testTupleWithLiteralValues() { + doTest("tuple[Literal[1]]", """ + expr = (1,) + """); + } + // PY-87575 public void testIterDefinedInMetaclass() { doTest("set[int]", """ diff --git a/python/testSrc/com/jetbrains/python/PyTypeConversionTest.java b/python/testSrc/com/jetbrains/python/PyTypeConversionTest.java index ff7b76ff7dd7..ef92b857bcfc 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeConversionTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeConversionTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python; import com.jetbrains.python.documentation.PythonDocumentationProvider; @@ -32,7 +32,7 @@ public final class PyTypeConversionTest extends PyTestCase { } public void testTupleToTypingIterable() { - doTest("typing.Iterable", "Iterable[int | str]", """ + doTest("typing.Iterable", "Iterable[Literal[1, \"foo\"]]", """ expr = (1, "foo") """); } diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 7d0040682c53..b48c1feaad3c 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -1,4 +1,4 @@ -// 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. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python; import com.google.common.collect.ImmutableList; @@ -52,7 +52,7 @@ public class PyTypeTest extends PyTestCase { } public void testTupleType() { - doTest("str", + doTest("Literal['a']", "t = ('a', 2)\n" + "expr = t[0]"); doTest("List[bool]", @@ -68,7 +68,7 @@ public class PyTypeTest extends PyTestCase { } public void testTupleAssignmentType() { - doTest("str", + doTest("Literal['a']", "t = ('a', 2)\n" + "(expr, q) = t"); } @@ -106,7 +106,7 @@ public class PyTypeTest extends PyTestCase { } public void testUnionOfTuples() { - doTest("Union[Tuple[int, str], Tuple[str, int]]", + doTest("Union[Tuple[Literal[1], Literal['a']], Tuple[Literal['a'], Literal[1]]]", """ def x(b): if b: @@ -298,7 +298,7 @@ public class PyTypeTest extends PyTestCase { } public void testIsInstance2() { - doTest("str", + doTest("Literal[\"\"]", """ x = "" if isinstance(x, (1, "")): @@ -1002,7 +1002,7 @@ public class PyTypeTest extends PyTestCase { // PY-9334 public void testIterateOverListOfNestedTuples() { - doTest("str", + doTest("Literal['foo']", """ def f(): for i, (expr, v) in [(0, ('foo', []))]: @@ -1047,7 +1047,7 @@ public class PyTypeTest extends PyTestCase { // PY-10967 public void testDefaultTupleParameterMember() { - doTest("int", + doTest("Literal[1]", """ def foo(xs=(1, 2)): expr, foo = xs @@ -1071,7 +1071,7 @@ public class PyTypeTest extends PyTestCase { } public void testTupleFromTuple() { - doTest("Tuple[str, int, int]", + doTest("Tuple[Literal['1'], Literal[2], Literal[3]]", "expr = tuple(('1', 2, 3))"); } @@ -1128,7 +1128,7 @@ public class PyTypeTest extends PyTestCase { } public void testTupleIterationType() { - doTest("Union[int, str]", + doTest("Literal[1, 'a']", """ xs = (1, 'a') for expr in xs: @@ -1138,35 +1138,35 @@ public class PyTypeTest extends PyTestCase { // PY-12801 public void testTupleConcatenation() { - doTest("Tuple[int, bool, str]", + doTest("Tuple[Literal[1], Literal[True], Literal['spam']]", "expr = (1,) + (True, 'spam') + ()"); } public void testTupleMultiplication() { - doTest("Tuple[int, bool, int, bool]", + doTest("Tuple[Literal[1], Literal[False], Literal[1], Literal[False]]", "expr = (1, False) * 2"); } public void testTupleDestructuring() { - doTest("str", + doTest("Literal['val']", "_, expr = (1, 'val') "); } public void testParensTupleDestructuring() { - doTest("str", + doTest("Literal['val']", "(_, expr) = (1, 'val') "); } // PY-19825 public void testSubTupleDestructuring() { - doTest("str", + doTest("Literal['val']", "(a, (_, expr)) = (1, (2,'val')) "); } // PY-19825 public void testSubTupleIndirectDestructuring() { - doTest("str", + doTest("Literal['val']", "xs = (2,'val')\n" + "(a, (_, expr)) = (1, xs) "); } @@ -1174,7 +1174,7 @@ public class PyTypeTest extends PyTestCase { // PY-38928 public void testIterateListOfTuples() { doTest( - "str", + "Literal['foo']", """ for ((_, expr)) in [(1, 'foo')]: pass @@ -1670,9 +1670,11 @@ public class PyTypeTest extends PyTestCase { } public void testHeterogeneousTupleLiteral() { - doTest("Tuple[str, int, int]", "expr = ('1', 1, 1)"); + doTest("Tuple[Literal['1'], Literal[1], Literal[1]]", "expr = ('1', 1, 1)"); - doTest("Tuple[str, int, int, int, int, int, int, int, int, int, int]", "expr = ('1', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)"); + doTest( + "Tuple[Literal['1'], Literal[1], Literal[1], Literal[1], Literal[1], Literal[1], Literal[1], Literal[1], Literal[1], Literal[1], Literal[1]]", + "expr = ('1', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)"); } // PY-20818 @@ -2712,14 +2714,14 @@ public class PyTypeTest extends PyTestCase { } public void testUnpackingToNestedTargetsInSquareBracketsInAssignments() { - doTest("int", + doTest("Literal[42]", """ [_, [[expr], _]] = "foo", ((42,), "bar") """); } public void testUnpackingToNestedTargetsInSquareBracketsInForLoops() { - doTest("str", + doTest("Literal[\"foo\"]", """ xs = [(1, ("foo",))] for [_, [expr]] in xs: @@ -2728,7 +2730,7 @@ public class PyTypeTest extends PyTestCase { } public void testUnpackingToNestedTargetsInSquareBracketsInComprehensions() { - doTest("str", + doTest("Literal[\"foo\"]", """ xs = [(1, ("foo",))] ys = [expr for [_, [expr]] in xs] diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 2ad166bc4ff6..7ffe8db52838 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -6158,6 +6158,19 @@ public class PyTypingTest extends PyTestCase { """); } + @TestFor(issues="PY-57621") + public void testEnumTuple() { + doTest("tuple[int, str]", """ + from enum import Enum + + class Color(Enum): + RED = 1, "red" + BLUE = 2, "blue" + + expr = Color.BLUE.value + """); + } + // PY-76149 public void testDataclassTransformConstructorSignatureWithFieldsAnnotatedWithDescriptor() { doTestExpressionUnderCaret("(id: int, name: str) -> MyClass", """ diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index 31cc1f54c8d8..a61b59e33f79 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 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. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.inspections; import com.intellij.openapi.util.RecursionManager; @@ -1874,8 +1874,8 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase { foo(1, bar, args=(0, 'foo')) foo(1, baz, args=(0, 'foo', 1.0, False)) - foo(1, bar, args=('foo', 0)) - foo(1, baz, args=('foo', 0, 1.0, False)) + foo(1, bar, args=('foo', 0)) + foo(1, baz, args=('foo', 0, 1.0, False)) """); } @@ -2044,12 +2044,28 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase { def foo(*args: Tuple[*Ts]): ... foo((0,), (1,)) - foo((0,), (1, 2)) + foo((0,), (1, 2)) # Should fail according to https://typing.python.org/en/latest/spec/generics.html#type-variable-tuple-equality - foo((0,), ('1',)) + foo((0,), ('1',)) """); } + public void testTypeVarTupleWidening() { + fixme("widen more literal types in type var tuples", AssertionError.class, "Expected type 'tuple[tuple[Literal[0]]]'", () -> { + doTestByText(""" + from typing import Literal, Sequence + + def foo[*Ts](*args: tuple[*Ts]): ... + + # nested tuples + foo(((0,),), ((1,),)) + def main(ones: Sequence[Literal[1]], twos: Sequence[Literal[2]]): + # should this widen to `Sequence[int]` or should it show an error? + foo((ones,), (twos,)) + """); + }); + } + // PY-53105 public void testVariadicGenericStarArgsOfVariadicGenericPrefixSuffix() { doTestByText(""" diff --git a/python/testSrc/com/jetbrains/python/inspections/PyPatternInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyPatternInspectionTest.java index 6b81419d137f..a37fa420cc21 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyPatternInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyPatternInspectionTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.inspections; import com.jetbrains.python.fixtures.PyInspectionTestCase; @@ -141,7 +141,7 @@ def f(a): class D: def __init__(self): self.x = 0 - __match_args__ = ("x", 1) + __match_args__ = ("x", 1) """); } @@ -278,7 +278,7 @@ class D: public void testMatchArgsInvalidTupleOfInts() { doTestByText(""" class D: - __match_args__ = (1, 2, 3) + __match_args__ = (1, 2, 3) """); } diff --git a/python/testSrc/com/jetbrains/python/testing/PyTestFixtureResolvingTest.kt b/python/testSrc/com/jetbrains/python/testing/PyTestFixtureResolvingTest.kt index 26f742a44f9a..a59dad40f3d0 100644 --- a/python/testSrc/com/jetbrains/python/testing/PyTestFixtureResolvingTest.kt +++ b/python/testSrc/com/jetbrains/python/testing/PyTestFixtureResolvingTest.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.testing import com.intellij.codeInsight.navigation.actions.GotoTypeDeclarationAction @@ -361,7 +361,7 @@ class PyTestFixtureResolvingTest : PyTestCase() { } fun testNamedParameterTypes() { - assertCorrectType(PARAMETRIZED_DIR, TEST_PARAMETER_TYPES, INT_STR_UNION) + assertCorrectType(PARAMETRIZED_DIR, TEST_PARAMETER_TYPES, "Literal[9] | str") } @TestFor(issues = ["PY-56268"])