From a7d48ffd40e0443c17e704435a283eb173284acb Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 27 Nov 2025 12:48:18 +0000 Subject: [PATCH] [ty] Add subdiagnostic hint if a variable with type `Never` is used in a type expression (#21660) --- ...ver`-inferred_var…_(6ce5aa6d2a0ce029).snap | 45 +++++++++++++++++++ .../resources/mdtest/unreachable.md | 32 +++++++++++++ crates/ty_python_semantic/src/types.rs | 14 +++--- 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 crates/ty_python_semantic/resources/mdtest/snapshots/unreachable.md_-_Unreachable_code_-_`Never`-inferred_var…_(6ce5aa6d2a0ce029).snap diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unreachable.md_-_Unreachable_code_-_`Never`-inferred_var…_(6ce5aa6d2a0ce029).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unreachable.md_-_Unreachable_code_-_`Never`-inferred_var…_(6ce5aa6d2a0ce029).snap new file mode 100644 index 0000000000..aa86b9cbef --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unreachable.md_-_Unreachable_code_-_`Never`-inferred_var…_(6ce5aa6d2a0ce029).snap @@ -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 + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/unreachable.md b/crates/ty_python_semantic/resources/mdtest/unreachable.md index 73e174f6a1..9ff84162ac 100644 --- a/crates/ty_python_semantic/resources/mdtest/unreachable.md +++ b/crates/ty_python_semantic/resources/mdtest/unreachable.md @@ -581,3 +581,35 @@ if False: 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: + + + +```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] +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index c6b6c540f5..d31e8ce21a 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -8879,11 +8879,15 @@ impl<'db> InvalidTypeExpression<'db> { } fn add_subdiagnostics(self, db: &'db dyn Db, mut diagnostic: LintDiagnosticGuard) { - if let InvalidTypeExpression::InvalidType(ty, scope) = self { - let Type::ModuleLiteral(module_type) = ty else { - return; - }; - let module = module_type.module(db); + if let InvalidTypeExpression::InvalidType(Type::Never, _) = self { + diagnostic.help( + "The variable may have been inferred as `Never` because \ + its definition was inferred as being unreachable", + ); + } 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 { return; };