Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- #623, #819, #863 Support MatchOr, MatchSequence, MatchStar (@jheld, @lieryan)
- #870 Add default implementation for is_dir() (@lieryan)
- #872 Fix unicode handling in patchedast (@lieryan)
- #845 Support PEP 695 type parameters on class definitions and type aliases in patchedast (@marlon-costa-dc)

# Release 1.14.0

Expand Down
15 changes: 11 additions & 4 deletions rope/refactor/patchedast.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ def _ClassDef(self, node):
for decorator in node.decorator_list:
children.extend(("@", decorator))
children.extend(["class", node.name])
self._add_type_params(node, children)
if node.bases:
children.append("(")
children.extend(self._child_nodes(node.bases, ","))
Expand Down Expand Up @@ -493,9 +494,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", [])
if type_params:
children.extend(["[", *self._child_nodes(type_params, ","), "]"])
self._add_type_params(node, children)
children.extend(["(", node.args, ")"])
children.append(":")
children.extend(node.body)
Expand Down Expand Up @@ -872,9 +871,17 @@ def _MatchMapping(self, node):
self._handle(node, children)

def _TypeAlias(self, node):
children = ["type", node.name, node.value]
children = ["type", node.name]
self._add_type_params(node, children)
children.append(node.value)
self._handle(node, children)

def _add_type_params(self, node, children):
"""Append the PEP 695 ``[T, ...]`` clause of a def, class or type alias."""
type_params = getattr(node, "type_params", [])
if type_params:
children.extend(["[", *self._child_nodes(type_params, ","), "]"])

def _TypeVar(self, node):
children = [node.name]
if node.bound:
Expand Down
77 changes: 77 additions & 0 deletions ropetest/refactor/patchedasttest.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,83 @@ def test_handling_format_strings_basic(self):
checker.check_children("JoinedStr", ['f"', "abc", "FormattedValue", "", '"'])
checker.check_children("FormattedValue", ["{", "", "Name", "", "}"])

@testutils.only_for_versions_higher("3.12")
def test_handling_pep695_type_alias(self):
source = dedent("""\
type Alias[T] = list[T]
""")
ast_frag = patchedast.get_patched_ast(source, True)
checker = _ResultChecker(self, ast_frag)
checker.check_children(
"TypeAlias",
["type", " ", "Name", "", "[", "", "TypeVar", "", "]", " = ",
"Subscript"],
)

@testutils.only_for_versions_higher("3.12")
def test_handling_pep695_generic_function(self):
# TP occurs only in the type parameter list, so its region can only
# come from the "[TP]" clause rendered between the name and "(".
source = dedent("""\
def f[TP](x):
return x
""")
ast_frag = patchedast.get_patched_ast(source, True)
checker = _ResultChecker(self, ast_frag)
checker.check_children(
"FunctionDef",
["def", " ", "f", "", "[", "", "TypeVar", "", "]", "", "(", "",
"arguments", "", ")", "", ":", "\n ", "Return"],
)
start = source.index("TP")
checker.check_region("TypeVar", start, start + len("TP"))

@testutils.only_for_versions_higher("3.12")
def test_handling_pep695_generic_class(self):
source = dedent("""\
class C[TP]:
pass
""")
ast_frag = patchedast.get_patched_ast(source, True)
checker = _ResultChecker(self, ast_frag)
checker.check_children(
"ClassDef",
["class", " ", "C", "", "[", "", "TypeVar", "", "]", "", ":",
"\n ", "Pass"],
)
start = source.index("TP")
checker.check_region("TypeVar", start, start + len("TP"))

@testutils.only_for_versions_higher("3.10")
def test_handling_match_sequence_and_star(self):
source = dedent("""\
match x:
case [1, *rest]:
pass
""")
ast_frag = patchedast.get_patched_ast(source, True)
checker = _ResultChecker(self, ast_frag)
checker.check_children(
"MatchSequence",
["[", "", "MatchValue", "", ",", " ", "MatchStar", "", "]"],
)
start = source.index("[1, *rest]")
checker.check_region("MatchSequence", start, start + len("[1, *rest]"))
checker.check_children("MatchStar", ["*", "", "rest"])

@testutils.only_for_versions_higher("3.10")
def test_handling_match_or_and_singleton(self):
source = dedent("""\
match x:
case 1 | None:
pass
""")
ast_frag = patchedast.get_patched_ast(source, True)
checker = _ResultChecker(self, ast_frag)
checker.check_children(
"MatchOr", ["MatchValue", " ", "|", " ", "MatchSingleton"]
)

@testutils.only_for_versions_higher("3.6")
def test_handling_format_strings_with_implicit_join(self):
source = '''"1" + rf'abc{a}' f"""xxx{b} """\n'''
Expand Down
Loading