From 06207f615d9fa5b08ff639f0f40faab2d91e19e2 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 10 Jun 2023 17:32:02 -0400 Subject: [PATCH] support PEP 563 rewrites for 3.12+ TypeVar bounds --- pyupgrade/_plugins/typing_pep563.py | 13 +++++++++++++ tests/features/typing_pep563_test.py | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/pyupgrade/_plugins/typing_pep563.py b/pyupgrade/_plugins/typing_pep563.py index 0eec8af1..5b16f193 100644 --- a/pyupgrade/_plugins/typing_pep563.py +++ b/pyupgrade/_plugins/typing_pep563.py @@ -176,3 +176,16 @@ def visit_AnnAssign( if not _supported_version(state): return yield from _replace_string_literal(node.annotation) + + +if sys.version_info >= (3, 12): # pragma: >=3.12 cover + @register(ast.TypeVar) + def visit_TypeVar( + state: State, + node: ast.TypeVar, + parent: ast.AST, + ) -> Iterable[tuple[Offset, TokenFunc]]: + if not _supported_version(state): + return + if node.bound is not None: + yield from _replace_string_literal(node.bound) diff --git a/tests/features/typing_pep563_test.py b/tests/features/typing_pep563_test.py index 85f7a419..59e77801 100644 --- a/tests/features/typing_pep563_test.py +++ b/tests/features/typing_pep563_test.py @@ -1,5 +1,7 @@ from __future__ import annotations +import sys + import pytest from pyupgrade._data import Settings @@ -54,6 +56,15 @@ 'x: Annotated[1:2] = ...\n', id='Annotated with invalid slice', ), + pytest.param( + 'def f[X: "int"](x: X) -> X: return x\n', + id='TypeVar quoted bound but no __future__ annotations', + ), + pytest.param( + 'from __future__ import annotations\n' + 'def f[X](x: X) -> X: return x\n', + id='TypeVar without bound', + ), ), ) def test_fix_typing_pep563_noop(s): @@ -363,3 +374,19 @@ def test_fix_typing_pep563_noop(s): def test_fix_typing_pep563(s, expected): ret = _fix_plugins(s, settings=Settings(min_version=(3, 7))) assert ret == expected + + +@pytest.mark.xfail(sys.version_info < (3, 12), reason='3.12+ syntax') +def test_typevar_bound(): + src = '''\ +from __future__ import annotations +def f[T: "int"](t: T) -> T: + return t +''' + expected = '''\ +from __future__ import annotations +def f[T: int](t: T) -> T: + return t +''' + ret = _fix_plugins(src, settings=Settings()) + assert ret == expected