weavatrix-graph 0.6.0

Deterministic, evidence-carrying graph primitives for repository intelligence
Documentation
use super::{Dominators, dominators_filtered};
use crate::{IndexGraphView, Vec};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DominanceFrontiers<Node> {
    root: Node,
    entries: Vec<(Node, Vec<Node>)>,
}

impl<Node> DominanceFrontiers<Node>
where
    Node: Copy + Eq,
{
    #[must_use]
    pub const fn root(&self) -> Node {
        self.root
    }

    #[must_use]
    pub fn frontier(&self, node: Node) -> Option<&[Node]> {
        self.entries
            .iter()
            .find_map(|(candidate, frontier)| (*candidate == node).then_some(frontier.as_slice()))
    }

    pub fn iter(&self) -> impl Iterator<Item = (Node, &[Node])> {
        self.entries
            .iter()
            .map(|(node, frontier)| (*node, frontier.as_slice()))
    }
}

#[must_use]
pub fn dominance_frontiers<G>(graph: &G, root: G::Node) -> Option<DominanceFrontiers<G::Node>>
where
    G: IndexGraphView,
{
    dominance_frontiers_filtered(graph, root, |_| true)
}

#[must_use]
pub fn dominance_frontiers_filtered<G, F>(
    graph: &G,
    root: G::Node,
    allows_edge: F,
) -> Option<DominanceFrontiers<G::Node>>
where
    G: IndexGraphView,
    F: Fn(G::Edge) -> bool,
{
    let mut allowed = vec![false; graph.edge_bound()];
    for edge in graph.edge_indices() {
        allowed[G::edge_slot(edge)] = allows_edge(edge);
    }
    let dominators = dominators_filtered(graph, root, |edge| allowed[G::edge_slot(edge)])?;
    let predecessors = reachable_predecessors(graph, &dominators, &allowed);
    let mut immediate = vec![None; graph.node_bound()];
    immediate[G::node_slot(root)] = Some(root);
    for (node, parent) in dominators.immediate_dominators() {
        immediate[G::node_slot(node)] = Some(parent);
    }
    let mut frontiers = vec![Vec::new(); graph.node_bound()];
    for &join in dominators.reachable_nodes() {
        let join_slot = G::node_slot(join);
        if predecessors[join_slot].len() < 2 {
            continue;
        }
        let stop = immediate[join_slot]?;
        for &predecessor in &predecessors[join_slot] {
            propagate::<G>(predecessor, stop, join, &immediate, &mut frontiers);
        }
    }
    let entries = dominators
        .reachable_nodes()
        .iter()
        .copied()
        .map(|node| {
            let frontier = &mut frontiers[G::node_slot(node)];
            frontier.sort_unstable_by_key(|candidate| G::node_slot(*candidate));
            frontier.dedup();
            (node, core::mem::take(frontier))
        })
        .collect();
    Some(DominanceFrontiers { root, entries })
}

fn reachable_predecessors<G>(
    graph: &G,
    dominators: &Dominators<G::Node>,
    allowed: &[bool],
) -> Vec<Vec<G::Node>>
where
    G: IndexGraphView,
{
    let mut reachable = vec![false; graph.node_bound()];
    for &node in dominators.reachable_nodes() {
        reachable[G::node_slot(node)] = true;
    }
    let mut predecessors = vec![Vec::new(); graph.node_bound()];
    for (edge, endpoints) in graph.edge_references() {
        if !allowed[G::edge_slot(edge)] {
            continue;
        }
        let source_slot = G::node_slot(endpoints.source());
        let target_slot = G::node_slot(endpoints.target());
        if !reachable[source_slot] || !reachable[target_slot] {
            continue;
        }
        let incoming = &mut predecessors[target_slot];
        if !incoming.contains(&endpoints.source()) {
            incoming.push(endpoints.source());
        }
    }
    predecessors
}

fn propagate<G>(
    mut runner: G::Node,
    stop: G::Node,
    join: G::Node,
    immediate: &[Option<G::Node>],
    frontiers: &mut [Vec<G::Node>],
) where
    G: IndexGraphView,
{
    while runner != stop {
        let slot = G::node_slot(runner);
        frontiers[slot].push(join);
        let Some(parent) = immediate[slot] else {
            break;
        };
        if parent == runner {
            break;
        }
        runner = parent;
    }
}