Skip to content

[ty] Avoid emitting cascading diagnostics when parsing invalid type expressions#24326

Merged
AlexWaygood merged 6 commits intomainfrom
alex/cascading-type-expr-diagnostics
Apr 1, 2026
Merged

[ty] Avoid emitting cascading diagnostics when parsing invalid type expressions#24326
AlexWaygood merged 6 commits intomainfrom
alex/cascading-type-expr-diagnostics

Conversation

@AlexWaygood
Copy link
Copy Markdown
Member

@AlexWaygood AlexWaygood commented Mar 31, 2026

Summary

In lots of places in our type-expression parsing, we continue to call self.infer_type_expression() on sub-expressions in the AST even after we've already determined that the type expression is invalid. I think that's generally a mistake; it often leads to us emitting many diagnostics on a single type expression when one would really be sufficient. This PR switches many callsites from infer_type_expression to infer_expression, to avoid this phenomenon of cascading diagnostics in error cases.

Test Plan

Mdtests and snapshots updated.

@AlexWaygood AlexWaygood added ty Multi-file analysis & type inference diagnostics Related to reporting of diagnostics. labels Mar 31, 2026
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Mar 31, 2026

Typing conformance results improved 🎉

The percentage of diagnostics emitted that were expected errors increased from 86.61% to 86.79%. The percentage of expected errors that received a diagnostic held steady at 81.56%. The number of fully passing files held steady at 70/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 867 +0
False Positives 134 132 -2 ⏬ (✅)
False Negatives 196 196 +0
Total Diagnostics 1064 1054 -10
Precision 86.61% 86.79% +0.17% ⏫ (✅)
Recall 81.56% 81.56% +0.00%
Passing Files 70/132 70/132 +0

Test file breakdown

2 files altered
File True Positives False Positives False Negatives Status
generics_defaults.py 5 8 (-1) ✅ 1 📈 Improving
generics_typevartuple_specialization.py 2 15 (-1) ✅ 4 📈 Improving
Total (all files) 867 132 (-2) ✅ 196 70/132

False positives removed (2)

2 diagnostics
Test case Diff

generics_defaults.py:203

-error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `list[bytes]`?

generics_typevartuple_specialization.py:52

-error[invalid-type-form] Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?

True positives changed (14)

14 diagnostics
Test case Diff

aliases_type_statement.py:38

-error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
+error[invalid-type-form] List literals are not allowed in this context in a type expression

aliases_type_statement.py:39

-error[invalid-type-form] Tuple literals are not allowed in this context in a type expression
-error[invalid-type-form] Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
+error[invalid-type-form] Tuple literals are not allowed in this context in a type expression

aliases_type_statement.py:43

-error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression
-error[invalid-type-form] Int literals are not allowed in this context in a type expression
+error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression

aliases_typealiastype.py:53

-error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
+error[invalid-type-form] List literals are not allowed in this context in a type expression

aliases_typealiastype.py:54

-error[invalid-type-form] Tuple literals are not allowed in this context in a type expression
-error[invalid-type-form] Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
+error[invalid-type-form] Tuple literals are not allowed in this context in a type expression

aliases_typealiastype.py:58

-error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression
-error[invalid-type-form] Int literals are not allowed in this context in a type expression
+error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression

annotations_forward_refs.py:42

-error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
+error[invalid-type-form] List literals are not allowed in this context in a type expression

annotations_forward_refs.py:47

-error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression
-error[invalid-type-form] Int literals are not allowed in this context in a type expression
+error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression

annotations_typeexpr.py:89

-error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
+error[invalid-type-form] List literals are not allowed in this context in a type expression

annotations_typeexpr.py:94

-error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression
-error[invalid-type-form] Int literals are not allowed in this context in a type expression
+error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression

generics_syntax_declarations.py:48

