Skip to content

[ty] Implement implicit inheritance from Generic[] for PEP-695 generic classes #18283

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

Merged
merged 2 commits into from
May 26, 2025

Conversation

AlexWaygood
Copy link
Member

Summary

PEP-695 classes have Generic implicitly appended to the end of their __bases__:

>>> class Foo[T]: ...
... 
>>> Foo.__mro__
(<class '__main__.Foo'>, <class 'typing.Generic'>, <class 'object'>)
>>> class Bar[T](int): ...
... 
>>> Bar.__mro__
(<class '__main__.Bar'>, <class 'int'>, <class 'typing.Generic'>, <class 'object'>)

This isn't something we fully model on main, leading us to infer inaccurate MROs for PEP-695 generic classes in some situations. This PR fixes that.

Test Plan

mdtests

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label May 23, 2025
Copy link
Contributor

github-actions bot commented May 23, 2025

mypy_primer results

Changes were detected when running on open source projects
pytest-robotframework (https://github.com/detachhead/pytest-robotframework)
- error[invalid-argument-type] pytest_robotframework/_internal/robot/utils.py:158:15: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] pytest_robotframework/_internal/robot/utils.py:158:15: `Unknown` is not a valid argument to `Generic`

starlette (https://github.com/encode/starlette)
- error[invalid-argument-type] starlette/middleware/__init__.py:17:26: `ParamSpec` is not a valid argument to subscripted `typing.Protocol`
+ error[invalid-argument-type] starlette/middleware/__init__.py:17:26: `ParamSpec` is not a valid argument to `Protocol`

comtypes (https://github.com/enthought/comtypes)
- error[invalid-argument-type] comtypes/hints.pyi:150:33: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] comtypes/hints.pyi:150:33: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] comtypes/hints.pyi:160:28: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] comtypes/hints.pyi:160:28: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] comtypes/hints.pyi:174:34: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] comtypes/hints.pyi:174:34: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] comtypes/hints.pyi:183:29: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] comtypes/hints.pyi:183:29: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] comtypes/hints.pyi:196:34: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] comtypes/hints.pyi:196:34: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] comtypes/hints.pyi:205:29: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] comtypes/hints.pyi:205:29: `ParamSpec` is not a valid argument to `Generic`

asynq (https://github.com/quora/asynq)
- error[invalid-argument-type] asynq/decorators.pyi:35:70: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] asynq/decorators.pyi:35:70: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] asynq/decorators.pyi:39:58: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] asynq/decorators.pyi:39:58: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] asynq/decorators.pyi:70:62: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] asynq/decorators.pyi:70:62: `ParamSpec` is not a valid argument to `Generic`

hydra-zen (https://github.com/mit-ll-responsible-ai/hydra-zen)
- error[invalid-argument-type] src/hydra_zen/typing/_implementations.py:139:32: `ParamSpec` is not a valid argument to subscripted `typing.Protocol`
+ error[invalid-argument-type] src/hydra_zen/typing/_implementations.py:139:32: `ParamSpec` is not a valid argument to `Protocol`
- error[invalid-argument-type] src/hydra_zen/wrapper/_implementations.py:110:11: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] src/hydra_zen/wrapper/_implementations.py:110:11: `ParamSpec` is not a valid argument to `Generic`

psycopg (https://github.com/psycopg/psycopg)
- error[invalid-argument-type] psycopg_pool/psycopg_pool/pool.py:38:22: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] psycopg_pool/psycopg_pool/pool.py:38:22: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] psycopg_pool/psycopg_pool/pool.py:814:21: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] psycopg_pool/psycopg_pool/pool.py:814:21: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] psycopg_pool/psycopg_pool/pool_async.py:38:27: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] psycopg_pool/psycopg_pool/pool_async.py:38:27: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] psycopg_pool/psycopg_pool/pool_async.py:868:21: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] psycopg_pool/psycopg_pool/pool_async.py:868:21: `Unknown` is not a valid argument to `Generic`

