-
Notifications
You must be signed in to change notification settings - Fork 261
Model GenericAlias runtime type #2251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
`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).
| 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]: |
There was a problem hiding this comment.
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 😮
|
cc @yangdanny97 over previous discussion on discord |
This comment has been minimized.
This comment has been minimized.
|
I think I introduced a regression for comparing generic aliases (now |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
pyrefly/lib/test/simple.rs
Outdated
| // which makes this test vacuously correct. | ||
| testcase!( | ||
| test_getitem_cannot_iterate_generic_class, | ||
| test_getitem_can_iterate_generic_class, |
There was a problem hiding this comment.
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
|
@stroxler Thanks for the example, that was something I didn't consider. I've added the |
|
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`
|
stroxler
left a comment
There was a problem hiding this 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.
Summary
GenericAliasis 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.pywith new test case.