Skip to content

Conversation

@ibraheemdev
Copy link
Member

@ibraheemdev ibraheemdev commented Sep 3, 2025

Summary

Adds support for generic PEP695 type aliases, e.g.,

type A[T] = T
reveal_type(A[int]) # A[int]

Resolves astral-sh/ty#677.

@ibraheemdev ibraheemdev added the typing Related to type annotations label Sep 3, 2025
@ibraheemdev ibraheemdev added the ty Multi-file analysis & type inference label Sep 3, 2025
@ibraheemdev ibraheemdev force-pushed the ibraheem/generic-type-alias branch 2 times, most recently from 2cdea11 to 01c535e Compare September 3, 2025 21:17
@github-actions
Copy link
Contributor

github-actions bot commented Sep 3, 2025

Diagnostic diff on typing conformance tests

Changes were detected when running ty on typing conformance tests
--- old-output.txt	2025-09-08 20:03:49.611822703 +0000
+++ new-output.txt	2025-09-08 20:03:52.598894420 +0000
@@ -1,5 +1,6 @@
 WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
-fatal[panic] Panicked at /home/runner/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/execute.rs:228:25 when checking `/home/runner/work/ruff/ruff/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(1227a)): execute: too many cycle iterations`
+fatal[panic] Panicked at /home/runner/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/execute.rs:228:25 when checking `/home/runner/work/ruff/ruff/typing/conformance/tests/aliases_type_statement.py`: `PEP695TypeAliasType < 'db >::value_type_(Id(b416)): execute: too many cycle iterations`
+fatal[panic] Panicked at /home/runner/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a3ffa22/src/function/execute.rs:228:25 when checking `/home/runner/work/ruff/ruff/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(1267a)): execute: too many cycle iterations`
 _directives_deprecated_library.py:15:31: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
 _directives_deprecated_library.py:30:26: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `str`
 _directives_deprecated_library.py:36:41: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `Self@__add__`
@@ -50,25 +51,6 @@
 aliases_newtype.py:18:1: error[invalid-assignment] Object of type `NewType` is not assignable to `type`
 aliases_newtype.py:26:21: error[invalid-base] Invalid class base with type `NewType`
 aliases_newtype.py:63:43: error[too-many-positional-arguments] Too many positional arguments to bound method `__init__`: expected 3, got 4
-aliases_type_statement.py:17:1: error[unresolved-attribute] Type `typing.TypeAliasType` has no attribute `bit_count`
-aliases_type_statement.py:19:1: error[call-non-callable] Object of type `TypeAliasType` is not callable
-aliases_type_statement.py:23:7: error[unresolved-attribute] Type `typing.TypeAliasType` has no attribute `other_attrib`
-aliases_type_statement.py:26:18: error[invalid-base] Invalid class base with type `typing.TypeAliasType`
-aliases_type_statement.py:37:22: error[invalid-type-form] Function calls are not allowed in type expressions
-aliases_type_statement.py:38:22: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
-aliases_type_statement.py:39:22: error[invalid-type-form] Tuple literals are not allowed in this context in a type expression
-aliases_type_statement.py:39:23: error[invalid-type-form] Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
-aliases_type_statement.py:40:22: error[invalid-type-form] List comprehensions are not allowed in type expressions
-aliases_type_statement.py:41:22: error[invalid-type-form] Dict literals are not allowed in type expressions
-aliases_type_statement.py:42:22: error[invalid-type-form] Function calls are not allowed in type expressions
-aliases_type_statement.py:43:28: error[invalid-type-form] Int literals are not allowed in this context in a type expression
-aliases_type_statement.py:44:22: error[invalid-type-form] `if` expressions are not allowed in type expressions
-aliases_type_statement.py:45:22: error[invalid-type-form] Variable of type `Literal[1]` is not allowed in a type expression
-aliases_type_statement.py:46:23: error[invalid-type-form] Boolean literals are not allowed in this context in a type expression
-aliases_type_statement.py:47:23: error[invalid-type-form] Int literals are not allowed in this context in a type expression
-aliases_type_statement.py:48:23: error[invalid-type-form] Boolean operations are not allowed in type expressions
-aliases_type_statement.py:49:23: error[fstring-type-annotation] Type expressions cannot use f-strings
-aliases_type_statement.py:80:37: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
 aliases_variance.py:18:24: error[non-subscriptable] Cannot subscript object of type `<class 'ClassA[typing.TypeVar]'>` with no `__class_getitem__` method
 aliases_variance.py:28:16: error[non-subscriptable] Cannot subscript object of type `<class 'ClassA[typing.TypeVar]'>` with no `__class_getitem__` method
 aliases_variance.py:44:16: error[non-subscriptable] Cannot subscript object of type `<class 'ClassB[typing.TypeVar, typing.TypeVar]'>` with no `__class_getitem__` method