-error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[str, int]`?
+error[invalid-type-form] List literals are not allowed in this context in a type expression

qualifiers_annotated.py:38

-error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
+error[invalid-type-form] List literals are not allowed in this context in a type expression

qualifiers_annotated.py:39

-error[invalid-type-form] Tuple literals are not allowed in this context in a type expression
-error[invalid-type-form] Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
+error[invalid-type-form] Tuple literals are not allowed in this context in a type expression

qualifiers_annotated.py:43

-error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression
-error[invalid-type-form] Int literals are not allowed in this context in a type expression
+error[invalid-type-form] Invalid subscript of object of type `list[<class 'int'>]` in type expression

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Mar 31, 2026

Memory usage report

Summary

Project Old New Diff Outcome
trio 117.72MB 117.72MB +0.00% (313.00B)
flake8 47.98MB 47.98MB -
sphinx 264.60MB 264.60MB -0.00% (472.00B) ⬇️
prefect 716.58MB 716.49MB -0.01% (92.85kB) ⬇️

Significant changes

Click to expand detailed breakdown

trio

Name Old New Diff Outcome
is_redundant_with_impl::interned_arguments 537.62kB 536.77kB -0.16% (880.00B)
check_file_impl 1.82MB 1.82MB +0.05% (861.00B)
Type<'db>::class_member_with_policy_ 2.00MB 2.00MB +0.03% (608.00B)
infer_deferred_types 2.38MB 2.38MB -0.02% (564.00B)
is_redundant_with_impl 473.21kB 472.70kB -0.11% (528.00B)
FunctionType<'db>::last_definition_signature_ 241.23kB 240.75kB -0.20% (496.00B)
infer_expression_types_impl 7.08MB 7.08MB +0.01% (496.00B)
infer_definition_types 7.73MB 7.73MB -0.01% (492.00B)
UnionType 304.73kB 304.28kB -0.15% (464.00B)
BoundTypeVarInstance 163.27kB 162.98kB -0.17% (288.00B)
place_by_id 550.84kB 551.08kB +0.04% (248.00B)
Type<'db>::member_lookup_with_policy_ 1.81MB 1.81MB +0.01% (224.00B)
StaticClassLiteral<'db>::implicit_attribute_inner_ 750.54kB 750.76kB +0.03% (224.00B)
StaticClassLiteral<'db>::try_metaclass_ 126.79kB 127.00kB +0.16% (208.00B)
Type<'db>::class_member_with_policy_::interned_arguments 1.11MB 1.11MB +0.02% (208.00B)
... 16 more

sphinx

Name Old New Diff Outcome
infer_deferred_types 5.61MB 5.61MB -0.00% (248.00B) ⬇️
TupleType 568.55kB 568.42kB -0.02% (128.00B) ⬇️
infer_definition_types 23.98MB 23.98MB -0.00% (96.00B) ⬇️

prefect

Name Old New Diff Outcome
infer_deferred_types 14.75MB 14.72MB -0.22% (32.62kB) ⬇️
check_file_impl 17.97MB 17.96MB -0.08% (14.25kB) ⬇️
infer_scope_types_impl 54.24MB 54.22MB -0.02% (13.06kB) ⬇️
TupleType 717.94kB 711.33kB -0.92% (6.61kB) ⬇️
infer_definition_types 89.94MB 89.93MB -0.01% (5.43kB) ⬇️
Specialization 2.50MB 2.50MB -0.13% (3.38kB) ⬇️
Type<'db>::class_member_with_policy_ 17.76MB 17.76MB -0.02% (3.07kB) ⬇️
Type<'db>::member_lookup_with_policy_ 16.22MB 16.21MB -0.01% (2.03kB) ⬇️
GenericAlias 1.16MB 1.15MB -0.14% (1.69kB) ⬇️
place_by_id 4.60MB 4.60MB -0.03% (1.62kB) ⬇️
BoundTypeVarInstance 1.38MB 1.38MB -0.10% (1.48kB) ⬇️
Type<'db>::apply_specialization_::interned_arguments 2.89MB 2.89MB -0.04% (1.09kB) ⬇️
place_by_id::interned_arguments 3.39MB 3.39MB -0.03% (1.05kB) ⬇️
Type<'db>::class_member_with_policy_::interned_arguments 9.61MB 9.61MB -0.01% (1.02kB) ⬇️
Type<'db>::member_lookup_with_policy_::interned_arguments 5.78MB 5.78MB -0.02% (1.02kB) ⬇️
... 13 more

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Mar 31, 2026

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-type-form 0 135 1
invalid-await 0 40 0
not-subscriptable 13 0 0
possibly-missing-submodule 0 4 0
unsupported-operator 3 0 0
invalid-return-type 0 1 0
unbound-type-variable 0 1 0
Total 16 181 1

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

Raw diff (157 changes)
django-stubs (https://github.com/typeddjango/django-stubs)
- django-stubs/contrib/gis/gdal/geometries.pyi:120:55 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:123:31 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:123:44 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:123:50 error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:123:31 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
- django-stubs/contrib/gis/gdal/geometries.pyi:125:30 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:125:43 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:125:49 error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:125:30 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
- django-stubs/contrib/gis/gdal/geometries.pyi:145:31 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:145:37 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:145:50 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:145:56 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:145:62 error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:145:31 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
+ django-stubs/contrib/gis/gdal/geometries.pyi:145:37 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
- django-stubs/contrib/gis/gdal/geometries.pyi:147:30 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:147:36 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:147:49 error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:147:30 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
+ django-stubs/contrib/gis/gdal/geometries.pyi:147:36 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
- django-stubs/contrib/gis/gdal/geometries.pyi:163:36 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/gdal/geometries.pyi:165:35 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/geos/collections.pyi:11:36 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/geos/collections.pyi:13:35 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/geos/coordseq.pyi:10:49 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/geos/coordseq.pyi:32:35 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/geos/linestring.pyi:9:49 error[invalid-type-form] `...` is not allowed in this context in a type expression
- django-stubs/contrib/gis/geos/linestring.pyi:12:31 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/geos/linestring.pyi:12:44 error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/linestring.pyi:12:31 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
- django-stubs/contrib/gis/geos/linestring.pyi:14:30 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/geos/linestring.pyi:14:43 error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/linestring.pyi:14:30 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
- django-stubs/contrib/gis/geos/polygon.pyi:18:31 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/geos/polygon.pyi:18:37 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/geos/polygon.pyi:18:50 error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/polygon.pyi:18:31 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
+ django-stubs/contrib/gis/geos/polygon.pyi:18:37 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
- django-stubs/contrib/gis/geos/polygon.pyi:20:30 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/geos/polygon.pyi:20:36 error[invalid-type-form] Invalid subscript of object of type `property` in type expression
- django-stubs/contrib/gis/geos/polygon.pyi:20:49 error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/polygon.pyi:20:30 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method
+ django-stubs/contrib/gis/geos/polygon.pyi:20:36 error[not-subscriptable] Cannot subscript object of type `property` with no `__getitem__` method

egglog-python (https://github.com/egraphs-good/egglog-python)
- python/egglog/builtins.py:542:95 error[invalid-type-form] Int literals are not allowed in this context in a type expression
- python/egglog/builtins.py:666:105 error[invalid-type-form] Int literals are not allowed in this context in a type expression
- python/egglog/builtins.py:1060:99 error[invalid-type-form] Int literals are not allowed in this context in a type expression
- python/egglog/thunk.py:41:37 error[unbound-type-variable] Type variable `T` is not bound to any outer generic context
- python/tests/test_convert.py:117:51 error[invalid-type-form] Int literals are not allowed in this context in a type expression
- python/tests/test_convert.py:140:49 error[invalid-type-form] Int literals are not allowed in this context in a type expression

mypy (https://github.com/python/mypy)
- mypy/typeshed/stdlib/typing.pyi:529:36 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:535:36 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:540:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:564:37 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:564:49 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:564:64 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:581:48 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:618:42 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:623:38 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:624:42 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:628:48 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:635:30 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:640:30 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:661:68 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:666:36 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:667:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:677:75 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:683:69 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:693:39 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:697:41 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:705:42 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:705:65 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:710:66 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:712:66 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:726:39 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:728:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:758:36 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:769:36 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:788:34 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:788:39 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:789:32 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:790:36 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:905:42 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:909:36 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:911:31 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:418:53 error[invalid-type-form] Bare ParamSpec `_P` is not valid in this context in a type expression
- mypy/typeshed/stdlib/typing.pyi:418:57 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:418:74 error[invalid-type-form] Bare ParamSpec `_P` is not valid in this context in a type expression
- mypy/typeshed/stdlib/typing.pyi:418:78 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:630:69 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:711:41 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:711:64 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:711:72 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
+ mypy/typeshed/stdlib/typing.pyi:711:64 error[unsupported-operator] Operator `|` is not supported between two objects of type `TypeVar`
- mypy/typeshed/stdlib/typing.pyi:713:42 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:713:65 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:713:73 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
+ mypy/typeshed/stdlib/typing.pyi:713:65 error[unsupported-operator] Operator `|` is not supported between two objects of type `TypeVar`
- mypy/typeshed/stdlib/typing.pyi:739:42 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:741:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:744:38 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:745:39 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:747:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:748:39 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:749:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:754:42 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:756:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:759:38 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:760:39 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:762:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:763:39 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:764:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:814:41 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypy/typeshed/stdlib/typing.pyi:814:46 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
+ mypy/typeshed/stdlib/typing.pyi:814:46 error[unsupported-operator] Operator `|` is not supported between objects of type `TypeVar` and `None`
- mypy/typeshed/stdlib/typing_extensions.pyi:647:25 error[invalid-type-form] Variable of type `Literal[Format.STRING]` is not allowed in a type expression
- mypy/typeshed/stdlib/typing_extensions.pyi:656:25 error[invalid-type-form] Variable of type `Literal[Format.FORWARDREF]` is not allowed in a type expression
- mypy/typeshed/stdlib/typing_extensions.pyi:675:25 error[invalid-type-form] Variable of type `Literal[Format.STRING]` is not allowed in a type expression
- mypy/typeshed/stdlib/typing_extensions.pyi:686:25 error[invalid-type-form] Variable of type `Literal[Format.FORWARDREF]` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:64:37 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:82:38 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:82:41 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:82:44 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:86:38 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:95:35 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:98:44 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:98:47 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:103:38 error[invalid-type-form] Variable of type `object` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:103:43 error[invalid-type-form] Variable of type `object` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:103:48 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:121:43 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:125:43 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:127:38 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:134:32 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:140:34 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:89:44 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:92:74 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:137:37 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:139:40 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:139:46 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:139:59 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:139:65 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:141:33 error[invalid-type-form] Invalid subscript of object of type `Literal[0]` in type expression
- mypyc/test-data/fixtures/typing-full.pyi:141:39 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
- mypyc/test-data/fixtures/typing-full.pyi:141:42 error[invalid-type-form] Variable of type `TypeVar` is not allowed in a type expression
+ mypyc/test-data/fixtures/typing-full.pyi:141:33 error[not-subscriptable] Cannot subscript object of type `Literal[0]` with no `__getitem__` method

pandas (https://github.com/pandas-dev/pandas)
- pandas/core/series.py:324:21 error[invalid-type-form] Variable of type `Accessor` is not allowed in a type expression

pydantic (https://github.com/pydantic/pydantic)
- pydantic/v1/annotated_types.py:13:50 error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions
- pydantic/v1/annotated_types.py:24:26 error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions
- pydantic/v1/validators.py:621:26 error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions

spack (https://github.com/spack/spack)
- lib/spack/spack/detection/path.py:228:45 warning[possibly-missing-submodule] Submodule `package_base` might not have been imported
- lib/spack/spack/detection/path.py:254:29 warning[possibly-missing-submodule] Submodule `package_base` might not have been imported
- lib/spack/spack/detection/path.py:360:45 warning[possibly-missing-submodule] Submodule `package_base` might not have been imported
- lib/spack/spack/detection/path.py:386:45 warning[possibly-missing-submodule] Submodule `package_base` might not have been imported

static-frame (https://github.com/static-frame/static-frame)
- static_frame/test/unit/test_type_clinic.py:459:21 error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, int]`?
+ static_frame/test/unit/test_type_clinic.py:459:21 error[invalid-type-form] List literals are not allowed in this context in a type expression