Expression (https://github.com/cognitedata/Expression)
- error[invalid-argument-type] expression/core/fn.py:14:16: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] expression/core/fn.py:14:16: `ParamSpec` is not a valid argument to `Generic`

mkdocs (https://github.com/mkdocs/mkdocs)
- error[invalid-argument-type] mkdocs/plugins.py:460:21: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mkdocs/plugins.py:460:21: `ParamSpec` is not a valid argument to `Generic`

mitmproxy (https://github.com/mitmproxy/mitmproxy)
- error[invalid-argument-type] mitmproxy/utils/signals.py:68:19: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mitmproxy/utils/signals.py:68:19: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] mitmproxy/utils/signals.py:81:20: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mitmproxy/utils/signals.py:81:20: `ParamSpec` is not a valid argument to `Generic`

static-frame (https://github.com/static-frame/static-frame)
- error[invalid-argument-type] static_frame/core/frame.py:239:31: `@Todo(Inference of subscript on special form)` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] static_frame/core/frame.py:239:31: `@Todo(Inference of subscript on special form)` is not a valid argument to `Generic`
- error[invalid-argument-type] static_frame/core/index_hierarchy.py:234:33: `@Todo(Inference of subscript on special form)` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] static_frame/core/index_hierarchy.py:234:33: `@Todo(Inference of subscript on special form)` is not a valid argument to `Generic`

mypy (https://github.com/python/mypy)
- error[invalid-argument-type] mypy/typeshed/stdlib/asyncio/tasks.pyi:431:34: `Unknown` is not a valid argument to subscripted `typing.Protocol`
+ error[invalid-argument-type] mypy/typeshed/stdlib/asyncio/tasks.pyi:431:34: `Unknown` is not a valid argument to `Protocol`
- error[invalid-argument-type] mypy/typeshed/stdlib/asyncio/tasks.pyi:443:33: `Unknown` is not a valid argument to subscripted `typing.Protocol`
+ error[invalid-argument-type] mypy/typeshed/stdlib/asyncio/tasks.pyi:443:33: `Unknown` is not a valid argument to `Protocol`
- error[invalid-argument-type] mypy/typeshed/stdlib/builtins.pyi:139:20: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mypy/typeshed/stdlib/builtins.pyi:139:20: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] mypy/typeshed/stdlib/builtins.pyi:156:19: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mypy/typeshed/stdlib/builtins.pyi:156:19: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] mypy/typeshed/stdlib/builtins.pyi:1999:45: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mypy/typeshed/stdlib/builtins.pyi:1999:45: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] mypy/typeshed/stdlib/contextlib.pyi:214:53: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mypy/typeshed/stdlib/contextlib.pyi:214:53: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] mypy/typeshed/stdlib/functools.pyi:86:16: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mypy/typeshed/stdlib/functools.pyi:86:16: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] mypy/typeshed/stdlib/functools.pyi:93:16: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mypy/typeshed/stdlib/functools.pyi:93:16: `ParamSpec` is not a valid argument to `Generic`
- error[invalid-argument-type] mypy/typeshed/stdlib/unittest/case.pyi:73:52: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mypy/typeshed/stdlib/unittest/case.pyi:73:52: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] mypy/typeshed/stdlib/weakref.pyi:189:16: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] mypy/typeshed/stdlib/weakref.pyi:189:16: `ParamSpec` is not a valid argument to `Generic`