@@ -866,5 +848,5 @@
 typeddicts_usage.py:28:1: error[missing-typed-dict-key] Missing required key 'name' in TypedDict `Movie` constructor
 typeddicts_usage.py:28:18: error[invalid-key] Invalid key access on TypedDict `Movie`: Unknown key "title"
 typeddicts_usage.py:40:24: error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions. Did you mean to use a concrete TypedDict or `collections.abc.Mapping[str, object]` instead?
-Found 867 diagnostics
+Found 849 diagnostics
 WARN A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 3, 2025

mypy_primer results

Changes were detected when running on open source projects
async-utils (https://github.com/mikeshardmind/async-utils)
+ src/async_utils/corofunc_cache.py:44:39: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/corofunc_cache.py:44:61: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/corofunc_cache.py:48:36: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/corofunc_cache.py:48:58: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/corofunc_cache.py:109:31: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/corofunc_cache.py:109:53: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/corofunc_cache.py:188:31: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/corofunc_cache.py:188:53: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/gen_transform.py:60:53: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/gen_transform.py:106:8: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:30:29: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:30:46: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:45:39: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:45:65: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:49:36: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:49:62: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:81:27: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:81:50: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:169:31: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:169:57: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:255:31: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ src/async_utils/task_cache.py:255:57: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
- Found 7 diagnostics
+ Found 29 diagnostics

artigraph (https://github.com/artigraph/artigraph)
+ tests/arti/internal/test_mappings.py:142:5: error[invalid-assignment] Object of type `Coord` is not assignable to attribute `z` on type `Unknown | int | Coord | TypedBox[Coord]`
+ tests/arti/internal/test_mappings.py:160:9: warning[possibly-unbound-attribute] Attribute `_dne` on type `Coord | TypedBox[Coord]` is possibly unbound
+ tests/arti/internal/test_mappings.py:162:9: error[invalid-assignment] Object of type `Unknown | Coord` is not assignable to attribute `_dne` on type `Coord | TypedBox[Coord]`
+ tests/arti/internal/test_mappings.py:172:5: error[invalid-assignment] Object of type `Unknown | Coord` is not assignable to attribute `coord` on type `Coord | TypedBox[Coord]`
+ tests/arti/internal/test_mappings.py:172:5: warning[possibly-unbound-attribute] Attribute `attribute` on type `Coord | TypedBox[Coord]` is possibly unbound
+ tests/arti/internal/test_mappings.py:172:5: warning[possibly-unbound-attribute] Attribute `child` on type `Coord | TypedBox[Coord]` is possibly unbound
- Found 133 diagnostics
+ Found 139 diagnostics

core (https://github.com/home-assistant/core)
+ homeassistant/components/androidtv/entity.py:46:16: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/androidtv/entity.py:46:49: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/androidtv/entity.py:54:15: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/androidtv/entity.py:55:10: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/cast/media_player.py:94:11: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/cast/media_player.py:95:6: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/hassio/addon_manager.py:37:6: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/hassio/addon_manager.py:37:42: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/hassio/addon_manager.py:43:15: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/hassio/addon_manager.py:44:10: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/heos/media_player.py:144:16: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/heos/media_player.py:144:36: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/heos/media_player.py:147:25: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/heos/media_player.py:147:47: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/http/decorators.py:33:6: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/http/decorators.py:34:5: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/http/decorators.py:44:12: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/http/decorators.py:45:6: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/http/decorators.py:53:12: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/http/decorators.py:59:10: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/http/decorators.py:60:9: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/http/decorators.py:62:7: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/http/decorators.py:67:15: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/http/decorators.py:68:10: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/husqvarna_automower/entity.py:53:16: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/husqvarna_automower/entity.py:53:46: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/husqvarna_automower/entity.py:56:25: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/husqvarna_automower/entity.py:56:57: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/izone/climate.py:122:16: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/izone/climate.py:122:46: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/izone/climate.py:123:20: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/izone/climate.py:123:52: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/openhome/media_player.py:71:6: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/openhome/media_player.py:71:44: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/openhome/media_player.py:76:15: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/openhome/media_player.py:77:10: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/recorder/util.py:607:16: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:607:38: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:613:24: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:613:48: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:621:16: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/recorder/util.py:621:45: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/recorder/util.py:627:24: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/recorder/util.py:627:55: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/recorder/util.py:634:10: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:635:6: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:663:16: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:663:36: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:673:14: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:674:10: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:684:16: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/recorder/util.py:684:43: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/recorder/util.py:694:14: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/recorder/util.py:695:10: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/recorder/util.py:704:10: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/recorder/util.py:708:6: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/roku/helpers.py:31:16: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/roku/helpers.py:31:46: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/roku/helpers.py:35:15: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/roku/helpers.py:36:10: error[too-many-positional-arguments] Too many positional arguments: expected 1, got 2
+ homeassistant/components/sonos/helpers.py:46:16: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/sonos/helpers.py:46:40: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/sonos/helpers.py:52:16: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/sonos/helpers.py:52:40: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/sonos/helpers.py:57:16: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/sonos/helpers.py:57:40: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/sonos/helpers.py:60:26: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/components/sonos/helpers.py:60:52: error[too-many-positional-arguments] Too many positional arguments: expected 2, got 3
+ homeassistant/helpers/dispatcher.py:72:18: error[too-many-positional-arguments] Too many positional arguments: expected 0, got 1
+ homeassistant/helpers/dispatcher.py:119:18: error[too-many-positional-arguments] Too many positional arguments: expected 0, got 1
+ homeassistant/helpers/dispatcher.py:234:18: error[too-many-positional-arguments] Too many positional arguments: expected 0, got 1
+ homeassistant/helpers/singleton.py:68:32: error[invalid-await] `_Coro | _U@singleton` is not awaitable
+ homeassistant/helpers/singleton.py:83:12: error[invalid-return-type] Return type does not match returned value: expected `(_FuncType, /) -> _FuncType`, found `Overload[(func: (HomeAssistant, /) -> Coroutine[Any, Any, _T@singleton]) -> (HomeAssistant, /) -> Coroutine[Any, Any, _T@singleton], (func: (HomeAssistant, /) -> _U@singleton) -> (HomeAssistant, /) -> _U@singleton]`
- homeassistant/helpers/singleton.py:102:5: error[type-assertion-failure] Argument does not have asserted type `int`
- homeassistant/helpers/singleton.py:103:5: error[type-assertion-failure] Argument does not have asserted type `int`
- homeassistant/helpers/singleton.py:106:35: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
- Found 13345 diagnostics
+ Found 13415 diagnostics
No memory usage changes detected ✅

@sharkdp
Copy link
Contributor

sharkdp commented Sep 4, 2025

Not a full review, but the new too-many-positional-arguments diagnostics in the ecosystem diff indicate that there is some problem with ParamSpecs. For example:

from typing import Callable

type ReturnsInt[**P] = Callable[P, int]

def call[**P](callable: ReturnsInt[P]) -> None:  # ty: too-many-positional-arguments
    pass

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have resources/mdtest/pep695_type_aliases.md. Should these two files be merged?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, although this file is specific to generic PEP 695 aliases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. And I don't care too much about the structure/layout of the tests. Just wanted do let you know that there are some generics-related tests in that other file as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I think we can look at this as a follow-up.

@ibraheemdev
Copy link
Member Author

@sharkdp I believe that's astral-sh/ty#157. All the generics infrastructure in this PR is reused from generic classes, which emit a similar error:

class ReturnsInt[**P]:
    x: Callable[P, int]

def call[**P](callable: ReturnsInt[P]) -> None:  # ty: too-many-positional-arguments
    pass

Copy link
Member

@dcreager dcreager left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at how pyright handles this, there might a simpler way to do this that punts on several of the questions we've had.

class C[T]: ...

def _[T](c: C[T]):
    reveal_type(c)  # revealed: C[T@_]

reveal_type(C)  # revealed: type[C[Unknown]]
reveal_type(C[int])  # revealed: type[C[int]]


type X[T] = C[T]
type Y = str

def _[T](x: X[T]):
    reveal_type(x)  # revealed: C[T@_]

def _(y: Y):
    reveal_type(y)  # revealed: str

reveal_type(X)  # revealed: TypeAliasType
reveal_type(X[int])  # revealed: TypeAliasType
reveal_type(Y)  # revealed: TypeAliasType

Note that in the last two function bodies, the revealed type does not mention the type alias at all. So in ty terms, NominalInstance does not need to wrap the (specialized) type alias.

Similarly, the revealed type of X[int] is TypeAliasType, and so GenericAlias doesn't need to wrap the type alias either.

This suggests that we can roll back all of the changes to GenericAlias and NominalInstance, and just make sure that in_type_expression propagates the generic context through when it resolves a type alias into its value type.


[I made several comments below about the current implementation, which might become moot given the above, but I'm keeping them here in case my analysis is wrong and we do need to keep those changes]

@dcreager
Copy link
Member

dcreager commented Sep 4, 2025

and so GenericAlias doesn't need to wrap the type alias either

And in particular, this reminds me of an earlier draft of this that you had shown me, where PEP695TypeAlias had an Option<Specialization> field, since we do need some way to distinguish X from X[int] in my example. And then in_type_expression would check if the type alias is generic, and if it is, fall back on the default specialization if that field is None.

@ibraheemdev ibraheemdev force-pushed the ibraheem/generic-type-alias branch 3 times, most recently from 9ea2dca to 9777a06 Compare September 5, 2025 17:35
@ibraheemdev ibraheemdev force-pushed the ibraheem/generic-type-alias branch from 9777a06 to 40b7c95 Compare September 5, 2025 17:36
Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great! A few comments.

@ibraheemdev
Copy link
Member Author

ibraheemdev commented Sep 8, 2025

The typing conformance suite is now panicking with value_type(_): too many cycle iterations on aliases_type_statement.md because of:

type RecursiveTypeAlias1[T] = T | list[RecursiveTypeAlias1[T]]
r1_1: RecursiveTypeAlias1[int] = 1

This looks like astral-sh/ty#256, which I believe is unrelated to this PR. The ecosystem failures also all seem to be cases where recognizing generic type aliases leads to more detailed diagnostics for underlying semantics that we do not support.

@ibraheemdev ibraheemdev force-pushed the ibraheem/generic-type-alias branch from b52a1dd to 0d2a272 Compare September 8, 2025 19:32
@carljm
Copy link
Contributor

carljm commented Sep 8, 2025

The typing conformance suite is now panicking with value_type(_): too many cycle iterations on aliases_type_statement.md

Let me look at this. We avoid this already for PEP 695 aliases in general by creating a type alias type for the assignment without resolving the RHS, and deferring resolution of the RHS as part of value_type on the alias. It seems like something in this PR must be breaking this (i.e., resolving the RHS too eagerly) for generic aliases. If necessary we can fix it in a separate follow-up, but it is something that I would expect to work at this point, it's not an expected failure.

@carljm
Copy link
Contributor

carljm commented Sep 8, 2025

Hmm I see the issue -- it's not that we resolve the RHS too eagerly in general, it's that when subscripting we resolve the value-type of the alias at that point to apply the specialization. We may need to allow Type::TypeAlias to carry a lazy specialization with it in order to support this. In any case, having looked at it I agree this isn't an issue we should try to solve in this PR.

Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I think we can look at this as a follow-up.

@carljm carljm merged commit aa5d665 into main Sep 8, 2025
38 checks passed
@carljm carljm deleted the ibraheem/generic-type-alias branch September 8, 2025 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference typing Related to type annotations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support generic type aliases

5 participants