Skip to content

Recognize quoted types in typing.TypeForm - #28507

Merged
ntBre merged 2 commits into
mainfrom
brent/typeform
Sep 11, 2026
Merged

ntBre merged 2 commits into
mainfrom
brent/typeform

Conversation

@ntBre

@ntBre ntBre commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

typing.TypeForm was introduced in Python 3.15 and PEP 747 as
a way to annotate the passing of type objects like int | str, list[int], or MyTypeAlias. It
can be used either as a subscripted generic, as in this example from the PEP:

from typing import TypeForm

def trycast[T](typx: TypeForm[T], value: object) -> T | None: ...

or as a callable, which returns its argument unchanged:

from typing import TypeForm

TypeForm("list[int]")

This PR recognizes typing.TypeForm as a standard-library generic and as a known typing callable
and visits its argument or subscript as a type definition.

While I was reading the typing docs, I realized that a couple of nearby types were also not
listed, so I've attempted to sneak them in here but could split them off if needed. We already had
coverage for typing.TypeGuard but not typing_extensions.TypeGuard (added in 3.10), and we were
missing both variants of TypeIs (added in 3.13). These are only valid in the subscript form, so
they don't need new Callable entries.

Test Plan

New mdtests, using unused-import (F401) to demonstrate that visiting these arguments as type
definitions marks them as used, but other rules will be affected as well.

Summary
--

`typing.TypeForm` was introduced in Python 3.15 and [PEP 747](https://peps.python.org/pep-0747/) as
a way to annotate the passing of type objects like `int | str`, `list[int]`, or `MyTypeAlias`. It
can be used either as a subscripted generic, as in this example from the PEP:

```py
from typing import TypeForm

def trycast[T](typx: TypeForm[T], value: object) -> T | None: ...
```

or as a callable, which returns its argument unchanged:

```py
from typing import TypeForm

TypeForm("list[int]")
```

This PR recognizes `typing.TypeForm` as a standard-library generic and as a known `typing` callable
and visits its argument or subscript as a type definition.

While I was reading the [typing docs], I realized that a couple of nearby types were also not
listed, so I've attempted to sneak them in here but could split them off if needed. We already had
coverage for `typing.TypeGuard` but not `typing_extensions.TypeGuard` (added in 3.10), and we were
missing both variants of `TypeIs` (added in 3.13). These are only valid in the subscript form, so
they don't need new `Callable` entries.

[typing docs]: https://docs.python.org/3.15/library/typing.html#typing.TypeForm

Test Plan
--

New mdtests, using `unused-import` (`F401`) to demonstrate that visiting these arguments as type
definitions marks them as used.
@ntBre ntBre added rule Implementing or modifying a lint rule python315 Related to Python 3.15 labels Sep 10, 2026
@ntBre ntBre mentioned this pull request Sep 10, 2026
27 of 32 tasks
@astral-sh-bot

astral-sh-bot Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Full report with detailed diff (timing results)

@ntBre
ntBre marked this pull request as ready for review September 10, 2026 21:01
@astral-sh-bot

astral-sh-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@MichaReiser
MichaReiser self-requested a review September 11, 2026 06:09
| "Type"
| "TypeForm"
| "TypeGuard"
| "TypeIs"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we also add some tests for the other newly added generic types?

@ntBre
ntBre enabled auto-merge (squash) September 11, 2026 14:05
@ntBre
ntBre merged commit 652bfea into main Sep 11, 2026
57 checks passed
@ntBre
ntBre deleted the brent/typeform branch September 11, 2026 14:12
carljm added a commit that referenced this pull request Sep 11, 2026
…aliases

* origin/main: (30 commits)
  [ty] Respect instance dictionary storage for slotted classes (#27749)
  Add a GitHub repository threat model for Ruff (#28395)
  [ty] Preserve wrapped signatures in nominal descriptor checks (#28466)
  [ty] Avoid rebinding extracted method calls (#28469)
  Recognize quoted types in `typing.TypeForm` (#28507)
  Support conditional assignment to `__lazy_modules__` (#28491)
  Add support for `__lazy_modules__` (#28459)
  [`pyupgrade`] Stop recommending deprecated `ByteString` aliases (`UP035`) (#28498)
  [ty] Anchor default exclude patterns at the project root (#28463)
  [ty] Add playground command to toggle inlay hints (#28517)
  [ty] Ignore divergent markers when detecting descriptors (#28514)
  [ty] Reuse rendered union elements when displaying types (#28494)
  [ty] Fix assignability of bounded typevars to intersection types (#28479)
  [ty] Retain package listings between resolution steps (#28278)
  Add accessors for comparison expressions (#28485)
  [ty] Refactor module resolution to use a `ModuleDirectory` abstraction. (#28418)
  [ty] Reduce retained AST memory by shrinking expressions (#28335)
  Bump version to 0.16.7 (#28496)
  Install rustfmt before linting releases (#28495)
  ensure prepare release changes pass prek (#28488)
  ...

# Conflicts:
#	crates/ty_python_semantic/src/types/bool.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python315 Related to Python 3.15 rule Implementing or modifying a lint rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants