Skip to content

Conversation

@fangyi-zhou
Copy link
Contributor

Summary

GenericAlias is the runtime time when a type argument is applied on a generic class (via subscripting)
https://docs.python.org/3.15/library/stdtypes.html#generic-alias-type

While the typing spec does not mention how this runtime time should be treated, we are adding some modelling of this behaviour in this diff, i.e. supporting attribute lookup on GenericAlias: the doc specifies a number of special attributes such as __origin__, __args__, __parameters__ (https://docs.python.org/3.15/library/stdtypes.html#special-attributes-of-genericalias-objects).

Notably, generic aliases support | operator, and can construct a union type with string literals (See discussion in #2048).

Test Plan

test.py with new test case.

`GenericAlias` is the runtime time when a type argument is applied on a
generic class (via subscripting)
https://docs.python.org/3.15/library/stdtypes.html#generic-alias-type

While the typing spec does not mention how this runtime time should be
treated, we are adding some modelling of this behaviour in this diff,
i.e. supporting attribute lookup on `GenericAlias`: the doc specifies
a number of special attributes such as `__origin__`, `__args__`,
`__parameters__`.

Notably, generic aliases support `|` operator, and can construct a union
type with string literals (See discussion in facebook#2048).
@meta-cla meta-cla bot added the cla signed label Jan 28, 2026
for x in A[str]:
assert_type(x, int)
for _ in B[str]: # E: Type `type[B[str]]` is not iterable
for _ in B[str]:
Copy link
Contributor Author

@fangyi-zhou fangyi-zhou Jan 28, 2026

Choose a reason for hiding this comment

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

$ cat test.py
class B[T]:
    pass
for x in B[str]:
    print(x)
$ python3 test.py
typing.Unpack[__main__.B[str]]

This is actually valid 😮

@fangyi-zhou
Copy link
Contributor Author

cc @yangdanny97 over previous discussion on discord

@github-actions

This comment has been minimized.

@fangyi-zhou
Copy link
Contributor Author

I think I introduced a regression for comparing generic aliases (now list[int] == list[str] is an error)

@fangyi-zhou fangyi-zhou marked this pull request as draft January 28, 2026 23:03
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@fangyi-zhou fangyi-zhou marked this pull request as ready for review January 29, 2026 00:34
@meta-codesync
Copy link

meta-codesync bot commented Jan 29, 2026

@migeed-z has imported this pull request. If you are a Meta employee, you can view this in D91743101.

@migeed-z migeed-z self-assigned this Jan 29, 2026
// which makes this test vacuously correct.
testcase!(
test_getitem_cannot_iterate_generic_class,
test_getitem_can_iterate_generic_class,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm actually not sure what part of the PR changes this.

I think it's somewhat mistaken. The type type[A[int]] means a type instance (a class object) of some class that is a subtype of A[int].

This actually can exist, e.g. if I define

class B(A[int]):
    ...

then B is a plain class object (not a GenericAlias) and is a valid value of type type[A[int]].

But note that B is not iterable - B() is, but the class object is not.

If it's hard to change the behavior, I'm okay with merging as-is if we mark this as a bug and update the comments (I'm not honestly all that worried about false negatives in weird edge cases - at the moment false positives matter more), but I do think we should track the issue

@fangyi-zhou
Copy link
Contributor Author

@stroxler Thanks for the example, that was something I didn't consider. I've added the bug annotation and some comments in the code to document this caveat.

@fangyi-zhou fangyi-zhou requested a review from stroxler January 30, 2026 01:05
@github-actions
Copy link

Diff from mypy_primer, showing the effect of this PR on open source code:

scrapy (https://github.com/scrapy/scrapy)
- ERROR tests/test_utils_datatypes.py:122:13-37: Class `MutableMapping` has no class attribute `fromkeys` [missing-attribute]
- ERROR tests/test_utils_datatypes.py:126:13-37: Class `MutableMapping` has no class attribute `fromkeys` [missing-attribute]
- ::error file=tests/test_utils_datatypes.py,line=122,col=13,endLine=122,endColumn=37,title=Pyrefly missing-attribute::Class `MutableMapping` has no class attribute `fromkeys`
- ::error file=tests/test_utils_datatypes.py,line=126,col=13,endLine=126,endColumn=37,title=Pyrefly missing-attribute::Class `MutableMapping` has no class attribute `fromkeys`

ibis (https://github.com/ibis-project/ibis)
- ERROR ibis/common/tests/test_typing.py:126:12-43: Class `Test` has no class attribute `__parameters__` [missing-attribute]
- ERROR ibis/common/tests/test_typing.py:127:12-37: Class `Test` has no class attribute `__args__` [missing-attribute]
- ERROR ibis/common/tests/test_typing.py:129:12-36: Class `Test` has no class attribute `__parameters__` [missing-attribute]
- ERROR ibis/common/tests/test_typing.py:130:12-30: Class `Test` has no class attribute `__args__` [missing-attribute]
- ::error file=ibis/common/tests/test_typing.py,line=126,col=12,endLine=126,endColumn=43,title=Pyrefly missing-attribute::Class `Test` has no class attribute `__parameters__`
- ::error file=ibis/common/tests/test_typing.py,line=127,col=12,endLine=127,endColumn=37,title=Pyrefly missing-attribute::Class `Test` has no class attribute `__args__`
- ::error file=ibis/common/tests/test_typing.py,line=129,col=12,endLine=129,endColumn=36,title=Pyrefly missing-attribute::Class `Test` has no class attribute `__parameters__`
- ::error file=ibis/common/tests/test_typing.py,line=130,col=12,endLine=130,endColumn=30,title=Pyrefly missing-attribute::Class `Test` has no class attribute `__args__`

streamlit (https://github.com/streamlit/streamlit)
- ERROR lib/tests/streamlit/elements/select_slider_test.py:390:33-49: Class `Sequence` has no class attribute `C` [missing-attribute]
- ::error file=lib/tests/streamlit/elements/select_slider_test.py,line=390,col=33,endLine=390,endColumn=49,title=Pyrefly missing-attribute::Class `Sequence` has no class attribute `C`

strawberry (https://github.com/strawberry-graphql/strawberry)
+ ERROR strawberry/schema/name_converter.py:177:59-64: Argument `tuple[Any, ...]` is not assignable to parameter `types` with type `list[StrawberryType | type[Any]]` in function `NameConverter.from_generic` [bad-argument-type]
+ ::error file=strawberry/schema/name_converter.py,line=177,col=59,endLine=177,endColumn=64,title=Pyrefly bad-argument-type::Argument `tuple[Any, ...]` is not assignable to parameter `types` with type `list[StrawberryType | type[Any]]` in function `NameConverter.from_generic`

colour (https://github.com/colour-science/colour)
- ERROR colour/continuous/signal.py:296:22-41: Class `float64` has no class attribute `__args__`
+ ERROR colour/continuous/signal.py:296:22-41: Class `float64` has no class attribute `__args__` [missing-attribute]
- Class `floating` has no class attribute `__args__` [missing-attribute]
- ERROR colour/continuous/signal.py:297:60-79: Class `float64` has no class attribute `__args__`
+ ERROR colour/continuous/signal.py:297:60-79: Class `float64` has no class attribute `__args__` [missing-attribute]
- Class `floating` has no class attribute `__args__` [missing-attribute]
- ERROR colour/utilities/array.py:564:53-70: Class `signedinteger` has no class attribute `__args__`
- Class `unsignedinteger` has no class attribute `__args__` [missing-attribute]
- ERROR colour/utilities/array.py:568:53-72: Class `float64` has no class attribute `__args__`
+ ERROR colour/utilities/array.py:568:53-72: Class `float64` has no class attribute `__args__` [missing-attribute]
- Class `floating` has no class attribute `__args__` [missing-attribute]
- ERROR colour/utilities/array.py:572:53-74: Class `complex128` has no class attribute `__args__`
+ ERROR colour/utilities/array.py:572:53-74: Class `complex128` has no class attribute `__args__` [missing-attribute]
- Class `complexfloating` has no class attribute `__args__` [missing-attribute]
- ERROR colour/utilities/array.py:659:21-38: Class `signedinteger` has no class attribute `__args__`
- Class `unsignedinteger` has no class attribute `__args__` [missing-attribute]
- ERROR colour/utilities/array.py:712:21-40: Class `float64` has no class attribute `__args__`
+ ERROR colour/utilities/array.py:712:21-40: Class `float64` has no class attribute `__args__` [missing-attribute]
- Class `floating` has no class attribute `__args__` [missing-attribute]
- ERROR colour/utilities/array.py:753:21-38: Class `signedinteger` has no class attribute `__args__`
- Class `unsignedinteger` has no class attribute `__args__` [missing-attribute]
- ERROR colour/utilities/array.py:786:21-40: Class `float64` has no class attribute `__args__`
+ ERROR colour/utilities/array.py:786:21-40: Class `float64` has no class attribute `__args__` [missing-attribute]
- Class `floating` has no class attribute `__args__` [missing-attribute]
- ERROR colour/utilities/array.py:900:21-42: Class `complex128` has no class attribute `__args__`
+ ERROR colour/utilities/array.py:900:21-42: Class `complex128` has no class attribute `__args__` [missing-attribute]
- Class `complexfloating` has no class attribute `__args__` [missing-attribute]
- ::error file=colour/continuous/signal.py,line=296,col=22,endLine=296,endColumn=41,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`%0AClass `floating` has no class attribute `__args__`%0A  Did you mean `__abs__`?
+ ::error file=colour/continuous/signal.py,line=296,col=22,endLine=296,endColumn=41,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`
- ::error file=colour/continuous/signal.py,line=297,col=60,endLine=297,endColumn=79,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`%0AClass `floating` has no class attribute `__args__`%0A  Did you mean `__abs__`?
+ ::error file=colour/continuous/signal.py,line=297,col=60,endLine=297,endColumn=79,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`
- ::error file=colour/utilities/array.py,line=564,col=53,endLine=564,endColumn=70,title=Pyrefly missing-attribute::Class `signedinteger` has no class attribute `__args__`%0AClass `unsignedinteger` has no class attribute `__args__`%0A  Did you mean `__abs__`?
- ::error file=colour/utilities/array.py,line=568,col=53,endLine=568,endColumn=72,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`%0AClass `floating` has no class attribute `__args__`%0A  Did you mean `__abs__`?
+ ::error file=colour/utilities/array.py,line=568,col=53,endLine=568,endColumn=72,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`
- ::error file=colour/utilities/array.py,line=572,col=53,endLine=572,endColumn=74,title=Pyrefly missing-attribute::Class `complex128` has no class attribute `__args__`%0AClass `complexfloating` has no class attribute `__args__`%0A  Did you mean `__abs__`?
+ ::error file=colour/utilities/array.py,line=572,col=53,endLine=572,endColumn=74,title=Pyrefly missing-attribute::Class `complex128` has no class attribute `__args__`
- ::error file=colour/utilities/array.py,line=659,col=21,endLine=659,endColumn=38,title=Pyrefly missing-attribute::Class `signedinteger` has no class attribute `__args__`%0AClass `unsignedinteger` has no class attribute `__args__`%0A  Did you mean `__abs__`?
- ::error file=colour/utilities/array.py,line=712,col=21,endLine=712,endColumn=40,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`%0AClass `floating` has no class attribute `__args__`%0A  Did you mean `__abs__`?
+ ::error file=colour/utilities/array.py,line=712,col=21,endLine=712,endColumn=40,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`
- ::error file=colour/utilities/array.py,line=753,col=21,endLine=753,endColumn=38,title=Pyrefly missing-attribute::Class `signedinteger` has no class attribute `__args__`%0AClass `unsignedinteger` has no class attribute `__args__`%0A  Did you mean `__abs__`?
- ::error file=colour/utilities/array.py,line=786,col=21,endLine=786,endColumn=40,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`%0AClass `floating` has no class attribute `__args__`%0A  Did you mean `__abs__`?
+ ::error file=colour/utilities/array.py,line=786,col=21,endLine=786,endColumn=40,title=Pyrefly missing-attribute::Class `float64` has no class attribute `__args__`
- ::error file=colour/utilities/array.py,line=900,col=21,endLine=900,endColumn=42,title=Pyrefly missing-attribute::Class `complex128` has no class attribute `__args__`%0AClass `complexfloating` has no class attribute `__args__`%0A  Did you mean `__abs__`?
+ ::error file=colour/utilities/array.py,line=900,col=21,endLine=900,endColumn=42,title=Pyrefly missing-attribute::Class `complex128` has no class attribute `__args__`

static-frame (https://github.com/static-frame/static-frame)
- ERROR static_frame/core/frame.py:5950:75-79: Argument `((Iterable[Any]) -> Sequence[Any]) | ((args: Unknown) -> DataclassInstance) | type[tuple[Any]] | Unknown` is not assignable to parameter `constructor` with type `((Iterable[Any]) -> Sequence[Any]) | type[tuple[Any]]` in function `static_frame.core.type_blocks.TypeBlocks.iter_row_tuples` [bad-argument-type]
+ ERROR static_frame/core/frame.py:5950:75-79: Argument `((Iterable[Any]) -> Sequence[Any]) | ((args: Unknown) -> DataclassInstance) | type[tuple[Any]] | Any` is not assignable to parameter `constructor` with type `((Iterable[Any]) -> Sequence[Any]) | type[tuple[Any]]` in function `static_frame.core.type_blocks.TypeBlocks.iter_row_tuples` [bad-argument-type]
- ERROR static_frame/core/interface.py:1056:22-46: Class `Mapping` has no class attribute `_INTERFACE` [missing-attribute]
- ::error file=static_frame/core/frame.py,line=5950,col=75,endLine=5950,endColumn=79,title=Pyrefly bad-argument-type::Argument `((Iterable[Any]) -> Sequence[Any]) | ((args: Unknown) -> DataclassInstance) | type[tuple[Any]] | Unknown` is not assignable to parameter `constructor` with type `((Iterable[Any]) -> Sequence[Any]) | type[tuple[Any]]` in function `static_frame.core.type_blocks.TypeBlocks.iter_row_tuples`
+ ::error file=static_frame/core/frame.py,line=5950,col=75,endLine=5950,endColumn=79,title=Pyrefly bad-argument-type::Argument `((Iterable[Any]) -> Sequence[Any]) | ((args: Unknown) -> DataclassInstance) | type[tuple[Any]] | Any` is not assignable to parameter `constructor` with type `((Iterable[Any]) -> Sequence[Any]) | type[tuple[Any]]` in function `static_frame.core.type_blocks.TypeBlocks.iter_row_tuples`
- ::error file=static_frame/core/interface.py,line=1056,col=22,endLine=1056,endColumn=46,title=Pyrefly missing-attribute::Class `Mapping` has no class attribute `_INTERFACE`

core (https://github.com/home-assistant/core)
+ ERROR homeassistant/helpers/template/__init__.py:257:36-40: Expected 0 positional arguments, got 1 in function `object.__str__` [bad-argument-count]
+ ::error file=homeassistant/helpers/template/__init__.py,line=257,col=36,endLine=257,endColumn=40,title=Pyrefly bad-argument-count::Expected 0 positional arguments, got 1 in function `object.__str__`

mypy (https://github.com/python/mypy)
- ERROR mypy/typeshed/stdlib/typing.pyi:740:50-76: Expected a type form, got instance of `UnionType | type[tuple[Unknown, ...]]` [not-a-type]
+ ERROR mypy/typeshed/stdlib/typing.pyi:740:50-76: Expected a type form, got instance of `UnionType` [not-a-type]
- ERROR mypy/typeshed/stdlib/typing.pyi:741:51-77: Expected a type form, got instance of `UnionType | type[tuple[Unknown, ...]]` [not-a-type]
+ ERROR mypy/typeshed/stdlib/typing.pyi:741:51-77: Expected a type form, got instance of `UnionType` [not-a-type]
- ERROR mypy/typeshed/stdlib/typing.pyi:744:51-77: Expected a type form, got instance of `UnionType | type[tuple[Unknown, ...]]` [not-a-type]
+ ERROR mypy/typeshed/stdlib/typing.pyi:744:51-77: Expected a type form, got instance of `UnionType` [not-a-type]
- ERROR mypy/typeshed/stdlib/typing.pyi:745:52-78: Expected a type form, got instance of `UnionType | type[tuple[Unknown, ...]]` [not-a-type]
+ ERROR mypy/typeshed/stdlib/typing.pyi:745:52-78: Expected a type form, got instance of `UnionType` [not-a-type]
- ::error file=mypy/typeshed/stdlib/typing.pyi,line=740,col=50,endLine=740,endColumn=76,title=Pyrefly not-a-type::Expected a type form, got instance of `UnionType | type[tuple[Unknown, ...]]`
+ ::error file=mypy/typeshed/stdlib/typing.pyi,line=740,col=50,endLine=740,endColumn=76,title=Pyrefly not-a-type::Expected a type form, got instance of `UnionType`
- ::error file=mypy/typeshed/stdlib/typing.pyi,line=741,col=51,endLine=741,endColumn=77,title=Pyrefly not-a-type::Expected a type form, got instance of `UnionType | type[tuple[Unknown, ...]]`
+ ::error file=mypy/typeshed/stdlib/typing.pyi,line=741,col=51,endLine=741,endColumn=77,title=Pyrefly not-a-type::Expected a type form, got instance of `UnionType`
- ::error file=mypy/typeshed/stdlib/typing.pyi,line=744,col=51,endLine=744,endColumn=77,title=Pyrefly not-a-type::Expected a type form, got instance of `UnionType | type[tuple[Unknown, ...]]`
+ ::error file=mypy/typeshed/stdlib/typing.pyi,line=744,col=51,endLine=744,endColumn=77,title=Pyrefly not-a-type::Expected a type form, got instance of `UnionType`
- ::error file=mypy/typeshed/stdlib/typing.pyi,line=745,col=52,endLine=745,endColumn=78,title=Pyrefly not-a-type::Expected a type form, got instance of `UnionType | type[tuple[Unknown, ...]]`
+ ::error file=mypy/typeshed/stdlib/typing.pyi,line=745,col=52,endLine=745,endColumn=78,title=Pyrefly not-a-type::Expected a type form, got instance of `UnionType`

Copy link
Contributor

@stroxler stroxler left a comment

Choose a reason for hiding this comment

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

Review automatically exported from Phabricator review in Meta.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants