This commit is contained in:
Charlie Marsh 2023-06-06 21:49:21 -04:00
parent ddd3ededec
commit 6a1591a76c
2 changed files with 46 additions and 22 deletions

View File

@ -1,3 +1,5 @@
use std::fmt;
use itertools::chain; use itertools::chain;
use rustpython_parser::ast::Ranged; use rustpython_parser::ast::Ranged;
@ -10,57 +12,79 @@ use crate::settings::types::PythonVersion::Py311;
#[violation] #[violation]
pub struct NoReturnArgumentAnnotationInStub { pub struct NoReturnArgumentAnnotationInStub {
is_never_builtin: bool, module: TypingModule,
} }
/// ## What it does /// ## 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? /// ## Why is this bad?
/// Use of `typing.Never` or `typing_extensions.Never` can make your intentions clearer. This is a /// Prefer `typing.Never` (or `typing_extensions.Never`) over `typing.NoReturn`,
/// purely stylistic choice in the name of readability. /// 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 /// ## Example
/// ```python /// ```python
/// def foo(arg: typing.NoReturn): ... /// from typing import NoReturn
///
///
/// def foo(x: NoReturn): ...
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```python /// ```python
/// def foo(arg: typing.Never): ... /// from typing import Never
///
///
/// def foo(x: Never): ...
/// ``` /// ```
impl Violation for NoReturnArgumentAnnotationInStub { impl Violation for NoReturnArgumentAnnotationInStub {
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
let typing_name = if self.is_never_builtin { let NoReturnArgumentAnnotationInStub { module } = self;
"typing" format!("Prefer `{module}.Never` over `NoReturn` for argument annotations")
} else {
"typing_extensions"
};
format!("Prefer {typing_name}.Never over NoReturn for argument annotations.")
} }
} }
/// PYI050 /// PYI050
pub(crate) fn no_return_argument_annotation(checker: &mut Checker, args: &Arguments) { pub(crate) fn no_return_argument_annotation(checker: &mut Checker, args: &Arguments) {
let annotations = chain!( for annotation in chain!(
args.args.iter(), args.args.iter(),
args.posonlyargs.iter(), args.posonlyargs.iter(),
args.kwonlyargs.iter() args.kwonlyargs.iter()
) )
.filter_map(|arg| arg.annotation.as_ref()); .filter_map(|arg| arg.annotation.as_ref())
{
let is_never_builtin = checker.settings.target_version >= Py311;
for annotation in annotations {
if checker if checker
.semantic_model() .semantic_model()
.match_typing_expr(annotation, "NoReturn") .match_typing_expr(annotation, "NoReturn")
{ {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
NoReturnArgumentAnnotationInStub { is_never_builtin }, NoReturnArgumentAnnotationInStub {
module: if checker.settings.target_version >= Py311 {
TypingModule::Typing
} else {
TypingModule::TypingExtensions
},
},
annotation.range(), 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"),
}
}
}

View File

@ -1,7 +1,7 @@
--- ---
source: crates/ruff/src/rules/flake8_pyi/mod.rs 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): ... 6 | def foo(arg): ...
7 | def foo_int(arg: int): ... 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, 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, 10 | arg: typing_extensions.NoReturn,
11 | ): ... # Error: PYI050 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): ... 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 11 | ): ... # Error: PYI050
12 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050 12 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050