Skip to content

Commit 4f97de7

Browse files
committed
Improve deep_replace performance.
Now we stop iterating over child nodes as soon as we replaced the mutation. The original method would continue in case there are multiple copies of `old_node` to replace.
1 parent 71fd1fa commit 4f97de7

File tree

1 file changed

+24
-2
lines changed

1 file changed

+24
-2
lines changed

mutmut/file_mutation.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ def function_trampoline_arrangement(function: cst.FunctionDef, mutants: Iterable
241241
mutant_name = f'{mangled_name}_{i+1}'
242242
mutant_names.append(mutant_name)
243243
mutated_method = function.with_changes(name=cst.Name(mutant_name))
244-
mutated_method = mutated_method.deep_replace(mutant.original_node, mutant.mutated_node)
244+
mutated_method = deep_replace(mutated_method, mutant.original_node, mutant.mutated_node)
245245
nodes.append(mutated_method) # type: ignore
246246

247247
# trampoline that forwards the calls
@@ -298,4 +298,26 @@ def pragma_no_mutate_lines(source: str) -> set[int]:
298298
i + 1
299299
for i, line in enumerate(source.split('\n'))
300300
if '# pragma:' in line and 'no mutate' in line.partition('# pragma:')[-1]
301-
}
301+
}
302+
303+
def deep_replace(tree: cst.CSTNode, old_node: cst.CSTNode, new_node: cst.CSTNode) -> cst.CSTNode:
304+
"""Like the CSTNode.deep_replace method, except that we only replace up to one occurence of old_node."""
305+
return tree.visit(ChildReplacementTransformer(old_node, new_node)) # type: ignore
306+
307+
class ChildReplacementTransformer(cst.CSTTransformer):
308+
def __init__(self, old_node: cst.CSTNode, new_node: cst.CSTNode):
309+
self.old_node = old_node
310+
self.new_node = new_node
311+
self.replaced_node = False
312+
313+
def on_visit(self, node: cst.CSTNode) -> bool:
314+
# If the node is one we are about to replace, we shouldn't
315+
# recurse down it, that would be a waste of time.
316+
# Also, we stop recursion when we already replaced the node.
317+
return not (self.replaced_node or node is self.old_node)
318+
319+
def on_leave(self, original_node: cst.CSTNode, updated_node: cst.CSTNode) -> cst.CSTNode:
320+
if original_node is self.old_node:
321+
self.replaced_node = True
322+
return self.new_node
323+
return updated_node

0 commit comments

Comments
 (0)