-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] improve bad specialization results & error messages #21840
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
base: main
Are you sure you want to change the base?
Conversation
Diagnostic diff on typing conformance testsChanges were detected when running ty on typing conformance tests--- old-output.txt 2025-12-10 10:29:28.201268540 +0000
+++ new-output.txt 2025-12-10 10:29:31.922278958 +0000
@@ -4,16 +4,16 @@
_directives_deprecated_library.py:41:25: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int | float`
_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:57:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `Unknown`
-aliases_explicit.py:67:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
-aliases_explicit.py:68:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+aliases_explicit.py:67:9: error[non-subscriptable] Cannot subscript non-generic type
+aliases_explicit.py:68:9: error[non-subscriptable] Cannot subscript non-generic type: `<class 'list[int | None]'>` is already specialized
aliases_explicit.py:69:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
aliases_explicit.py:70:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
aliases_explicit.py:71:24: error[invalid-type-arguments] Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`
aliases_explicit.py:101:6: error[call-non-callable] Object of type `UnionType` is not callable
-aliases_explicit.py:102:20: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+aliases_explicit.py:102:5: error[non-subscriptable] Cannot subscript non-generic type
aliases_implicit.py:68:5: error[type-assertion-failure] Type `(int, str, str, /) -> None` does not match asserted type `Unknown`
-aliases_implicit.py:76:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
-aliases_implicit.py:77:24: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+aliases_implicit.py:76:9: error[non-subscriptable] Cannot subscript non-generic type
+aliases_implicit.py:77:9: error[non-subscriptable] Cannot subscript non-generic type: `<class 'list[int | None]'>` is already specialized
aliases_implicit.py:78:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
aliases_implicit.py:79:29: error[invalid-type-arguments] Too many type arguments: expected 1, got 2
aliases_implicit.py:80:24: error[invalid-type-arguments] Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`
@@ -28,7 +28,7 @@
aliases_implicit.py:118:10: error[invalid-type-form] Variable of type `Literal["int"]` is not allowed in a type expression
aliases_implicit.py:119:10: error[invalid-type-form] Variable of type `Literal["int | str"]` is not allowed in a type expression
aliases_implicit.py:133:6: error[call-non-callable] Object of type `UnionType` is not callable
-aliases_implicit.py:135:20: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+aliases_implicit.py:135:5: error[non-subscriptable] Cannot subscript non-generic type
aliases_newtype.py:11:8: error[invalid-argument-type] Argument is incorrect: Expected `int`, found `Literal["user"]`
aliases_newtype.py:12:14: error[invalid-assignment] Object of type `Literal[42]` is not assignable to `UserId`
aliases_newtype.py:18:11: error[invalid-assignment] Object of type `<NewType pseudo-class 'UserId'>` is not assignable to `type`
@@ -610,40 +610,41 @@
generics_typevartuple_callable.py:49:1: error[type-assertion-failure] Type `tuple[int | float, str, int | float | complex]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
generics_typevartuple_concat.py:47:42: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo(PEP 646), ...]`
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_specialization.py:45:23: error[invalid-type-arguments] Too many type arguments: expected 0, got 2
-generics_typevartuple_specialization.py:45:51: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
-generics_typevartuple_specialization.py:46:5: error[type-assertion-failure] Type `tuple[int, int | float, bool]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
+generics_typevartuple_specialization.py:45:14: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:45:40: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[str, @Todo(specialized non-generic class)]'>` is already specialized
+generics_typevartuple_specialization.py:46:5: error[type-assertion-failure] Type `tuple[int, int | float, bool]` does not match asserted type `Unknown`
+generics_typevartuple_specialization.py:47:5: error[type-assertion-failure] Type `tuple[str, @Todo(specialized non-generic class)]` does not match asserted type `Unknown`
generics_typevartuple_specialization.py:51:5: error[type-assertion-failure] Type `tuple[int]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
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:92:28: error[invalid-type-arguments] Too many type arguments: expected 0, got 2
-generics_typevartuple_specialization.py:92:56: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
-generics_typevartuple_specialization.py:93:5: error[type-assertion-failure] Type `tuple[str, int]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
-generics_typevartuple_specialization.py:94:5: error[type-assertion-failure] Type `tuple[int | float]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
+generics_typevartuple_specialization.py:92:14: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:92:42: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:93:5: error[type-assertion-failure] Type `tuple[str, int]` does not match asserted type `Unknown`
+generics_typevartuple_specialization.py:94:5: error[type-assertion-failure] Type `tuple[int | float]` does not match asserted type `Unknown`
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:102:32: error[invalid-type-arguments] Too many type arguments: expected 0, got 2
-generics_typevartuple_specialization.py:103:33: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
-generics_typevartuple_specialization.py:127:9: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
-generics_typevartuple_specialization.py:130:18: error[invalid-type-arguments] Too many type arguments: expected 0, got 3
+generics_typevartuple_specialization.py:102:20: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:103:21: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:127:5: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:130:14: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
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:134:18: error[invalid-type-arguments] Too many type arguments: expected 0, got 2
-generics_typevartuple_specialization.py:134:37: error[invalid-type-arguments] Too many type arguments: expected 0, got 3
-generics_typevartuple_specialization.py:134:63: error[invalid-type-arguments] Too many type arguments: expected 0, got 4
+generics_typevartuple_specialization.py:134:14: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:134:33: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:134:59: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
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]`
generics_typevartuple_specialization.py:136:5: error[type-assertion-failure] Type `tuple[tuple[str], bool, int | float]` does not match asserted type `tuple[tuple[@Todo(PEP 646), ...], Unknown, Unknown]`
generics_typevartuple_specialization.py:137:5: error[type-assertion-failure] Type `tuple[tuple[str, bool], int | float, int]` does not match asserted type `tuple[tuple[@Todo(PEP 646), ...], Unknown, Unknown]`
-generics_typevartuple_specialization.py:143:18: error[invalid-type-arguments] Too many type arguments: expected 0, got 4
+generics_typevartuple_specialization.py:143:14: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
generics_typevartuple_specialization.py:143:39: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[tuple[@Todo(PEP 646), ...], T1@func9, T2@func9, T3@func9]`
-generics_typevartuple_specialization.py:147:19: error[invalid-type-arguments] Too many type arguments: expected 0, got 3
-generics_typevartuple_specialization.py:147:45: error[invalid-type-arguments] Too many type arguments: expected 0, got 4
+generics_typevartuple_specialization.py:147:15: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:147:41: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
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:153:12: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
-generics_typevartuple_specialization.py:156:28: error[invalid-type-arguments] Too many type arguments: expected 0, got 2
-generics_typevartuple_specialization.py:156:59: error[invalid-type-arguments] Too many type arguments: expected 0, got 2
-generics_typevartuple_specialization.py:157:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], int]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
-generics_typevartuple_specialization.py:158:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], str]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
-generics_typevartuple_specialization.py:159:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], str]` does not match asserted type `tuple[@Todo(PEP 646), ...]`
-generics_typevartuple_specialization.py:163:13: error[invalid-type-arguments] Too many type arguments: expected 0, got 1
+generics_typevartuple_specialization.py:153:8: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:156:24: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:156:55: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
+generics_typevartuple_specialization.py:157:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], int]` does not match asserted type `Unknown`
+generics_typevartuple_specialization.py:158:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], str]` does not match asserted type `Unknown`
+generics_typevartuple_specialization.py:159:5: error[type-assertion-failure] Type `tuple[*tuple[int, ...], str]` does not match asserted type `Unknown`
+generics_typevartuple_specialization.py:163:8: error[non-subscriptable] Cannot subscript non-generic type: `<class 'tuple[@Todo(PEP 646), ...]'>` is already specialized
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]`
generics_upper_bound.py:43:1: error[type-assertion-failure] Type `list[int] | set[int]` does not match asserted type `list[Unknown | int] | set[Unknown | int]`
@@ -1025,4 +1026,4 @@
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
-Found 1027 diagnostics
+Found 1028 diagnostics
|
|
|
|
||
| # error: [invalid-type-arguments] "Type `int` is not assignable to upper bound `str` of type variable `U@TupleOfIntAndStr`" | ||
| def _(x: TupleOfIntAndStr[int, int]): | ||
| reveal_type(x) # revealed: tuple[int, Unknown] |
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.
This behavior is consistent with pyright.
mypy and pyrefly do not replace even the failed parts with Unknown, which is not a good idea.
|
The type conformance test results are good: the error messages are mostly as written in the scripts (except for |
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-type-arguments |
0 | 460 | 2 |
non-subscriptable |
460 | 0 | 0 |
unresolved-attribute |
0 | 0 | 7 |
invalid-argument-type |
0 | 2 | 0 |
type-assertion-failure |
2 | 0 | 0 |
unused-ignore-comment |
2 | 0 | 0 |
| Total | 464 | 462 | 9 |
crates/ty_python_semantic/resources/corpus/cyclic_pep695_typevars.py
Outdated
Show resolved
Hide resolved
|
I find some of the error messages on this branch a bit confusing. For example: class Foo[T]:
x: T
# error[non-subscriptable] "Cannot subscript non-generic type alias: `<class 'Foo[int]'>` is already specialized"
f = Foo[int][str]()But I never created a type alias there! |
carljm
left a comment
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.
This looks excellent, thank you!
I only have minor nits about the diagnostic messages.
| Some( | ||
| ExplicitSpecializationError::UnsatisfiedBound | ||
| | ExplicitSpecializationError::UnsatisfiedConstraints | ||
| | ExplicitSpecializationError::InvalidParamSpec, | ||
| ) |
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.
This behavior change fits well with a TODO I recently added to consider lazily checking explicit specializations against bounds/constraints, instead of checking it eagerly. (If we are going to return the requested specialization here even if it violates bounds/constraints, then there is no need to eagerly evaluate the bounds/constraints at all.) This would help us avoid some cycle problems.
| if value_type.is_generic_nominal_instance() { | ||
| builder.into_diagnostic(format_args!( | ||
| "Cannot subscript non-generic type alias: `{}` is already specialized", | ||
| value_type.display(db), | ||
| )); | ||
| } else { | ||
| builder.into_diagnostic(format_args!( | ||
| "Cannot subscript non-generic type alias" | ||
| )); | ||
| } |
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.
As Alex points out in separate comment, is_generic_nominal_instance is awkwardly specific -- probably too specific.
Would there be any harm in just always providing the actual type we failed to specialize? Why not simplify to
| if value_type.is_generic_nominal_instance() { | |
| builder.into_diagnostic(format_args!( | |
| "Cannot subscript non-generic type alias: `{}` is already specialized", | |
| value_type.display(db), | |
| )); | |
| } else { | |
| builder.into_diagnostic(format_args!( | |
| "Cannot subscript non-generic type alias" | |
| )); | |
| } | |
| builder.into_diagnostic(format_args!( | |
| "Cannot subscript non-generic type `{}`", | |
| value_type.display(db), | |
| )); | |
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.
Consider the case where the user has a type alias type MyList = list[int] and accidentally specifies MyList[int].
The user will see an error "Cannot subscript non-generic type list[int]", which is likely to be confusing. If the user mistakenly thought MyList was a generic type and wanted list[int] as the result of specialization, the user might not immediately realize what the problem is. In fact, pyright gives a hint in this case: "list[int] is already specialized".
Also, the error message should be mostly the same whether the type alias is defined implicitly or using PEP 695 syntax.
| { | ||
| if value_ty.is_generic_alias() { | ||
| builder.into_diagnostic(format_args!( | ||
| "Cannot subscript non-generic type alias: `{}` is already specialized", |
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.
Similar to below, I'm not really clear why we should only provide value_ty in the diagnostic in certain limited cases. Why not simplify this code and just always provide it, with a message like "Cannot subscript non-generic type {}"? To me that seems clear enough in all cases.
| if value_type.is_generic_nominal_instance() { | ||
| builder.into_diagnostic(format_args!( | ||
| "Cannot subscript non-generic type alias: `{}` is already specialized", | ||
| value_type.display(self.db()), | ||
| )); | ||
| } else { | ||
| builder.into_diagnostic(format_args!( | ||
| "Cannot subscript non-generic type alias" | ||
| )); | ||
| } |
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.
Similar comment as below
1759af3 to
41e30cc
Compare
Summary
This PR includes the following changes:
Unknown. Also, the error message is improved.Unknown. Also, the error message is improved.Unknown.G[int][int]Furthermore, after applying this PR, the fuzzing tests for seeds 1052 and 4419, which panic in main, now pass.
This is because the false recursions on type variables have been removed.
Test Plan
New corpus test
mdtest files updated