Diffcontext / diffcontext /impact /blast_radius.py
trakshan-mishra
Deploy FastAPI & MCP server over SSE
036a2db
Raw
History Blame Contribute Delete
1.78 kB
"""
blast_radius.py — Find all symbols transitively affected by a change.
Given a changed symbol, walks the REVERSE graph (callers) to find
everything that depends on it.
Performance fix: accept an optional pre-built reverse graph so the caller
can build it once and reuse it across multiple symbols instead of
rebuilding it O(N) times.
"""
from typing import Dict, List, Optional, Set
def build_reverse_graph(graph: Dict[str, List[str]]) -> Dict[str, Set[str]]:
"""Build the reverse (caller) graph once; reuse for many queries."""
reverse: Dict[str, Set[str]] = {}
for caller, callees in graph.items():
for callee in callees:
reverse.setdefault(callee, set()).add(caller)
return reverse
def get_blast_radius(
graph: Dict[str, List[str]],
changed_symbol: str,
reverse: Optional[Dict[str, Set[str]]] = None,
) -> List[str]:
"""
All functions that (transitively) call changed_symbol.
Args:
graph: Forward call graph (caller -> [callees]).
changed_symbol: The symbol whose blast radius we want.
reverse: Pre-built reverse graph. If None it is built here
(correct but wasteful when called in a loop).
Uses iterative DFS with cycle detection.
"""
if reverse is None:
reverse = build_reverse_graph(graph)
affected: List[str] = []
visited: Set[str] = set()
# Iterative DFS on reverse graph
stack = [changed_symbol]
visited.add(changed_symbol)
while stack:
current = stack.pop()
for caller in reverse.get(current, ()):
if caller not in visited:
visited.add(caller)
affected.append(caller)
stack.append(caller)
return affected