use std::path::PathBuf;
use super::{
AntigenDeclaration, ItemTarget, ParseFailure, Presentation, ProvenanceEntry, ScanReport,
generates_synthesis_pass, locus_matches, synthesis_pass,
};
pub fn finalize_report_with_catalog(
report: &mut ScanReport,
parsed_files: &[(PathBuf, syn::File)],
catalog_fingerprints: &[(String, antigen_fingerprint::Fingerprint)],
) {
let mut fp_parse_failures: Vec<ParseFailure> = Vec::new();
let mut fingerprints: Vec<(String, antigen_fingerprint::Fingerprint)> = report
.antigens
.iter()
.filter_map(|ag| {
let raw = ag.fingerprint.as_deref()?;
match antigen_fingerprint::Fingerprint::parse(raw) {
Ok(fp) => Some((ag.type_name.clone(), fp)),
Err(e) => {
fp_parse_failures.push(ParseFailure {
file: ag.file.clone(),
error: format!(
"antigen `{}`: fingerprint failed to re-parse during synthesis: {e}",
ag.type_name
),
});
None
},
}
})
.collect();
report.parse_failures.extend(fp_parse_failures);
fingerprints.extend(catalog_fingerprints.iter().cloned());
if !fingerprints.is_empty() {
let declaration_sites: std::collections::HashSet<(String, PathBuf)> = report
.antigens
.iter()
.map(|ag| (ag.type_name.clone(), ag.file.clone()))
.collect();
synthesis_pass(parsed_files, &fingerprints, &declaration_sites, report);
}
if !report.generates_declarations.is_empty() {
generates_synthesis_pass(parsed_files, report);
}
synthesize_inherited_presentations(report);
}
pub fn synthesize_inherited_presentations(report: &mut ScanReport) {
use std::collections::HashMap;
let antigen_by_key: HashMap<AntigenKey, AntigenDeclaration> = report
.antigens
.iter()
.map(|a| ((a.type_name.clone(), a.canonical_path.clone()), a.clone()))
.collect();
let mut adjacency: LineageAdjacency = LineageAdjacency::new();
for e in &report.lineage_edges {
let child_key = (e.child.clone(), e.child_canonical_path.clone());
let parent_key = (e.parent.clone(), e.parent_canonical_path.clone());
if !antigen_by_key.contains_key(&child_key) || !antigen_by_key.contains_key(&parent_key) {
continue;
}
adjacency.entry(child_key).or_default().push(parent_key);
}
let presentations_snapshot: Vec<Presentation> = report.presentations.clone();
let mut presentations_by_antigen: HashMap<AntigenKey, Vec<usize>> = HashMap::new();
for (idx, p) in presentations_snapshot.iter().enumerate() {
presentations_by_antigen
.entry((p.antigen_type.clone(), p.canonical_path.clone()))
.or_default()
.push(idx);
}
for child_decl in report.antigens.clone() {
let child_key = (
child_decl.type_name.clone(),
child_decl.canonical_path.clone(),
);
if !adjacency.contains_key(&child_key) {
continue;
}
let ancestors_in_order = transitive_ancestors_dfs(&adjacency, &child_key);
propagate_ancestors_to_descendant(
report,
&child_decl,
&ancestors_in_order,
&presentations_snapshot,
&presentations_by_antigen,
);
}
}
type AntigenKey = (String, Option<String>);
type LineageAdjacency = std::collections::HashMap<AntigenKey, Vec<AntigenKey>>;
fn transitive_ancestors_dfs(
adjacency: &LineageAdjacency,
child_key: &AntigenKey,
) -> Vec<AntigenKey> {
use std::collections::HashSet;
let mut visited: HashSet<AntigenKey> = HashSet::new();
let mut stack: Vec<AntigenKey> = adjacency.get(child_key).cloned().unwrap_or_default();
let mut ancestors_in_order: Vec<AntigenKey> = Vec::new();
while let Some(node) = stack.pop() {
if !visited.insert(node.clone()) {
continue;
}
ancestors_in_order.push(node.clone());
if let Some(parents) = adjacency.get(&node) {
for parent in parents.iter().rev() {
if !visited.contains(parent) {
stack.push(parent.clone());
}
}
}
}
ancestors_in_order
}
fn propagate_ancestors_to_descendant(
report: &mut ScanReport,
child_decl: &AntigenDeclaration,
ancestors_in_order: &[AntigenKey],
presentations_snapshot: &[Presentation],
presentations_by_antigen: &std::collections::HashMap<AntigenKey, Vec<usize>>,
) {
use std::collections::BTreeSet;
let descendant_item_target = ItemTarget::Struct(child_decl.type_name.clone());
let descendant_item_kind = "struct".to_string();
for ancestor_key in ancestors_in_order {
let provenance = ProvenanceEntry {
antigen_type: ancestor_key.0.clone(),
canonical_path: ancestor_key.1.clone(),
};
let Some(ancestor_pres_indices) = presentations_by_antigen.get(ancestor_key) else {
continue;
};
for &ancestor_pres_idx in ancestor_pres_indices {
let ancestor_pres = &presentations_snapshot[ancestor_pres_idx];
let existing_idx = report.presentations.iter().position(|p| {
p.antigen_type == ancestor_pres.antigen_type
&& p.canonical_path == ancestor_pres.canonical_path
&& p.item_target.addresses(&descendant_item_target)
&& locus_matches(
p.file.as_path(),
p.canonical_path.as_deref(),
child_decl.file.as_path(),
child_decl.canonical_path.as_deref(),
)
});
if let Some(idx) = existing_idx {
let existing = &mut report.presentations[idx];
let mut chain: BTreeSet<ProvenanceEntry> = existing
.inherited_from
.take()
.unwrap_or_default()
.into_iter()
.collect();
chain.insert(provenance.clone());
existing.inherited_from = Some(chain.into_iter().collect());
} else {
report.presentations.push(Presentation {
antigen_type: ancestor_pres.antigen_type.clone(),
file: child_decl.file.clone(),
line: child_decl.line,
item_kind: descendant_item_kind.clone(),
item_target: descendant_item_target.clone(),
match_kind: ancestor_pres.match_kind.clone(),
canonical_path: ancestor_pres.canonical_path.clone(),
inherited_from: Some(vec![provenance.clone()]),
structural_fingerprint: ancestor_pres.structural_fingerprint.clone(),
requires_predicate: ancestor_pres.requires_predicate.clone(),
proof: ancestor_pres.proof.clone(),
});
}
}
}
}