PY-90345 Count a type variable once across value and class-object positions in a type alias

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
This commit is contained in:
Daniil Kalinin
2026-07-21 14:09:16 +00:00
committed by intellij-monorepo-bot
parent ca8fc24822
commit e5b8e52e8d
@@ -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)
""")
}