streamlit (https://github.com/streamlit/streamlit)
- error[invalid-argument-type] lib/streamlit/elements/widgets/button_group.py:94:25: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] lib/streamlit/elements/widgets/button_group.py:94:25: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] lib/streamlit/elements/widgets/button_group.py:117:26: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] lib/streamlit/elements/widgets/button_group.py:117:26: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] lib/streamlit/elements/widgets/button_group.py:153:24: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] lib/streamlit/elements/widgets/button_group.py:153:24: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] lib/streamlit/elements/widgets/multiselect.py:65:24: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] lib/streamlit/elements/widgets/multiselect.py:65:24: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] lib/streamlit/elements/widgets/radio.py:61:18: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] lib/streamlit/elements/widgets/radio.py:61:18: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] lib/streamlit/elements/widgets/select_slider.py:76:25: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] lib/streamlit/elements/widgets/select_slider.py:76:25: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] lib/streamlit/elements/widgets/selectbox.py:64:22: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] lib/streamlit/elements/widgets/selectbox.py:64:22: `Unknown` is not a valid argument to `Generic`
- error[invalid-argument-type] lib/streamlit/testing/v1/element_tree.py:1120:22: `Unknown` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] lib/streamlit/testing/v1/element_tree.py:1120:22: `Unknown` is not a valid argument to `Generic`

zulip (https://github.com/zulip/zulip)
- error[invalid-argument-type] zerver/lib/cache.py:745:39: `ParamSpec` is not a valid argument to `typing.Generic`
+ error[invalid-argument-type] zerver/lib/cache.py:745:39: `ParamSpec` is not a valid argument to `Generic`

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.

Excellent, thank you for catching this!

@AlexWaygood AlexWaygood force-pushed the alex/695-generic-base branch from c167e47 to d8a1519 Compare May 26, 2025 19:27
@AlexWaygood
Copy link
Member Author

This change means that we now naturally recognize classes such as class Foo[T](Generic[T]): ... at MRO-resolution time, so I was able to reimplement the diagnostic telling users off for doing that as an MroError variant. This enabled me to push some simplifications to the GenericContext struct (we no longer need to record whether the context came from typing.Generic or typing.Protocol inheritance), which in turn has led to some small speedups in the benchmarks 🎉 https://codspeed.io/astral-sh/ruff/branches/alex%2F695-generic-base

@AlexWaygood AlexWaygood merged commit 0a11baf into main May 26, 2025
35 checks passed
@AlexWaygood AlexWaygood deleted the alex/695-generic-base branch May 26, 2025 19:40
carljm added a commit to MatthewMckee4/ruff that referenced this pull request May 28, 2025
* main: (246 commits)
  [ty] Simplify signature types, use them in `CallableType` (astral-sh#18344)
  [ty] Support ephemeral uv virtual environments (astral-sh#18335)
  Add a `ViolationMetadata::rule` method (astral-sh#18234)
  Return `DiagnosticGuard` from `Checker::report_diagnostic` (astral-sh#18232)
  [flake8_use_pathlib]: Replace os.symlink with Path.symlink_to (PTH211) (astral-sh#18337)
  [ty] Support cancellation and retry in the server (astral-sh#18273)
  [ty] Synthetic function-like callables (astral-sh#18242)
  [ty] Support publishing diagnostics in the server (astral-sh#18309)
  Add Autofix for ISC003 (astral-sh#18256)
  [`pyupgrade`]: new rule UP050 (`useless-class-metaclass-type`) (astral-sh#18334)
  [pycodestyle] Make `E712` suggestion not assume a context (astral-sh#18328)
  put similar dunder-call tests next to each other (astral-sh#18343)
  [ty] Derive `PartialOrd, Ord` for `KnownInstanceType` (astral-sh#18340)
  [ty] Simplify `Type::try_bool()` (astral-sh#18342)
  [ty] Simplify `Type::normalized` slightly (astral-sh#18339)
  [ty] Move arviz off the list of selected primer projects (astral-sh#18336)
  [ty] Add --config-file CLI arg (astral-sh#18083)
  [ty] Tell the user why we inferred a certain Python version when reporting version-specific syntax errors (astral-sh#18295)
  [ty] Implement implicit inheritance from `Generic[]` for PEP-695 generic classes (astral-sh#18283)
  [ty] Add hint if async context manager is used in non-async with statement (astral-sh#18299)
  ...
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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants