From e5b8e52e8d1f3de85c2364aeeee8728407abfb65 Mon Sep 17 00:00:00 2001 From: Daniil Kalinin Date: Wed, 17 Jun 2026 12:02:19 +0200 Subject: [PATCH] PY-90345 Count a type variable once across value and class-object positions in a type alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A type variable used both as a value (`T`) and as a class object (`type[T]`) was collected twice — in instance and definition form — so a parameterized generic type alias such as `Union[Role[T], type[T]]` appeared to have two type parameters. Subscripting it with a single argument mapped the surplus parameter to Any, which collapsed the `type[T]` arm and left the type variable unbound: e.g. `select(Model)` was inferred as `Select[tuple[Any]]` instead of `Select[tuple[Model]]`, losing element types (and completion) on query results. Normalize type variables to their instance form when counting a generic type's parameters in PyTypeChecker.parameterizeType, so the same variable is counted once. (cherry picked from commit 5ae8e727eef7168caafa269dc5fd64c0a38f36f8) GitOrigin-RevId: f7c58e89e561d1cbc70bee22821b7e62272fd545 --- .../python/types/PyGenericTypeTest.kt | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/python/testSrc/com/jetbrains/python/types/PyGenericTypeTest.kt b/python/testSrc/com/jetbrains/python/types/PyGenericTypeTest.kt index 925d4e585ca6..aaa6d2d61bc9 100644 --- a/python/testSrc/com/jetbrains/python/types/PyGenericTypeTest.kt +++ b/python/testSrc/com/jetbrains/python/types/PyGenericTypeTest.kt @@ -733,6 +733,65 @@ class PyGenericTypeTest : PyCodeInsightTestCase() { expr: TypeAlias[int] #└ TYPE type[int] """) + + @Test + @TestFor(issues = ["PY-90345"]) + fun `call inference via union TypeAlias with class-object arm`() = test(""" + from typing import Generic, TypeVar + T = TypeVar('T') + class Role(Generic[T]): ... + Alias = Role[T] | type[T] + def f(x: Alias[T]) -> T: ... + expr = f(int) + #└ TYPE int + """) + + @Test + @TestFor(issues = ["PY-90345"]) + fun `call inference via union TypeAlias with class-object arm first`() = test(""" + from typing import Generic, TypeVar + T = TypeVar('T') + class Role(Generic[T]): ... + Alias = type[T] | Role[T] + def f(x: Alias[T]) -> T: ... + expr = f(int) + #└ TYPE int + """) + + @Test + @TestFor(issues = ["PY-90345"]) + fun `call inference via three-arm union TypeAlias with class-object`() = test(""" + from typing import Generic, TypeVar + T = TypeVar('T') + class A(Generic[T]): ... + class B(Generic[T]): ... + Alias = A[T] | B[T] | type[T] + def f(x: Alias[T]) -> T: ... + expr = f(int) + #└ TYPE int + """) + + @Test + @TestFor(issues = ["PY-90345"]) + fun `parameterized union TypeAlias with class-object arm`() = test(""" + from typing import Generic, TypeVar + T = TypeVar('T') + class Role(Generic[T]): ... + Alias = Role[T] | type[T] + expr: Alias[int] + #└ TYPE Role[int] | type[int] + """) + + @Test + @TestFor(issues = ["PY-90345"]) + fun `parameterized union TypeAlias with class-object arm PEP695`() = test(""" + from typing import Generic, TypeVar + T = TypeVar('T') + class Role(Generic[T]): ... + type Alias[T] = Role[T] | type[T] + expr: Alias[int] + #└ TYPE Role[int] | type[int] + """) } @Nested @@ -1758,4 +1817,5 @@ class PyGenericTypeTest : PyCodeInsightTestCase() { # FIXME PY-37876: an error is expected here but is not produced; documents current behavior. func(42, accepts_anything) """) + }