From 6ef8edd39db03cc4663b5b7966d0f581d9492ddb Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 16 Aug 2026 20:13:26 +1000 Subject: [PATCH 01/12] Implement patchedast for type alias --- CHANGELOG.md | 3 ++- rope/refactor/patchedast.py | 4 ++++ ropetest/refactor/patchedasttest.py | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4431f01bc..e8b2d875c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # **Upcoming release** - #850 Update and pin black version in pre-commit and Github Actions -- #850 Bump supported python version to up to Python 3.14 +- #851 Bump supported python version to up to Python 3.14 +- #852 Implement patchedast for TypeAlias # Release 1.14.0 diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index 4e842c046..614c3c71c 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -825,6 +825,10 @@ def _MatchMapping(self, node): children.append("}") self._handle(node, children) + def _TypeAlias(self, node): + children = ["type", node.name, node.value] + self._handle(node, children) + class _Source: def __init__(self, source): diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index 3ac341f25..7dec9430e 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -1510,6 +1510,20 @@ def test_match_node_with_match_mapping_match_as(self): "}", ]) + def test_type_alias(self): + source = dedent("""\ + type Point = tuple[float, float] + """) + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + checker.check_children("TypeAlias", [ + "type", + " ", + "Name", + " = ", + "Subscript", + ]) + class _ResultChecker: def __init__(self, test_case, ast): From 8fb9cdb407974373495ae0f2d8d13f22ded66629 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 16 Aug 2026 22:48:33 +1000 Subject: [PATCH 02/12] Implement patchedast for TypeVar syntax in function definition --- CHANGELOG.md | 3 +- rope/refactor/patchedast.py | 11 +++ ropetest/refactor/patchedasttest.py | 113 ++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8b2d875c..a67d1c2b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ - #850 Update and pin black version in pre-commit and Github Actions - #851 Bump supported python version to up to Python 3.14 -- #852 Implement patchedast for TypeAlias +- #852 Implement patchedast handlers for TypeAlias +- #853 Implement patchedast handlers TypeVar # Release 1.14.0 diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index 614c3c71c..924b2cb56 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -491,6 +491,9 @@ def _handle_function_def_node(self, node, is_async): children.extend(("@", decorator)) children.extend(["async", "def"] if is_async else ["def"]) children.append(node.name) + type_params = getattr(node, "type_params") + if type_params: + children.extend(["[", *self._child_nodes(type_params, ","), "]"]) children.extend(["(", node.args, ")"]) children.append(":") children.extend(node.body) @@ -829,6 +832,14 @@ def _TypeAlias(self, node): children = ["type", node.name, node.value] self._handle(node, children) + def _TypeVar(self, node): + children = [node.name] + if node.bound: + children.extend([":", node.bound]) + if node.default_value: + children.extend(["=", node.default_value]) + self._handle(node, children) + class _Source: def __init__(self, source): diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index 7dec9430e..8d50ae022 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -37,6 +37,28 @@ def assert_single_case_match_block(self, checker, match_type): "Expr", ]) + def assert_function_def_has_one_type_var(self, checker): + checker.check_children( + "FunctionDef", + [ + "def", + " ", + "foo", + "", + "[", + "", + "TypeVar", + "", + "]", + "", + "(", "", "arguments", "", ")", + "", + ":", + "\n ", + "Pass", + ], + ) + def test_operator_support_completeness(self): ast_ops = { n.__name__ @@ -1524,6 +1546,97 @@ def test_type_alias(self): "Subscript", ]) + def test_type_var_simple(self): + source = dedent("""\ + def foo[S, T](x): + pass + """) + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + + checker.check_children( + "FunctionDef", + [ + "def", + " ", + "foo", + "", + "[", + "", + "TypeVar", + "", + ",", + " ", + "TypeVar", + "", + "]", + "", + "(", "", "arguments", "", ")", + "", + ":", + "\n ", + "Pass", + ], + ) + + def test_type_var_with_default_value(self): + source = dedent("""\ + def foo[T = D](x): + pass + """) + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + + self.assert_function_def_has_one_type_var(checker) + + checker.check_children("TypeVar", [ + "T", + " ", + "=", + " ", + "Name", + ]) + + def test_type_var_with_constraint(self): + source = dedent("""\ + def foo[T: (A, B)](x): + pass + """) + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + + self.assert_function_def_has_one_type_var(checker) + + checker.check_children("TypeVar", [ + "T", + "", + ":", + " ", + "Tuple", + ]) + + def test_type_var_with_constraint_and_default_value(self): + source = dedent("""\ + def foo[T: (A, B) = D](x): + pass + """) + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + + self.assert_function_def_has_one_type_var(checker) + + checker.check_children("TypeVar", [ + "T", + "", + ":", + " ", + "Tuple", + " ", + "=", + " ", + "Name", + ]) + class _ResultChecker: def __init__(self, test_case, ast): From b79c997aa5982e8c5db1f52ad509cdc3d9432f16 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 16 Aug 2026 23:01:19 +1000 Subject: [PATCH 03/12] FunctionDef.type_params attribute only since Python 3.12 and higher --- rope/refactor/patchedast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index 924b2cb56..0168ad614 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -491,7 +491,7 @@ def _handle_function_def_node(self, node, is_async): children.extend(("@", decorator)) children.extend(["async", "def"] if is_async else ["def"]) children.append(node.name) - type_params = getattr(node, "type_params") + type_params = getattr(node, "type_params", []) if type_params: children.extend(["[", *self._child_nodes(type_params, ","), "]"]) children.extend(["(", node.args, ")"]) From 9c7442a7cae14cb5bf41bd359672c14680459f55 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Sun, 16 Aug 2026 23:42:07 +1000 Subject: [PATCH 04/12] TypeVar.default_value only exists on Python 3.13 and up --- rope/refactor/patchedast.py | 3 ++- ropetest/refactor/patchedasttest.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index 0168ad614..aab82e56e 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -836,7 +836,8 @@ def _TypeVar(self, node): children = [node.name] if node.bound: children.extend([":", node.bound]) - if node.default_value: + default_value = getattr(node, "default_value", None) + if default_value: children.extend(["=", node.default_value]) self._handle(node, children) diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index 8d50ae022..0bf59f8ef 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -1579,6 +1579,7 @@ def foo[S, T](x): ], ) + @testutils.only_for_versions_higher("3.13") def test_type_var_with_default_value(self): source = dedent("""\ def foo[T = D](x): @@ -1615,6 +1616,7 @@ def foo[T: (A, B)](x): "Tuple", ]) + @testutils.only_for_versions_higher("3.13") def test_type_var_with_constraint_and_default_value(self): source = dedent("""\ def foo[T: (A, B) = D](x): From 2223c65c8987ad69c7e7ff5812da236575fdd8a8 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Mon, 17 Aug 2026 00:04:24 +1000 Subject: [PATCH 05/12] TypeAlias and TypeVar only exists on Python 3.12 and up --- ropetest/refactor/patchedasttest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index 0bf59f8ef..af0da1fb0 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -1532,6 +1532,7 @@ def test_match_node_with_match_mapping_match_as(self): "}", ]) + @testutils.only_for_versions_higher("3.12") def test_type_alias(self): source = dedent("""\ type Point = tuple[float, float] @@ -1546,6 +1547,7 @@ def test_type_alias(self): "Subscript", ]) + @testutils.only_for_versions_higher("3.12") def test_type_var_simple(self): source = dedent("""\ def foo[S, T](x): @@ -1598,6 +1600,7 @@ def foo[T = D](x): "Name", ]) + @testutils.only_for_versions_higher("3.12") def test_type_var_with_constraint(self): source = dedent("""\ def foo[T: (A, B)](x): From 9bb112680ee5ed38b8222022ab7956d0c8e7e295 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Mon, 17 Aug 2026 00:20:33 +1000 Subject: [PATCH 06/12] Implement patchedast for TypeVarTuple --- rope/refactor/patchedast.py | 6 +++ ropetest/refactor/patchedasttest.py | 58 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index aab82e56e..aa1cc2866 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -841,6 +841,12 @@ def _TypeVar(self, node): children.extend(["=", node.default_value]) self._handle(node, children) + def _TypeVarTuple(self, node): + children = ["*", node.name] + if node.default_value: + children.extend(["=", node.default_value]) + self._handle(node, children) + class _Source: def __init__(self, source): diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index af0da1fb0..4f296a8db 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -59,6 +59,28 @@ def assert_function_def_has_one_type_var(self, checker): ], ) + def assert_function_def_has_one_type_var_tuple(self, checker): + checker.check_children( + "FunctionDef", + [ + "def", + " ", + "foo", + "", + "[", + "", + "TypeVarTuple", + "", + "]", + "", + "(", "", "arguments", "", ")", + "", + ":", + "\n ", + "Pass", + ], + ) + def test_operator_support_completeness(self): ast_ops = { n.__name__ @@ -1642,6 +1664,42 @@ def foo[T: (A, B) = D](x): "Name", ]) + def test_type_var_tuple_simple(self): + source = dedent("""\ + def foo[*T](x): + pass + """) + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + + self.assert_function_def_has_one_type_var_tuple(checker) + + checker.check_children("TypeVarTuple", [ + "*", + "", + "T", + ]) + + def test_type_var_tuple_with_default_value(self): + source = dedent("""\ + def foo[*T = (A, B)](x): + pass + """) + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + + self.assert_function_def_has_one_type_var_tuple(checker) + + checker.check_children("TypeVarTuple", [ + "*", + "", + "T", + " ", + "=", + " ", + "Tuple", + ]) + class _ResultChecker: def __init__(self, test_case, ast): From e78b4fa81831bed0f7717aff01fe4547219e0452 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Mon, 17 Aug 2026 00:28:23 +1000 Subject: [PATCH 07/12] Implement patchedast for ParamSpec --- rope/refactor/patchedast.py | 6 +++ ropetest/refactor/patchedasttest.py | 62 +++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index aa1cc2866..fbe5ace2d 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -847,6 +847,12 @@ def _TypeVarTuple(self, node): children.extend(["=", node.default_value]) self._handle(node, children) + def _ParamSpec(self, node): + children = ["**", node.name] + if node.default_value: + children.extend(["=", node.default_value]) + self._handle(node, children) + class _Source: def __init__(self, source): diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index 4f296a8db..499e557be 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -45,11 +45,7 @@ def assert_function_def_has_one_type_var(self, checker): " ", "foo", "", - "[", - "", - "TypeVar", - "", - "]", + "[", "", "TypeVar", "", "]", "", "(", "", "arguments", "", ")", "", @@ -67,11 +63,25 @@ def assert_function_def_has_one_type_var_tuple(self, checker): " ", "foo", "", - "[", + "[", "", "TypeVarTuple", "", "]", "", - "TypeVarTuple", + "(", "", "arguments", "", ")", "", - "]", + ":", + "\n ", + "Pass", + ], + ) + + def assert_function_def_has_one_param_spec(self, checker): + checker.check_children( + "FunctionDef", + [ + "def", + " ", + "foo", + "", + "[", "", "ParamSpec", "", "]", "", "(", "", "arguments", "", ")", "", @@ -1700,6 +1710,42 @@ def foo[*T = (A, B)](x): "Tuple", ]) + def test_param_spec_simple(self): + source = dedent("""\ + def foo[**T](x): + pass + """) + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + + self.assert_function_def_has_one_param_spec(checker) + + checker.check_children("ParamSpec", [ + "**", + "", + "T", + ]) + + def test_param_spec_with_default_value(self): + source = dedent("""\ + def foo[**T = (A, B)](x): + pass + """) + ast_frag = patchedast.get_patched_ast(source, True) + checker = _ResultChecker(self, ast_frag) + + self.assert_function_def_has_one_param_spec(checker) + + checker.check_children("ParamSpec", [ + "**", + "", + "T", + " ", + "=", + " ", + "Tuple", + ]) + class _ResultChecker: def __init__(self, test_case, ast): From e28ed9c0f4c0a9951bc3c807351098ac156bc83a Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Mon, 17 Aug 2026 00:33:09 +1000 Subject: [PATCH 08/12] fixup! TypeVar.default_value only exists on Python 3.13 and up --- rope/refactor/patchedast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index fbe5ace2d..e67349454 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -838,7 +838,7 @@ def _TypeVar(self, node): children.extend([":", node.bound]) default_value = getattr(node, "default_value", None) if default_value: - children.extend(["=", node.default_value]) + children.extend(["=", default_value]) self._handle(node, children) def _TypeVarTuple(self, node): From 6565428713a9a8e7caca74b16cfff079e3a7cfe6 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Mon, 17 Aug 2026 00:34:38 +1000 Subject: [PATCH 09/12] TypeVarTuple.default_value only exist in Python 3.13 and up --- rope/refactor/patchedast.py | 5 +++-- ropetest/refactor/patchedasttest.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index e67349454..676e3d2e4 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -843,8 +843,9 @@ def _TypeVar(self, node): def _TypeVarTuple(self, node): children = ["*", node.name] - if node.default_value: - children.extend(["=", node.default_value]) + default_value = getattr(node, "default_value", None) + if default_value: + children.extend(["=", default_value]) self._handle(node, children) def _ParamSpec(self, node): diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index 499e557be..d294408bf 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -1690,6 +1690,7 @@ def foo[*T](x): "T", ]) + @testutils.only_for_versions_higher("3.13") def test_type_var_tuple_with_default_value(self): source = dedent("""\ def foo[*T = (A, B)](x): From af114dd185408ec72b8f66a2f14da62588c1372f Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Mon, 17 Aug 2026 00:34:49 +1000 Subject: [PATCH 10/12] ParamSpec.default_value only exist in Python 3.13 and up --- rope/refactor/patchedast.py | 5 +++-- ropetest/refactor/patchedasttest.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index 676e3d2e4..78fbc19a4 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -850,8 +850,9 @@ def _TypeVarTuple(self, node): def _ParamSpec(self, node): children = ["**", node.name] - if node.default_value: - children.extend(["=", node.default_value]) + default_value = getattr(node, "default_value", None) + if default_value: + children.extend(["=", default_value]) self._handle(node, children) diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index d294408bf..db3e534d2 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -1727,6 +1727,7 @@ def foo[**T](x): "T", ]) + @testutils.only_for_versions_higher("3.13") def test_param_spec_with_default_value(self): source = dedent("""\ def foo[**T = (A, B)](x): From cad67a81d4ba157a9411af7c3d1442481247fbe0 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Mon, 17 Aug 2026 00:37:21 +1000 Subject: [PATCH 11/12] TypeVarTuple and ParamSpec only exists in Python 3.12 and up --- ropetest/refactor/patchedasttest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ropetest/refactor/patchedasttest.py b/ropetest/refactor/patchedasttest.py index db3e534d2..57fdc0e08 100644 --- a/ropetest/refactor/patchedasttest.py +++ b/ropetest/refactor/patchedasttest.py @@ -1674,6 +1674,7 @@ def foo[T: (A, B) = D](x): "Name", ]) + @testutils.only_for_versions_higher("3.12") def test_type_var_tuple_simple(self): source = dedent("""\ def foo[*T](x): @@ -1711,6 +1712,7 @@ def foo[*T = (A, B)](x): "Tuple", ]) + @testutils.only_for_versions_higher("3.12") def test_param_spec_simple(self): source = dedent("""\ def foo[**T](x): From 8516dd631e1c134353a696eb8609ee1118b3b491 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Mon, 17 Aug 2026 00:45:18 +1000 Subject: [PATCH 12/12] Refactor _PatchingASTWalker._handle_default_value() --- rope/refactor/patchedast.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rope/refactor/patchedast.py b/rope/refactor/patchedast.py index 78fbc19a4..d3a14102c 100644 --- a/rope/refactor/patchedast.py +++ b/rope/refactor/patchedast.py @@ -836,24 +836,23 @@ def _TypeVar(self, node): children = [node.name] if node.bound: children.extend([":", node.bound]) - default_value = getattr(node, "default_value", None) - if default_value: - children.extend(["=", default_value]) + self._handle_default_value(node, children) self._handle(node, children) def _TypeVarTuple(self, node): children = ["*", node.name] - default_value = getattr(node, "default_value", None) - if default_value: - children.extend(["=", default_value]) + self._handle_default_value(node, children) self._handle(node, children) def _ParamSpec(self, node): children = ["**", node.name] + self._handle_default_value(node, children) + self._handle(node, children) + + def _handle_default_value(self, node, children): default_value = getattr(node, "default_value", None) if default_value: children.extend(["=", default_value]) - self._handle(node, children) class _Source: