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..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,6 +57,23 @@ pub(crate) fn use_pep646_unpack(checker: &mut Checker, expr: &ExprSubscript) { return; } + // 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()) + .and_then(|kwarg| kwarg.annotation.as_ref()) + .and_then(|annotation| annotation.as_subscript_expr()) + .is_some_and(|subscript| subscript == expr) + { + return; + } + let ExprSubscript { range, value,