-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] tighten up handling of subscripts in type expressions #21503
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
Conversation
Diagnostic diff on typing conformance testsChanges were detected when running ty on typing conformance tests--- old-output.txt 2025-11-18 18:17:10.806838177 +0000
+++ new-output.txt 2025-11-18 18:17:14.257848390 +0000
@@ -6,22 +6,22 @@
_directives_deprecated_library.py:45:24: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `str`
aliases_explicit.py:41:24: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[str, str]`?
aliases_explicit.py:45:10: error[invalid-type-form] Variable of type `Literal["int | str"]` is not allowed in a type expression
-aliases_explicit.py:52:5: error[type-assertion-failure] Type `list[int]` does not match asserted type `@Todo(unknown type subscript)`
+aliases_explicit.py:52:5: error[type-assertion-failure] Type `list[int]` does not match asserted type `@Todo(specialized generic alias in type expression)`
aliases_explicit.py:53:5: error[type-assertion-failure] Type `tuple[str, ...] | list[str]` does not match asserted type `@Todo(Generic specialization of types.UnionType)`
-aliases_explicit.py:54:5: error[type-assertion-failure] Type `tuple[int, int, int, str]` does not match asserted type `@Todo(unknown type subscript)`
+aliases_explicit.py:54:5: error[type-assertion-failure] Type `tuple[int, int, int, str]` does not match asserted type `@Todo(specialized generic alias in type expression)`
aliases_explicit.py:56:5: error[type-assertion-failure] Type `(int, str, /) -> str` does not match asserted type `@Todo(Generic specialization of typing.Callable)`
aliases_explicit.py:57:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `@Todo(Generic specialization of typing.Callable)`
-aliases_explicit.py:59:5: error[type-assertion-failure] Type `int | str | None | list[list[int]]` does not match asserted type `int | str | None | list[@Todo(unknown type subscript)]`
+aliases_explicit.py:59:5: error[type-assertion-failure] Type `int | str | None | list[list[int]]` does not match asserted type `int | str | None | list[@Todo(specialized generic alias in type expression)]`
aliases_explicit.py:61:5: error[type-assertion-failure] Type `int | str` does not match asserted type `Unknown`
aliases_explicit.py:101:6: error[call-non-callable] Object of type `UnionType` is not callable
aliases_implicit.py:54:24: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[str, str]`?
-aliases_implicit.py:63:5: error[type-assertion-failure] Type `list[int]` does not match asserted type `@Todo(unknown type subscript)`
+aliases_implicit.py:63:5: error[type-assertion-failure] Type `list[int]` does not match asserted type `@Todo(specialized generic alias in type expression)`
aliases_implicit.py:64:5: error[type-assertion-failure] Type `tuple[str, ...] | list[str]` does not match asserted type `@Todo(Generic specialization of types.UnionType)`
-aliases_implicit.py:65:5: error[type-assertion-failure] Type `tuple[int, int, int, str]` does not match asserted type `@Todo(unknown type subscript)`
+aliases_implicit.py:65:5: error[type-assertion-failure] Type `tuple[int, int, int, str]` does not match asserted type `@Todo(specialized generic alias in type expression)`
aliases_implicit.py:67:5: error[type-assertion-failure] Type `(int, str, /) -> str` does not match asserted type `@Todo(Generic specialization of typing.Callable)`
aliases_implicit.py:68:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `@Todo(Generic specialization of typing.Callable)`
-aliases_implicit.py:70:5: error[type-assertion-failure] Type `int | str | None | list[list[int]]` does not match asserted type `int | str | None | list[@Todo(unknown type subscript)]`
-aliases_implicit.py:71:5: error[type-assertion-failure] Type `list[bool]` does not match asserted type `@Todo(unknown type subscript)`
+aliases_implicit.py:70:5: error[type-assertion-failure] Type `int | str | None | list[list[int]]` does not match asserted type `int | str | None | list[@Todo(specialized generic alias in type expression)]`
+aliases_implicit.py:71:5: error[type-assertion-failure] Type `list[bool]` does not match asserted type `@Todo(specialized generic alias in type expression)`
aliases_implicit.py:107:9: error[invalid-type-form] Variable of type `list[Unknown | <class 'int'> | <class 'str'>]` is not allowed in a type expression
aliases_implicit.py:108:9: error[invalid-type-form] Variable of type `tuple[tuple[<class 'int'>, <class 'str'>]]` is not allowed in a type expression
aliases_implicit.py:109:9: error[invalid-type-form] Variable of type `list[<class 'int'> | Unknown]` is not allowed in a type expression
@@ -58,6 +58,7 @@
aliases_type_statement.py:40:22: error[invalid-type-form] List comprehensions are not allowed in type expressions
aliases_type_statement.py:41:22: error[invalid-type-form] Dict literals are not allowed in type expressions
aliases_type_statement.py:42:22: error[invalid-type-form] Function calls are not allowed in type expressions
+aliases_type_statement.py:43:22: error[invalid-type-form] Invalid subscript of object of type `list[Unknown | <class 'int'>]` in type expression
aliases_type_statement.py:43:28: error[invalid-type-form] Int literals are not allowed in this context in a type expression
aliases_type_statement.py:44:22: error[invalid-type-form] `if` expressions are not allowed in type expressions
aliases_type_statement.py:45:22: error[invalid-type-form] Variable of type `Literal[1]` is not allowed in a type expression
@@ -76,6 +77,7 @@
aliases_variance.py:18:24: error[non-subscriptable] Cannot subscript object of type `<class 'ClassA[typing.TypeVar]'>` with no `__class_getitem__` method
aliases_variance.py:28:16: error[non-subscriptable] Cannot subscript object of type `<class 'ClassA[typing.TypeVar]'>` with no `__class_getitem__` method
aliases_variance.py:44:16: error[non-subscriptable] Cannot subscript object of type `<class 'ClassB[typing.TypeVar, typing.TypeVar]'>` with no `__class_getitem__` method
+annotations_forward_refs.py:47:10: error[invalid-type-form] Invalid subscript of object of type `list[Unknown | <class 'int'>]` in type expression
annotations_forward_refs.py:49:10: error[invalid-type-form] Variable of type `Literal[1]` is not allowed in a type expression
annotations_forward_refs.py:54:11: error[fstring-type-annotation] Type expressions cannot use f-strings
annotations_forward_refs.py:55:11: error[invalid-type-form] Variable of type `<module 'types'>` is not allowed in a type expression
@@ -101,6 +103,7 @@
annotations_typeexpr.py:91:9: error[invalid-type-form] List comprehensions are not allowed in type expressions
annotations_typeexpr.py:92:9: error[invalid-type-form] Dict literals are not allowed in type expressions
annotations_typeexpr.py:93:9: error[invalid-type-form] Function calls are not allowed in type expressions
+annotations_typeexpr.py:94:9: error[invalid-type-form] Invalid subscript of object of type `list[Unknown | <class 'int'>]` in type expression
annotations_typeexpr.py:94:15: error[invalid-type-form] Int literals are not allowed in this context in a type expression
annotations_typeexpr.py:95:9: error[invalid-type-form] `if` expressions are not allowed in type expressions
annotations_typeexpr.py:96:9: error[invalid-type-form] Variable of type `Literal[3]` is not allowed in a type expression
@@ -447,7 +450,7 @@
generics_defaults_referential.py:94:1: error[type-assertion-failure] Type `@Todo(unsupported nested subscript in type[X])` does not match asserted type `<class 'Bar'>`
generics_defaults_referential.py:95:1: error[type-assertion-failure] Type `@Todo(unsupported nested subscript in type[X])` does not match asserted type `<class 'Bar[int, list[int]]'>`
generics_defaults_specialization.py:26:5: error[type-assertion-failure] Type `SomethingWithNoDefaults[int, str]` does not match asserted type `SomethingWithNoDefaults[int, typing.TypeVar]`
-generics_defaults_specialization.py:27:5: error[type-assertion-failure] Type `SomethingWithNoDefaults[int, bool]` does not match asserted type `@Todo(unknown type subscript)`
+generics_defaults_specialization.py:27:5: error[type-assertion-failure] Type `SomethingWithNoDefaults[int, bool]` does not match asserted type `@Todo(specialized generic alias in type expression)`
generics_defaults_specialization.py:30:1: error[non-subscriptable] Cannot subscript object of type `<class 'SomethingWithNoDefaults[int, typing.TypeVar]'>` with no `__class_getitem__` method
generics_defaults_specialization.py:45:1: error[type-assertion-failure] Type `@Todo(unsupported nested subscript in type[X])` does not match asserted type `<class 'Bar'>`
generics_paramspec_basic.py:10:1: error[invalid-paramspec] The name of a `ParamSpec` (`NotIt`) must match the name of the variable it is assigned to (`WrongName`)
@@ -593,16 +596,16 @@
generics_typevartuple_concat.py:52:1: error[type-assertion-failure] Type `tuple[int, bool, str]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
generics_typevartuple_overloads.py:16:13: error[invalid-argument-type] `@Todo(starred expression)` is not a valid argument to `Generic`
generics_typevartuple_specialization.py:16:13: error[invalid-argument-type] `@Todo(starred expression)` is not a valid argument to `Generic`
-generics_typevartuple_specialization.py:46:5: error[type-assertion-failure] Type `tuple[int, int | float, bool]` does not match asserted type `@Todo(unknown type subscript)`
-generics_typevartuple_specialization.py:47:5: error[type-assertion-failure] Type `tuple[str, @Todo(specialized non-generic class)]` does not match asserted type `@Todo(unknown type subscript)`
+generics_typevartuple_specialization.py:46:5: error[type-assertion-failure] Type `tuple[int, int | float, bool]` does not match asserted type `@Todo(specialized generic alias in type expression)`
+generics_typevartuple_specialization.py:47:5: error[type-assertion-failure] Type `tuple[str, @Todo(specialized non-generic class)]` does not match asserted type `@Todo(specialized generic alias in type expression)`
generics_typevartuple_specialization.py:50:23: error[invalid-type-form] Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?
generics_typevartuple_specialization.py:50:42: error[invalid-type-form] Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?
-generics_typevartuple_specialization.py:51:5: error[type-assertion-failure] Type `tuple[int]` does not match asserted type `@Todo(unknown type subscript)`
-generics_typevartuple_specialization.py:52:5: error[type-assertion-failure] Type `tuple[str, @Todo(specialized non-generic class)]` does not match asserted type `@Todo(unknown type subscript)`
+generics_typevartuple_specialization.py:51:5: error[type-assertion-failure] Type `tuple[int]` does not match asserted type `@Todo(specialized generic alias in type expression)`
+generics_typevartuple_specialization.py:52:5: error[type-assertion-failure] Type `tuple[str, @Todo(specialized non-generic class)]` does not match asserted type `@Todo(specialized generic alias in type expression)`
generics_typevartuple_specialization.py:52:37: error[invalid-type-form] Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?
generics_typevartuple_specialization.py:59:14: error[invalid-argument-type] `@Todo(starred expression)` is not a valid argument to `Generic`
-generics_typevartuple_specialization.py:93:5: error[type-assertion-failure] Type `tuple[str, int]` does not match asserted type `@Todo(unknown type subscript)`
-generics_typevartuple_specialization.py:94:5: error[type-assertion-failure] Type `tuple[int | float]` does not match asserted type `@Todo(unknown type subscript)`
+generics_typevartuple_specialization.py:93:5: error[type-assertion-failure] Type `tuple[str, int]` does not match asserted type `@Todo(specialized generic alias in type expression)`
+generics_typevartuple_specialization.py:94:5: error[type-assertion-failure] Type `tuple[int | float]` does not match asserted type `@Todo(specialized generic alias in type expression)`
generics_typevartuple_specialization.py:95:5: error[type-assertion-failure] Type `tuple[Any, *tuple[Any, ...]]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
generics_typevartuple_specialization.py:130:35: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[tuple[@Todo(PEP 646), ...], T1@func7, T2@func7]`
generics_typevartuple_specialization.py:135:5: error[type-assertion-failure] Type `tuple[tuple[()], str, bool]` does not match asserted type `tuple[tuple[@Todo(PEP 646), ...], Unknown, Unknown]`
@@ -612,8 +615,8 @@
generics_typevartuple_specialization.py:148:5: error[type-assertion-failure] Type `tuple[tuple[()], str, bool, int | float]` does not match asserted type `tuple[tuple[@Todo(PEP 646), ...], Unknown, Unknown, Unknown]`
generics_typevartuple_specialization.py:149:5: error[type-assertion-failure] Type `tuple[tuple[bool], str, int | float, int]` does not match asserted type `tuple[tuple[@Todo(PEP 646), ...], Unknown, Unknown, Unknown]`
generics_typevartuple_specialization.py:157:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], int]` does not match asserted type `@Todo(Support for `typing.GenericAlias` instances in type expressions)`
-generics_typevartuple_specialization.py:158:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], str]` does not match asserted type `@Todo(unknown type subscript)`
-generics_typevartuple_specialization.py:159:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], str]` does not match asserted type `@Todo(unknown type subscript)`
+generics_typevartuple_specialization.py:158:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], str]` does not match asserted type `@Todo(specialized generic alias in type expression)`
+generics_typevartuple_specialization.py:159:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], str]` does not match asserted type `@Todo(specialized generic alias in type expression)`
generics_typevartuple_unpack.py:17:13: error[invalid-argument-type] `@Todo(starred expression)` is not a valid argument to `Generic`
generics_upper_bound.py:37:1: error[type-assertion-failure] Type `list[int]` does not match asserted type `list[Unknown | int]`
generics_upper_bound.py:38:1: error[type-assertion-failure] Type `set[int]` does not match asserted type `set[Unknown | int]`
@@ -837,6 +840,7 @@
qualifiers_annotated.py:40:17: error[invalid-type-form] List comprehensions are not allowed in type expressions
qualifiers_annotated.py:41:17: error[invalid-type-form] Dict literals are not allowed in type expressions
qualifiers_annotated.py:42:17: error[invalid-type-form] Function calls are not allowed in type expressions
+qualifiers_annotated.py:43:17: error[invalid-type-form] Invalid subscript of object of type `list[Unknown | <class 'int'>]` in type expression
qualifiers_annotated.py:43:23: error[invalid-type-form] Int literals are not allowed in this context in a type expression
qualifiers_annotated.py:44:17: error[invalid-type-form] `if` expressions are not allowed in type expressions
qualifiers_annotated.py:45:17: error[unresolved-reference] Name `var1` used when not defined
@@ -989,5 +993,5 @@
typeddicts_usage.py:28:17: error[missing-typed-dict-key] Missing required key 'name' in TypedDict `Movie` constructor
typeddicts_usage.py:28:18: error[invalid-key] Unknown key "title" for TypedDict `Movie`: Unknown key "title"
typeddicts_usage.py:40:24: error[invalid-type-form] The special form `typing.TypedDict` is not allowed in type expressions. Did you mean to use a concrete TypedDict or `collections.abc.Mapping[str, object]` instead?
-Found 991 diagnostics
+Found 995 diagnostics
WARN A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details.
|
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-type-form |
222 | 1 | 0 |
invalid-argument-type |
0 | 2 | 9 |
unused-ignore-comment |
4 | 3 | 0 |
non-subscriptable |
0 | 5 | 0 |
unresolved-reference |
5 | 0 | 0 |
invalid-await |
0 | 0 | 3 |
invalid-return-type |
0 | 0 | 3 |
possibly-missing-attribute |
0 | 0 | 3 |
type-assertion-failure |
0 | 0 | 3 |
unsupported-operator |
0 | 2 | 0 |
invalid-assignment |
0 | 0 | 1 |
| Total | 231 | 13 | 22 |
e62ea73 to
45e98ea
Compare
45e98ea to
1e1cab0
Compare
| p: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions" | ||
| q: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions" | ||
| # error: [invalid-type-form] "Slices are not allowed in type expressions" | ||
| # error: [invalid-type-form] "Cannot subscript object of type" |
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 don't think this makes grammatical sense -- cannot subscript object of type what, exactly? 😄
Also, you can subscript [1, 2, 3], right?
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.
That's because it's a partial match on the message string, it's not the full message; I didn't feel like writing out the full types :)
I can include the full message, or at least enough to make it less confusing.
Also, you can subscript
[1, 2, 3], right?
The full message includes "in a type expression"
crates/ty_python_semantic/resources/mdtest/annotations/literal.md
Outdated
Show resolved
Hide resolved
1e1cab0 to
d32b0d6
Compare
|
| # TODO should be Unknown | int | ||
| reveal_type(type_var_or_int) # revealed: T@_ | int | ||
| # TODO should be int | Unknown | ||
| reveal_type(int_or_type_var) # revealed: int | T@_ | ||
| # TODO should be Unknown | None | ||
| reveal_type(type_var_or_none) # revealed: T@_ | None | ||
| # TODO should be None | Unknown | ||
| reveal_type(none_or_type_var) # revealed: None | T@_ |
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.
Working on it ... 😄
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 was actually going to ask if you wanted these tests here or in a separate "generics" section, since they are testing the combination of both generic aliases and unions :) Feel free to move them if you want in your PR.
* origin/main: [ty] Fix flaky tests on macos (#21524) [ty] Add tests for generic implicit type aliases (#21522) [ty] Semantic tokens: consistently add the `DEFINITION` modifier (#21521) Only render hyperlinks for terminals known to support them (#21519) [ty] Keep colorizing `mypy_primer` output (#21515) [ty] Exit with `2` if there's any IO error (#21508) [`ruff`] Fix false positive for complex conversion specifiers in `logging-eager-conversion` (`RUF065`) (#21464) [ty] tighten up handling of subscripts in type expressions (#21503)
* origin/main: [ty] Fix flaky tests on macos (#21524) [ty] Add tests for generic implicit type aliases (#21522) [ty] Semantic tokens: consistently add the `DEFINITION` modifier (#21521) Only render hyperlinks for terminals known to support them (#21519) [ty] Keep colorizing `mypy_primer` output (#21515) [ty] Exit with `2` if there's any IO error (#21508) [`ruff`] Fix false positive for complex conversion specifiers in `logging-eager-conversion` (`RUF065`) (#21464) [ty] tighten up handling of subscripts in type expressions (#21503)
Summary
Get rid of the catch-all todo type from subscripting a base type we haven't implemented handling for yet in a type expression, and turn it into a diagnostic instead.
Handle a few more cases explicitly, to avoid false positives from the above change:
Test Plan
Adjusted mdtests, ecosystem.
All new diagnostics in conformance suite are supposed to be diagnostics, so this PR is a strict improvement there.
New diagnostics in the ecosystem are surfacing cases where we already don't understand an annotation, but now we emit a diagnostic about it. They are mostly intentional choices. Analysis of particular cases:
attrs,bokeh,django-stubs,dulwich,ibis,kornia,mitmproxy,mongo-python-driver,mypy,pandas,poetry,prefect,pydantic,pytest,scrapy,trio,werkzeug, andxarrayare all cases where underfrom __future__ import annotationsor Python 3.14 deferred-annotations semantics, we follow normal name-scoping rules, whereas some other type checkers prefer global names over local names. This means we don't like it if e.g. you have a class with a method or attribute namedtypeortuple, and you also try to usetypeortuplein method/attribute annotations of that class. This PR isn't changing those semantics, just revealing them in more cases where previously we just silently fell back toUnknown. I think failing with a diagnostic (so authors can alias names as needed to avoid relying on scoping rules that differ between type checkers) is better than failing silently here.beartypeassumes we supportTypeForm(because it only supports mypy and pyright, it usesif MYPY:to hide theTypeFormfrom mypy, and pyright supportsTypeForm), and we don't yet.graphql-corelikes to use atry: ... except ImportError: ...pattern for importing special forms fromtypingwith fallback totyping_extensions, instead of usingsys.version_infochecks. We don't handle this well when type checking under an older Python version (where the import fromtypingis not found); we see the imported name as of type e.g.Unknown | SpecialFormType(...), and because of the union withUnknownwe fail to handle it as the special form type. Mypy and pyright also don't seem to support this pattern. They don't complain about subscripting such special forms, but they do silently fail to treat them as the desired special form. Again here, if we are going to fail I'd rather fail with a diagnostic rather than silently.ibisis trying to usefrozendict: type[FrozenDict]as a way to create a "type alias" toFrozenDict, but this is wrong: that meansfrozendict: type[FrozenDict[Any, Any]].mypyhas some errors due to the fact that type-checkingtyping.pyiitself (without knowing that it's the realtyping.pyi) doesn't work very well.mypy-protobufimports some types from the protobufs library that end up unioned withUnknownfor some reason, and so we don't allow explicit-specialization of them. Depending on the reason they end up unioned withUnknown, we might want to better support this? But it's orthogonal to this PR -- we aren't failing any worse here, just alerting the author that we didn't understand their annotation.pwndbghas unresolved references due to star-importing from a dependency that isn't installed, and uses un-imported names likeDictin annotation expressions. Some of the unresolved references were hidden by https://github.com/astral-sh/ruff/blob/main/crates/ty_python_semantic/src/types/infer/builder.rs#L7223-L7228 when some annotations previously resolved to a Todo type that no longer do.