xarray (https://github.com/pydata/xarray)
- xarray/core/indexing.py:427:40 error[invalid-type-form] `...` is not allowed in this context in a type expression
- xarray/core/indexing.py:433:35 error[invalid-type-form] `...` is not allowed in this context in a type expression

Full report with detailed diff (timing results)

@AlexWaygood AlexWaygood marked this pull request as ready for review March 31, 2026 12:13
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.

Results look good. I would like to understand the subscript change better.

Comment on lines +463 to +470
if matches!(
&*subscript.value,
ast::Expr::Name(_) | ast::Expr::Attribute(_)
) {
builder.infer_type_expression(expr)
} else {
builder.infer_expression(expr, TypeContext::default())
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This seems deserving of a comment to explain the rationale for why different expressions should be treated differently. This doesn't seem to match the logic described in the PR summary, in that we aren't deciding to stop using infer_type_expression here due to an outer invalid-type-expression error.

Unless the rationale here is that a subscript should always be a name/attribute, and anything else will be an error? But if that's it, then couldn't this cause us to miss emitting an error we should emit?

There's also a similar (non-?)issue here as in other places, where we'll now emit value-expression errors on the subscript expression that we wouldn't have before, e.g. unresolved-reference in def f(x: "list[int][foo()]") -> None: ....

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Unless the rationale here is that a subscript should always be a name/attribute, and anything else will be an error? But if that's it, then couldn't this cause us to miss emitting an error we should emit?

I think that's what we should be doing, yes (see #24329).

I'm inclined to rip this change out of this PR for now, though, and maybe include it as part of #24329 instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've ripped out all changes to builder/subscript.rs for now.

.iter()
.map(|element| self.infer_type_expression(element))
.collect();
self.infer_list_expression(list, TypeContext::default());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In some scenarios this adds new diagnostics that we don't emit on main, e.g.

def f(x: "[foo()]") -> None: ...

This PR emits both the "list literal not allowed" message and "foo not defined" -- the latter is not emitted on main. But main does emit an additional "call not allowed", which this PR omits.

I think that's fine?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've changed it so that we skip the infer_expression() calls entirely if we're inside a stringized annotation. That means that we still get errors about unresolved references inside invalid-in-type-expression AST nodes (which I think is desirable), but we avoid false-positive diagnostics like that inside stringized annotations

| SpecialFormType::AlwaysTruthy
| SpecialFormType::AlwaysFalsy => {
self.infer_type_expression(arguments_slice);
self.infer_expression(arguments_slice, TypeContext::default());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Similar to above, now def f(x: "Any[foo()]") -> None: ... emits an unresolved-reference on foo that main doesn't emit. But I think that's OK.

) -> Type<'db> {
if matches!(
&*subscript.value,
ast::Expr::Name(_) | ast::Expr::Attribute(_)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What if the subscript is a string-quoted name/attribute expression?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

that's an invalid type expression -- you should quote the whole subscript expression, not just the value. "list"[int] is going to fail at runtime on Python <3.14, there's no reason to write that even on newer Python versions, and it's not permitted by the grammar at https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions. Just write "list[int]" instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I was actually confused about whether this was checking the "value" or the "slice" expression -- that makes sense.

@AlexWaygood AlexWaygood force-pushed the alex/cascading-type-expr-diagnostics branch from ae1f280 to 06909e8 Compare April 1, 2026 10:42
@AlexWaygood AlexWaygood force-pushed the alex/cascading-type-expr-diagnostics branch from a18198c to 9ddda47 Compare April 1, 2026 10:56
@AlexWaygood AlexWaygood merged commit ab032bf into main Apr 1, 2026
51 checks passed
@AlexWaygood AlexWaygood deleted the alex/cascading-type-expr-diagnostics branch April 1, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

diagnostics Related to reporting of diagnostics. ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants