Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / codegen / inline / definition_edits.py: 20%
66 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15"""Rewrite or remove top-level definitions after constructor replacement."""
17from __future__ import annotations
19import ast
20from collections.abc import Iterable
22from hyper_parallel.codegen.astkit.edits import TextEdit, apply_edits
23from hyper_parallel.codegen.inline.ir import ClassRemovalPatch
26def drop_shadowed_definitions(text: str, imported_names: Iterable[str]) -> str:
27 """Remove decorated definitions shadowing imported runtime helpers."""
28 names = {name for name in imported_names if name}
29 if not names:
30 return text
31 tree = ast.parse(text)
32 offsets = _line_offsets(text)
33 edits = [
34 TextEdit(start, _skip_trailing_blank_lines(text, end), "")
35 for node in tree.body
36 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
37 and node.name in names
38 and node.decorator_list
39 for start, end in (_node_span(node, offsets),)
40 ]
41 return apply_edits(text, edits) if edits else text
44def apply_class_removals(
45 text: str, patches: Iterable[ClassRemovalPatch]
46) -> str:
47 """Rewrite surviving references and delete classes made unused by patches."""
48 patch_list = list(patches)
49 if not patch_list:
50 return text
51 tree = ast.parse(text)
52 offsets = _line_offsets(text)
53 names = {patch.old_name for patch in patch_list}
54 removable = {name for name in names if not _name_is_referenced(tree, name)}
55 if not removable:
56 return text
57 replacements = {patch.old_name: patch.new_name for patch in patch_list}
58 classes = {
59 node.name: node
60 for node in tree.body
61 if isinstance(node, ast.ClassDef) and node.name in removable
62 }
63 deletion_spans = [_node_span(node, offsets) for node in classes.values()]
64 edits = _reference_edits(tree, offsets, removable, replacements, deletion_spans)
65 for name, node in classes.items():
66 del name
67 start, end = _node_span(node, offsets)
68 edits.append(TextEdit(start, _skip_trailing_blank_lines(text, end), ""))
69 return apply_edits(text, edits) if edits else text
72def _reference_edits(
73 tree: ast.AST,
74 offsets: tuple[int, ...],
75 removable: set[str],
76 replacements: dict[str, str],
77 deletion_spans: list[tuple[int, int]],
78) -> list[TextEdit]:
79 """Rewrite references that survive class deletion."""
80 edits = []
81 for node in ast.walk(tree):
82 if not isinstance(node, ast.Name) or node.id not in removable:
83 continue
84 start, end = _node_span(node, offsets)
85 if any(span <= start < stop for span, stop in deletion_spans):
86 continue
87 edits.append(TextEdit(start, end, replacements[node.id]))
88 return edits
91def _name_is_referenced(tree: ast.AST, name: str) -> bool:
92 """Return whether a name survives outside its own definition."""
93 return any(
94 (isinstance(node, ast.Name) and node.id == name)
95 or (isinstance(node, ast.Attribute) and node.attr == name)
96 for node in ast.walk(tree)
97 )
100def _node_span(node: ast.AST, offsets: tuple[int, ...]) -> tuple[int, int]:
101 """Return a node span including decorators."""
102 line, column = node.lineno, node.col_offset
103 decorators = getattr(node, "decorator_list", None)
104 if decorators:
105 first = min(decorators, key=lambda item: item.lineno)
106 if first.lineno < line:
107 line, column = first.lineno, max(0, first.col_offset - 1)
108 return (
109 offsets[line - 1] + column,
110 offsets[node.end_lineno - 1] + node.end_col_offset,
111 )
114def _line_offsets(source_text: str) -> tuple[int, ...]:
115 """Return absolute offsets for source line starts."""
116 offsets = [0]
117 for line in source_text.splitlines(keepends=True):
118 offsets.append(offsets[-1] + len(line))
119 return tuple(offsets)
122def _skip_trailing_blank_lines(text: str, end: int) -> int:
123 """Advance beyond whitespace following a deleted definition."""
124 while end < len(text) and text[end] in " \t":
125 end += 1
126 while end < len(text) and text[end] == "\n":
127 end += 1
128 while end < len(text) and text[end] in " \t":
129 end += 1
130 return end
133__all__ = ["apply_class_removals", "drop_shadowed_definitions"]