diff --git a/crates/ruff/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs b/crates/ruff/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs index b52b010583..8fcb45d403 100644 --- a/crates/ruff/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs +++ b/crates/ruff/src/rules/flake8_pyi/rules/no_return_argument_annotation.rs @@ -1,3 +1,5 @@ +use std::fmt; + use itertools::chain; use rustpython_parser::ast::Ranged; @@ -10,57 +12,79 @@ use crate::settings::types::PythonVersion::Py311; #[violation] pub struct NoReturnArgumentAnnotationInStub { - is_never_builtin: bool, + module: TypingModule, } /// ## What it does -/// Checks for usages of `typing.NoReturn` or `typing_extensions.NoReturn` in stubs. +/// Checks for uses of `typing.NoReturn` (and `typing_extensions.NoReturn`) in +/// stubs. /// /// ## Why is this bad? -/// Use of `typing.Never` or `typing_extensions.Never` can make your intentions clearer. This is a -/// purely stylistic choice in the name of readability. +/// Prefer `typing.Never` (or `typing_extensions.Never`) over `typing.NoReturn`, +/// as the former is more explicit about the intent of the annotation. This is +/// a purely stylistic choice, as the two are semantically equivalent. /// /// ## Example /// ```python -/// def foo(arg: typing.NoReturn): ... +/// from typing import NoReturn +/// +/// +/// def foo(x: NoReturn): ... /// ``` /// /// Use instead: /// ```python -/// def foo(arg: typing.Never): ... +/// from typing import Never +/// +/// +/// def foo(x: Never): ... /// ``` impl Violation for NoReturnArgumentAnnotationInStub { #[derive_message_formats] fn message(&self) -> String { - let typing_name = if self.is_never_builtin { - "typing" - } else { - "typing_extensions" - }; - format!("Prefer {typing_name}.Never over NoReturn for argument annotations.") + let NoReturnArgumentAnnotationInStub { module } = self; + format!("Prefer `{module}.Never` over `NoReturn` for argument annotations") } } /// PYI050 pub(crate) fn no_return_argument_annotation(checker: &mut Checker, args: &Arguments) { - let annotations = chain!( + for annotation in chain!( args.args.iter(), args.posonlyargs.iter(), args.kwonlyargs.iter() ) - .filter_map(|arg| arg.annotation.as_ref()); - - let is_never_builtin = checker.settings.target_version >= Py311; - - for annotation in annotations { + .filter_map(|arg| arg.annotation.as_ref()) + { if checker .semantic_model() .match_typing_expr(annotation, "NoReturn") { checker.diagnostics.push(Diagnostic::new( - NoReturnArgumentAnnotationInStub { is_never_builtin }, + NoReturnArgumentAnnotationInStub { + module: if checker.settings.target_version >= Py311 { + TypingModule::Typing + } else { + TypingModule::TypingExtensions + }, + }, annotation.range(), )); } } } + +#[derive(Debug, PartialEq, Eq)] +enum TypingModule { + Typing, + TypingExtensions, +} + +impl fmt::Display for TypingModule { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + TypingModule::Typing => fmt.write_str("typing"), + TypingModule::TypingExtensions => fmt.write_str("typing_extensions"), + } + } +} diff --git a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI050_PYI050.pyi.snap b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI050_PYI050.pyi.snap index 5b051189e1..f4f7de2503 100644 --- a/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI050_PYI050.pyi.snap +++ b/crates/ruff/src/rules/flake8_pyi/snapshots/ruff__rules__flake8_pyi__tests__PYI050_PYI050.pyi.snap @@ -1,7 +1,7 @@ --- source: crates/ruff/src/rules/flake8_pyi/mod.rs --- -PYI050.pyi:6:24: PYI050 Prefer typing_extensions.Never over NoReturn for argument annotations. +PYI050.pyi:6:24: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations | 6 | def foo(arg): ... 7 | def foo_int(arg: int): ... @@ -11,7 +11,7 @@ PYI050.pyi:6:24: PYI050 Prefer typing_extensions.Never over NoReturn for argumen 10 | arg: typing_extensions.NoReturn, | -PYI050.pyi:10:44: PYI050 Prefer typing_extensions.Never over NoReturn for argument annotations. +PYI050.pyi:10:44: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations | 10 | arg: typing_extensions.NoReturn, 11 | ): ... # Error: PYI050 @@ -21,7 +21,7 @@ PYI050.pyi:10:44: PYI050 Prefer typing_extensions.Never over NoReturn for argume 14 | def foo_never(arg: Never): ... | -PYI050.pyi:11:47: PYI050 Prefer typing_extensions.Never over NoReturn for argument annotations. +PYI050.pyi:11:47: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations | 11 | ): ... # Error: PYI050 12 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050