From 2a996f9d4107887970dec67d74063c9620a320ed Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 30 Jul 2023 22:18:33 -0400 Subject: [PATCH] rewrite pep 646 Unpack to splat in `*args` --- pyupgrade/_plugins/typing_pep646_unpack.py | 39 +++++++++++++++++++++ tests/features/typing_pep646_unpack_test.py | 17 +++++++++ 2 files changed, 56 insertions(+) diff --git a/pyupgrade/_plugins/typing_pep646_unpack.py b/pyupgrade/_plugins/typing_pep646_unpack.py index 632814a7..4cee0d23 100644 --- a/pyupgrade/_plugins/typing_pep646_unpack.py +++ b/pyupgrade/_plugins/typing_pep646_unpack.py @@ -37,3 +37,42 @@ def visit_Subscript( if is_name_attr(node.value, state.from_imports, ('typing',), ('Unpack',)): if isinstance(parent, (ast.Subscript, ast.Index)): yield ast_to_offset(node.value), _replace_unpack_with_star + + +def _visit_func( + state: State, + node: ast.AsyncFunctionDef | ast.FunctionDef, + parent: ast.AST, +) -> Iterable[tuple[Offset, TokenFunc]]: + if state.settings.min_version < (3, 11): + return + + vararg = node.args.vararg + if ( + vararg is not None and + isinstance(vararg.annotation, ast.Subscript) and + is_name_attr( + vararg.annotation.value, + state.from_imports, + ('typing',), ('Unpack',), + ) + ): + yield ast_to_offset(vararg.annotation.value), _replace_unpack_with_star + + +@register(ast.AsyncFunctionDef) +def visit_AsyncFunctionDef( + state: State, + node: ast.AsyncFunctionDef, + parent: ast.AST, +) -> Iterable[tuple[Offset, TokenFunc]]: + yield from _visit_func(state, node, parent) + + +@register(ast.FunctionDef) +def visit_FunctionDef( + state: State, + node: ast.FunctionDef, + parent: ast.AST, +) -> Iterable[tuple[Offset, TokenFunc]]: + yield from _visit_func(state, node, parent) diff --git a/tests/features/typing_pep646_unpack_test.py b/tests/features/typing_pep646_unpack_test.py index c1d82e85..d63909ad 100644 --- a/tests/features/typing_pep646_unpack_test.py +++ b/tests/features/typing_pep646_unpack_test.py @@ -21,6 +21,14 @@ ' pass', id='Not inside a subscript', ), + pytest.param( + 'from typing import Unpack\n' + 'from typing import TypedDict\n' + 'class D(TypedDict):\n' + ' x: int\n' + 'def f(**kwargs: Unpack[D]) -> None: pass\n', + id='3.12 TypedDict for kwargs', + ), ), ) def test_fix_pep646_noop(s): @@ -53,6 +61,15 @@ def test_fix_pep646_noop(s): 'class C(Generic[*Shape]):\n' ' pass', ), + pytest.param( + 'from typing import Unpack\n' + 'def f(*args: Unpack[tuple[int, ...]]): pass\n', + + 'from typing import Unpack\n' + 'def f(*args: *tuple[int, ...]): pass\n', + + id='Unpack for *args', + ), ), ) def test_typing_unpack(s, expected):