[ty] Add subdiagnostic hint if a variable with type `Never` is used in a type expression (#21660)

This commit is contained in:
Alex Waygood 2025-11-27 12:48:18 +00:00 committed by GitHub
parent 77f8fa6906
commit a7d48ffd40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 86 additions and 5 deletions

View File

@ -0,0 +1,45 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: unreachable.md - Unreachable code - `Never`-inferred variables in type expressions
mdtest path: crates/ty_python_semantic/resources/mdtest/unreachable.md
---
# Python source files
## module.py
```
1 | import sys
2 |
3 | if sys.version_info >= (3, 14):
4 | raise RuntimeError("this library doesn't support 3.14 yet!!!")
5 |
6 | class AwesomeAPI: ...
```
## main.py
```
1 | import module
2 |
3 | def f(x: module.AwesomeAPI): ... # error: [invalid-type-form]
```
# Diagnostics
```
error[invalid-type-form]: Variable of type `Never` is not allowed in a type expression
--> src/main.py:3:10
|
1 | import module
2 |
3 | def f(x: module.AwesomeAPI): ... # error: [invalid-type-form]
| ^^^^^^^^^^^^^^^^^
|
help: The variable may have been inferred as `Never` because its definition was inferred as being unreachable
info: rule `invalid-type-form` is enabled by default
```

View File

@ -581,3 +581,35 @@ if False:
1 / number 1 / number
``` ```
## `Never`-inferred variables in type expressions
We offer a helpful subdiagnostic if a variable in a type expression is inferred as having type
`Never`, since this almost certainly resulted in the definition of the type being inferred by ty as
being unreachable:
<!-- snapshot-diagnostics -->
```toml
[environment]
python-version = "3.14"
```
`module.py`:
```py
import sys
if sys.version_info >= (3, 14):
raise RuntimeError("this library doesn't support 3.14 yet!!!")
class AwesomeAPI: ...
```
`main.py`:
```py
import module
def f(x: module.AwesomeAPI): ... # error: [invalid-type-form]
```

View File

@ -8879,11 +8879,15 @@ impl<'db> InvalidTypeExpression<'db> {
} }
fn add_subdiagnostics(self, db: &'db dyn Db, mut diagnostic: LintDiagnosticGuard) { fn add_subdiagnostics(self, db: &'db dyn Db, mut diagnostic: LintDiagnosticGuard) {
if let InvalidTypeExpression::InvalidType(ty, scope) = self { if let InvalidTypeExpression::InvalidType(Type::Never, _) = self {
let Type::ModuleLiteral(module_type) = ty else { diagnostic.help(
return; "The variable may have been inferred as `Never` because \
}; its definition was inferred as being unreachable",
let module = module_type.module(db); );
} else if let InvalidTypeExpression::InvalidType(ty @ Type::ModuleLiteral(module), scope) =
self
{
let module = module.module(db);
let Some(module_name_final_part) = module.name(db).components().next_back() else { let Some(module_name_final_part) = module.name(db).components().next_back() else {
return; return;
}; };