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

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.""" 

16 

17from __future__ import annotations 

18 

19import ast 

20from collections.abc import Iterable 

21 

22from hyper_parallel.codegen.astkit.edits import TextEdit, apply_edits 

23from hyper_parallel.codegen.inline.ir import ClassRemovalPatch 

24 

25 

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 

42 

43 

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 

70 

71 

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 

89 

90 

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 ) 

98 

99 

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 ) 

112 

113 

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) 

120 

121 

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 

131 

132 

133__all__ = ["apply_class_removals", "drop_shadowed_definitions"]