Skip to content

[ty] Use infer_type_expression for validating PEP-613 type aliases#24370

Merged
AlexWaygood merged 1 commit intomainfrom
alex-carl/pep-613
Apr 2, 2026
Merged

[ty] Use infer_type_expression for validating PEP-613 type aliases#24370
AlexWaygood merged 1 commit intomainfrom
alex-carl/pep-613

Conversation

@AlexWaygood
Copy link
Copy Markdown
Member

Summary

Replace our ad-hoc validation of type qualifiers and AST structure of PEP-613 type alias values with a second pass over such values after inference has completed. The second pass uses infer_type_expression, which is much better at handling all possible edge cases of illegal type expressions than the ad-hoc handling we had previously.

This PR also fixes a bug where x: Literal[-3.14] was not detected as an illegal type annotation. Ironically, this was something our previous ad-hoc validation for type aliases did handle, but that infer_type_expression did not!

Co-authored-by: Carl Meyer carl@astral.sh

Test Plan

mdtests extended

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Apr 2, 2026
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Apr 2, 2026

Typing conformance results improved 🎉

The percentage of diagnostics emitted that were expected errors increased from 86.79% to 86.81%. The percentage of expected errors that received a diagnostic increased from 81.72% to 81.90%. The number of fully passing files improved from 70/132 to 71/132.

Summary

How are test cases classified?

Each test case represents one expected error annotation or a group of annotations sharing a tag. Counts are per test case, not per diagnostic — multiple diagnostics on the same line count as one. Required annotations (E) are true positives when ty flags the expected location and false negatives when it does not. Optional annotations (E?) are true positives when flagged but true negatives (not false negatives) when not. Tagged annotations (E[tag]) require ty to flag exactly one of the tagged lines; tagged multi-annotations (E[tag+]) allow any number up to the tag count. Flagging unexpected locations counts as a false positive.

Metric Old New Diff Outcome
True Positives 867 869 +2 ⏫ (✅)
False Positives 132 132 +0
False Negatives 194 192 -2 ⏬ (✅)
Total Diagnostics 1052 1055 +3
Precision 86.79% 86.81% +0.03% ⏫ (✅)
Recall 81.72% 81.90% +0.19% ⏫ (✅)
Passing Files 70/132 71/132 +1 ⏫ (✅)

Test file breakdown

2 files altered
File True Positives False Positives False Negatives Status
aliases_explicit.py 20 (+1) ✅ 0 1 (-1) ✅ 📈 Improving
generics_paramspec_basic.py 7 (+1) ✅ 0 0 (-1) ✅ ✅ Newly Passing 🎉
Total (all files) 869 (+2) ✅ 132 192 (-2) ✅ 71/132

True positives added (2)

2 diagnostics
Test case Diff

aliases_explicit.py:87

+error[invalid-type-form] Variable of type `Literal[3]` is not allowed in a type alias value

generics_paramspec_basic.py:15

+error[invalid-type-form] Bare ParamSpec `P` is not valid in this context in a type expression

True positives changed (14)

14 diagnostics
Test case Diff

aliases_explicit.py:79

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] Function calls are not allowed in type alias values

aliases_explicit.py:80

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] List literals are not allowed in this context in a type alias value

aliases_explicit.py:81

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] Tuple literals are not allowed in this context in a type alias value

aliases_explicit.py:82

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] List comprehensions are not allowed in type alias values

aliases_explicit.py:83

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] Dict literals are not allowed in type alias values

aliases_explicit.py:84

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] Function calls are not allowed in type alias values

aliases_explicit.py:85

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] Only simple names and dotted names can be subscripted in type alias values

aliases_explicit.py:86

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] `if` expressions are not allowed in type alias values

aliases_explicit.py:88

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] Boolean literals are not allowed in this context in a type alias value: Did you mean `typing.Literal[True]`?

aliases_explicit.py:89

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] Int literals are not allowed in this context in a type alias value: Did you mean `typing.Literal[1]`?

aliases_explicit.py:90

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] Boolean operations are not allowed in type alias values

aliases_explicit.py:91

-error[invalid-type-form] Invalid right-hand side for `typing.TypeAlias` assignment
+error[invalid-type-form] F-strings are not allowed in type alias values

classes_classvar.py:78

-error[invalid-type-form] Type qualifiers are not allowed in type alias definitions
+error[invalid-type-form] Type qualifier `typing.ClassVar` is not allowed in type alias values

generics_self_usage.py:108

-error[invalid-type-form] Variable of type `<special-form 'typing.Self'>` is not allowed in a type expression
+error[invalid-type-form] Variable of type `<special-form 'typing.Self'>` is not allowed in a type expression
+error[invalid-type-form] Variable of type `<special-form 'typing.Self'>` is not allowed in a type alias value

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Apr 2, 2026

Memory usage report

Summary

Project Old New Diff Outcome
prefect 716.78MB 716.81MB +0.00% (34.02kB)
flake8 47.99MB 47.99MB -
sphinx 264.61MB 264.61MB -
trio 117.73MB 117.73MB -

Significant changes

Click to expand detailed breakdown

prefect

Name Old New Diff Outcome
infer_scope_types_impl 54.27MB 54.30MB +0.06% (30.86kB)
check_file_impl 17.97MB 17.97MB +0.02% (3.16kB)

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Apr 2, 2026

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-await 0 40 0
invalid-type-form 2 0 0
unsupported-operator 2 0 0
invalid-return-type 0 1 0
Total 4 41 0

Changes in flaky projects detected. Raw diff output excludes flaky projects; see the HTML report for details.

Raw diff:

mypy (https://github.com/python/mypy)
+ mypy/typeshed/stdlib/builtins.pyi:1462:72 error[invalid-type-form] `...` is not allowed in this context in a type alias value
+ mypy/typeshed/stdlib/typing.pyi:944:7 error[invalid-type-form] Invalid subscript of object of type `_SpecialForm` in a type alias value

pydantic (https://github.com/pydantic/pydantic)
+ pydantic/_internal/_utils.py:33:35 error[unsupported-operator] Operator `|` is unsupported between objects of type `<class 'Mapping[int, Any]'>` and `<class 'Mapping[str, Any]'>`
+ pydantic/_internal/_utils.py:34:36 error[unsupported-operator] Operator `|` is unsupported between objects of type `<class 'AbstractSet[int]'>` and `<class 'AbstractSet[str]'>`

Full report with detailed diff (timing results)

@AlexWaygood
Copy link
Copy Markdown
Member Author

I'm pretty sure the mypy hits are just things getting weird because we don't recognise mypy's vendored typeshed as the "real" typeshed, like we do our own vendored typeshed.

The pydantic hits are strictly-speaking false positives because they're using | in an if TYPE_CHECKING block that won't be executed at runtime. But they already have pyright: ignores on those lines, so I don't think those false positives need to block this PR. The false positives will be naturally fixed if we implement astral-sh/ty#1553, which should be trivial following the improvements @sharkdp made in #24179

@AlexWaygood
Copy link
Copy Markdown
Member Author

Hmm, it is worth noting that there are regressions of up to 4% on some real-world benchmarks: https://codspeed.io/astral-sh/ruff/branches/alex-carl/pep-613?utm_source=github&utm_medium=check&utm_content=button&page=2. I guess pydantic uses a lot of PEP-613 type aliases? We only do the double-inference for PEP-613 aliases defined in first-party files.

@AlexWaygood AlexWaygood marked this pull request as ready for review April 2, 2026 18:21
Copy link
Copy Markdown
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.

We need this validation, and I don't think we can get it (in an accurate way) without some performance cost on TypeAlias-heavy codebases.

@AlexWaygood AlexWaygood merged commit c18f449 into main Apr 2, 2026
52 checks passed
@AlexWaygood AlexWaygood deleted the alex-carl/pep-613 branch April 2, 2026 20:53
carljm added a commit that referenced this pull request Apr 3, 2026
* main:
  Document adding fixes in CONTRIBUTING.md (#24393)
  Sort formatter diagnostics in snapshots (#24375)
  [`pyupgrade`] Fix panic caused by handling of octals in `UP012` (#24390)
  Upgrade to nix v0.31.2 (#24385)
  Strip form feeds from indent passed to `dedent_to` (#24381)
  add recent move of the `deferred` submodule to `.git-blame-ignore-revs` (#24379)
  [ty] Fix extra_items TypedDict tests (#24367)
  [ty] Use `infer_type_expression` for validating PEP-613 type aliases (#24370)
  [`flake8-simplify`] Make the fix for `collapsible-if` (`SIM102`) safe in `preview` (#24371)
  [ty] Validate TypedDict fields when subclassing (#24338)
  [ty] pass type context to sequence literals in binary operations (#24197)
  Add release environment to notify-dependents job (#24372)
  Bump 0.15.9 (#24369)
  [ty] Move the `deferred` submodule inside `infer/builder` (#24368)
  [ty] Infer the `extra_items` keyword argument to class-based TypedDicts as an annotation expression (#24362)
  [ty] Validate type qualifiers in functional TypedDict fields and the `extra_items` keyword to functional TypedDicts (#24360)
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