[ty] Improve `unsupported-base` and `invalid-super-argument` diagnostics to avoid extremely long lines when encountering verbose types (#22022)

This commit is contained in:
Alex Waygood 2025-12-17 14:43:11 +00:00 committed by GitHub
parent 421f88bb32
commit 0bd7a94c27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 115 additions and 16 deletions

View File

@ -615,6 +615,22 @@ def _(x: type[typing.Any], y: typing.Any):
reveal_type(super(x, y)) # revealed: <super: Any, Any>
```
### Diagnostic when the invalid type is rendered very verbosely
<!-- snapshot-diagnostics -->
```py
def coinflip() -> bool:
return False
def f():
if coinflip():
class A: ...
else:
class A: ...
super(A, A()) # error: [invalid-super-argument]
```
### Instance Member Access via `super`
Accessing instance members through `super()` is not allowed.

View File

@ -289,6 +289,14 @@ reveal_type(x) # revealed: <class 'A'> | <class 'B'>
class Foo(x): ...
reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
def f():
if returns_bool():
class C: ...
else:
class C: ...
class D(C): ... # error: [unsupported-base]
```
## `UnionType` instances are now allowed as a base

View File

@ -31,17 +31,25 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
17 | class Foo(x): ...
18 |
19 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
20 |
21 | def f():
22 | if returns_bool():
23 | class C: ...
24 | else:
25 | class C: ...
26 |
27 | class D(C): ... # error: [unsupported-base]
```
# Diagnostics
```
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>`
warning[unsupported-base]: Unsupported class base
--> src/mdtest_snippet.py:17:11
|
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
17 | class Foo(x): ...
| ^
| ^ Has type `<class 'A'> | <class 'B'>`
18 |
19 | reveal_mro(Foo) # revealed: (<class 'Foo'>, Unknown, <class 'object'>)
|
@ -50,3 +58,18 @@ info: Only class objects or `Any` are supported as class bases
info: rule `unsupported-base` is enabled by default
```
```
warning[unsupported-base]: Unsupported class base
--> src/mdtest_snippet.py:27:13
|
25 | class C: ...
26 |
27 | class D(C): ... # error: [unsupported-base]
| ^ Has type `<class 'mdtest_snippet.<locals of function 'f'>.C @ src/mdtest_snippet.py:23'> | <class 'mdtest_snippet.<locals of function 'f'>.C @ src/mdtest_snippet.py:25'>`
|
info: ty cannot resolve a consistent MRO for class `D` due to this base
info: Only class objects or `Any` are supported as class bases
info: rule `unsupported-base` is enabled by default
```

View File

@ -47,13 +47,13 @@ info: rule `invalid-base` is enabled by default
```
```
warning[unsupported-base]: Unsupported class base with type `Foo`
warning[unsupported-base]: Unsupported class base
--> src/mdtest_snippet.py:6:11
|
4 | return ()
5 |
6 | class Bar(Foo()): ... # error: [unsupported-base]
| ^^^^^
| ^^^^^ Has type `Foo`
7 | class Bad1:
8 | def __mro_entries__(self, bases, extra_arg):
|

View File

@ -0,0 +1,39 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: super.md - Super - Invalid Usages - Diagnostic when the invalid type is rendered very verbosely
mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
---
# Python source files
## mdtest_snippet.py
```
1 | def coinflip() -> bool:
2 | return False
3 |
4 | def f():
5 | if coinflip():
6 | class A: ...
7 | else:
8 | class A: ...
9 | super(A, A()) # error: [invalid-super-argument]
```
# Diagnostics
```
error[invalid-super-argument]: Argument is not a valid class
--> src/mdtest_snippet.py:9:5
|
7 | else:
8 | class A: ...
9 | super(A, A()) # error: [invalid-super-argument]
| ^^^^^^^^^^^^^ Argument has type `<class 'mdtest_snippet.<locals of function 'f'>.A @ src/mdtest_snippet.py:6'> | <class 'mdtest_snippet.<locals of function 'f'>.A @ src/mdtest_snippet.py:8'>`
|
info: rule `invalid-super-argument` is enabled by default
```

View File

@ -76,15 +76,25 @@ impl<'db> BoundSuperError<'db> {
BoundSuperError::InvalidPivotClassType { pivot_class } => {
if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) {
match pivot_class {
Type::GenericAlias(alias) => builder.into_diagnostic(format_args!(
"`types.GenericAlias` instance `{}` is not a valid class",
alias.display_with(context.db(), DisplaySettings::default()),
)),
_ => builder.into_diagnostic(format_args!(
"`{pivot_class}` is not a valid class",
pivot_class = pivot_class.display(context.db()),
)),
};
Type::GenericAlias(alias) => {
builder.into_diagnostic(format_args!(
"`types.GenericAlias` instance `{}` is not a valid class",
alias.display_with(context.db(), DisplaySettings::default()),
));
}
_ => {
let mut diagnostic =
builder.into_diagnostic("Argument is not a valid class");
diagnostic.set_primary_message(format_args!(
"Argument has type `{}`",
pivot_class.display(context.db())
));
diagnostic.set_concise_message(format_args!(
"`{}` is not a valid class",
pivot_class.display(context.db()),
));
}
}
}
}
BoundSuperError::FailingConditionCheck {

View File

@ -3478,13 +3478,16 @@ fn report_unsupported_base(
let Some(builder) = context.report_lint(&UNSUPPORTED_BASE, base_node) else {
return;
};
let mut diagnostic = builder.into_diagnostic(format_args!(
let db = context.db();
let mut diagnostic = builder.into_diagnostic("Unsupported class base");
diagnostic.set_primary_message(format_args!("Has type `{}`", base_type.display(db)));
diagnostic.set_concise_message(format_args!(
"Unsupported class base with type `{}`",
base_type.display(context.db())
base_type.display(db)
));
diagnostic.info(format_args!(
"ty cannot resolve a consistent MRO for class `{}` due to this base",
class.name(context.db())
class.name(db)
));
diagnostic.info("Only class objects or `Any` are supported as class bases");
}