use super::anchor_index::AnchorInfo;
use super::pub_fns::PubFnInfo;
use super::workspace_graph::{canonical_name_for_pub_fn, CallGraph};
use super::HandlerTouchpoints;
use crate::adapters::analyzers::architecture::compiled::CompiledCallParity;
use crate::adapters::analyzers::architecture::{MatchLocation, ViolationKind};
use std::collections::HashMap;
pub(crate) fn check_multiplicity_mismatch<'ast>(
pub_fns_by_layer: &HashMap<String, Vec<PubFnInfo<'ast>>>,
graph: &CallGraph,
touchpoints: &HandlerTouchpoints,
cp: &CompiledCallParity,
) -> Vec<MatchLocation> {
let counts = build_per_adapter_target_counts(pub_fns_by_layer, touchpoints, cp);
let mut out = Vec::new();
if let Some(targets) = pub_fns_by_layer.get(&cp.target) {
for info in targets {
let canonical = canonical_name_for_pub_fn(info);
if graph.is_anchor_backed_concrete(&canonical, &cp.target, &cp.adapters)
&& !any_adapter_counts_concrete(&canonical, &counts)
{
continue;
}
if let Some(hit) = inspect_target(info, &counts, cp) {
out.push(hit);
}
}
}
for (anchor, info) in graph.target_anchor_capabilities(&cp.target, &cp.adapters) {
if let Some(hit) = inspect_anchor(anchor, info, &counts, cp) {
out.push(hit);
}
}
out
}
fn inspect_anchor(
anchor: &str,
info: &AnchorInfo,
counts: &AdapterTargetCounts,
cp: &CompiledCallParity,
) -> Option<MatchLocation> {
let per_adapter = collect_counts(anchor, counts, cp);
if per_adapter.len() != cp.adapters.len() {
return None;
}
if !counts_diverge(&per_adapter) {
return None;
}
let location = info.location.as_ref()?;
Some(MatchLocation {
file: location.file.clone(),
line: location.line,
column: location.column,
kind: ViolationKind::CallParityMultiplicityMismatch {
target_fn: anchor.to_string(),
target_layer: cp.target.clone(),
counts_per_adapter: per_adapter,
},
})
}
fn any_adapter_counts_concrete(concrete: &str, counts: &AdapterTargetCounts) -> bool {
counts.values().any(|m| m.contains_key(concrete))
}
type AdapterTargetCounts = HashMap<String, HashMap<String, usize>>;
fn build_per_adapter_target_counts(
pub_fns_by_layer: &HashMap<String, Vec<PubFnInfo<'_>>>,
touchpoints: &HandlerTouchpoints,
cp: &CompiledCallParity,
) -> AdapterTargetCounts {
let mut counts: AdapterTargetCounts = HashMap::new();
for adapter in &cp.adapters {
let per_target = count_for_adapter(pub_fns_by_layer.get(adapter), touchpoints);
counts.insert(adapter.clone(), per_target);
}
counts
}
fn count_for_adapter(
handlers: Option<&Vec<PubFnInfo<'_>>>,
touchpoints: &HandlerTouchpoints,
) -> HashMap<String, usize> {
let mut per_target: HashMap<String, usize> = HashMap::new();
let Some(handlers) = handlers else {
return per_target;
};
for info in handlers {
let canonical = canonical_name_for_pub_fn(info);
let Some(tps) = touchpoints.get(&canonical) else {
continue;
};
for tp in tps {
*per_target.entry(tp.clone()).or_insert(0) += 1;
}
}
per_target
}
fn inspect_target(
info: &PubFnInfo<'_>,
counts: &AdapterTargetCounts,
cp: &CompiledCallParity,
) -> Option<MatchLocation> {
let canonical = canonical_name_for_pub_fn(info);
let per_adapter = collect_counts(&canonical, counts, cp);
if per_adapter.len() != cp.adapters.len() {
return None;
}
if !counts_diverge(&per_adapter) {
return None;
}
Some(build_finding(info, canonical, per_adapter, &cp.target))
}
fn collect_counts(
target: &str,
counts: &AdapterTargetCounts,
cp: &CompiledCallParity,
) -> Vec<(String, usize)> {
let mut out: Vec<(String, usize)> = cp
.adapters
.iter()
.filter_map(|a| {
counts
.get(a)
.and_then(|m| m.get(target))
.map(|c| (a.clone(), *c))
})
.collect();
out.sort_by(|a, b| a.0.cmp(&b.0));
out
}
fn counts_diverge(per_adapter: &[(String, usize)]) -> bool {
per_adapter.windows(2).any(|w| w[0].1 != w[1].1)
}
fn build_finding(
info: &PubFnInfo<'_>,
canonical: String,
counts_per_adapter: Vec<(String, usize)>,
target_layer: &str,
) -> MatchLocation {
MatchLocation {
file: info.file.clone(),
line: info.line,
column: 0,
kind: ViolationKind::CallParityMultiplicityMismatch {
target_fn: canonical,
target_layer: target_layer.to_string(),
counts_per_adapter,
},
}
}