From c3b01fcc615a7ba0a455c44b122f78c225f96860 Mon Sep 17 00:00:00 2001 From: Steve C Date: Fri, 1 Nov 2024 18:26:07 -0400 Subject: [PATCH 1/2] [`pyupgrade`] - ignore kwarg unpacking for `UP044` --- .../resources/test/fixtures/pyupgrade/UP044.py | 8 ++++++++ .../src/rules/pyupgrade/rules/use_pep646_unpack.rs | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP044.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP044.py index cd5aebe3780d1..7468a2635bba3 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP044.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP044.py @@ -17,3 +17,11 @@ def f(*args: Unpack[other.Type]): pass def foo(*args: Unpack[int | str]) -> None: pass def foo(*args: Unpack[int and str]) -> None: pass def foo(*args: Unpack[int > str]) -> None: pass + +# We do not use the shorthand unpacking syntax in the following cases +from typing import TypedDict +class KwargsDict(TypedDict): + x: int + y: int + +def foo(name: str, /, **kwargs: Unpack[KwargsDict]) -> None: pass diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs index 454b7b0c8b4e1..93b38dd0f28b9 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs @@ -58,6 +58,17 @@ pub(crate) fn use_pep646_unpack(checker: &mut Checker, expr: &ExprSubscript) { return; } + // ignore kwarg unpacks like `def f(**kwargs: Unpack[CustomTypedDict]):` + if checker + .semantic() + .current_statement() + .as_function_def_stmt() + .and_then(|stmt| stmt.parameters.kwarg.as_ref()) + .map_or(false, |kwarg| kwarg.range.contains_range(expr.range)) + { + return; + } + let ExprSubscript { range, value, From b9bc57dd0976b4447e7885ce012e2e2093ddb8ce Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 2 Nov 2024 12:59:06 -0400 Subject: [PATCH 2/2] Simplify --- .../rules/pyupgrade/rules/use_pep646_unpack.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs index 93b38dd0f28b9..4ac19e34715cc 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/use_pep646_unpack.rs @@ -11,7 +11,7 @@ use crate::{checkers::ast::Checker, settings::types::PythonVersion}; /// ## Why is this bad? /// [PEP 646] introduced a new syntax for unpacking sequences based on the `*` /// operator. This syntax is more concise and readable than the previous -/// `typing.Unpack` syntax. +/// `Unpack[]` syntax. /// /// ## Example /// @@ -30,8 +30,7 @@ use crate::{checkers::ast::Checker, settings::types::PythonVersion}; /// pass /// ``` /// -/// ## References -/// - [PEP 646](https://peps.python.org/pep-0646/#unpack-for-backwards-compatibility) +/// [PEP 646]: https://peps.python.org/pep-0646/ #[violation] pub struct NonPEP646Unpack; @@ -58,13 +57,19 @@ pub(crate) fn use_pep646_unpack(checker: &mut Checker, expr: &ExprSubscript) { return; } - // ignore kwarg unpacks like `def f(**kwargs: Unpack[CustomTypedDict]):` + // Ignore `kwarg` unpacking, as in: + // ```python + // def f(**kwargs: Unpack[Array]): + // ... + // ``` if checker .semantic() .current_statement() .as_function_def_stmt() .and_then(|stmt| stmt.parameters.kwarg.as_ref()) - .map_or(false, |kwarg| kwarg.range.contains_range(expr.range)) + .and_then(|kwarg| kwarg.annotation.as_ref()) + .and_then(|annotation| annotation.as_subscript_expr()) + .is_some_and(|subscript| subscript == expr) { return; }