use std::path::PathBuf;
use syn::visit::Visit;
use super::{
attr_is, render_path, render_type, ItemTarget, MatchKind, Presentation, ScanReport, ScanVisitor,
};
pub fn synthesis_pass(
parsed_files: &[(PathBuf, syn::File)],
fingerprints: &[(String, antigen_fingerprint::Fingerprint)],
declaration_sites: &std::collections::HashSet<(String, PathBuf)>,
report: &mut ScanReport,
) {
for (file_path, parsed) in parsed_files {
for syn_item in &parsed.items {
let Some((kind_str, item_target)) = item_kind_and_target(syn_item) else {
continue;
};
let item_kind_for_dispatch = match syn_item {
syn::Item::Struct(_) => Some(antigen_fingerprint::ItemKind::Struct),
syn::Item::Enum(_) => Some(antigen_fingerprint::ItemKind::Enum),
syn::Item::Trait(_) => Some(antigen_fingerprint::ItemKind::Trait),
syn::Item::Fn(_) => Some(antigen_fingerprint::ItemKind::Fn),
syn::Item::Impl(_) => Some(antigen_fingerprint::ItemKind::Impl),
syn::Item::Type(_) => Some(antigen_fingerprint::ItemKind::Type),
syn::Item::Mod(_) => Some(antigen_fingerprint::ItemKind::Mod),
syn::Item::Const(_) => Some(antigen_fingerprint::ItemKind::Const),
syn::Item::Static(_) => Some(antigen_fingerprint::ItemKind::Static),
syn::Item::Union(_) => Some(antigen_fingerprint::ItemKind::Union),
_ => None,
};
for (antigen_type, fp) in fingerprints {
if let Some(required_kind) = fp.node_kind() {
if item_kind_for_dispatch != Some(required_kind) {
continue;
}
}
if !fp.matches(syn_item) {
continue;
}
let is_self_decl = matches!(&item_target, ItemTarget::Struct(s) if s == antigen_type)
&& declaration_sites.contains(&(antigen_type.clone(), file_path.clone()));
if is_self_decl {
continue;
}
let already_covered = report.presentations.iter().any(|p| {
p.match_kind == MatchKind::ExplicitMarker
&& p.antigen_type == *antigen_type
&& p.file == *file_path
&& p.item_target.addresses(&item_target)
}) || report.tolerances.iter().any(|t| {
t.antigen_type == *antigen_type
&& t.file == *file_path
&& t.item_target.addresses(&item_target)
});
if already_covered {
continue;
}
let duplicate_emitted = report.presentations.iter().any(|p| {
p.match_kind == MatchKind::FingerprintMatch
&& p.antigen_type == *antigen_type
&& p.file == *file_path
&& p.item_target == item_target
});
if duplicate_emitted {
continue;
}
let line = item_line(syn_item);
let structural_fingerprint = match syn_item {
syn::Item::Struct(i) => antigen_fingerprint::structural_digest(i),
syn::Item::Enum(i) => antigen_fingerprint::structural_digest(i),
syn::Item::Trait(i) => antigen_fingerprint::structural_digest(i),
syn::Item::Fn(i) => antigen_fingerprint::structural_digest(i),
syn::Item::Type(i) => antigen_fingerprint::structural_digest(i),
syn::Item::Impl(i) => antigen_fingerprint::structural_digest(i),
syn::Item::Const(i) => antigen_fingerprint::structural_digest(i),
syn::Item::Static(i) => antigen_fingerprint::structural_digest(i),
syn::Item::Union(i) => antigen_fingerprint::structural_digest(i),
_ => String::new(),
};
report.presentations.push(Presentation {
antigen_type: antigen_type.clone(),
file: file_path.clone(),
line,
item_kind: kind_str.to_string(),
item_target: item_target.clone(),
match_kind: MatchKind::FingerprintMatch,
canonical_path: None,
inherited_from: None,
structural_fingerprint,
requires_predicate: None,
proof: None,
});
}
}
}
}
struct MacroInvocation {
macro_name: String,
line: usize,
item_target: ItemTarget,
}
struct GeneratesInvocationVisitor<'a> {
generators: &'a std::collections::HashSet<String>,
found: Vec<MacroInvocation>,
}
impl GeneratesInvocationVisitor<'_> {
fn scan_attrs(&mut self, attrs: &[syn::Attribute], target: &ItemTarget) {
for attr in attrs {
if attr_is(attr, "derive") {
if let syn::Meta::List(list) = &attr.meta {
let parsed = list.parse_args_with(
syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated,
);
if let Ok(paths) = parsed {
for p in paths {
if let Some(seg) = p.segments.last() {
let name = seg.ident.to_string();
if self.generators.contains(&name) {
self.found.push(MacroInvocation {
macro_name: name,
line: ScanVisitor::line_of_attr(attr),
item_target: target.clone(),
});
}
}
}
}
}
continue;
}
if let Some(seg) = attr.path().segments.last() {
let name = seg.ident.to_string();
if self.generators.contains(&name) {
self.found.push(MacroInvocation {
macro_name: name,
line: ScanVisitor::line_of_attr(attr),
item_target: target.clone(),
});
}
}
}
}
}
impl<'ast> Visit<'ast> for GeneratesInvocationVisitor<'_> {
fn visit_item(&mut self, item: &'ast syn::Item) {
if let Some((_, target)) = item_kind_and_target(item) {
let attrs: &[syn::Attribute] = match item {
syn::Item::Struct(i) => &i.attrs,
syn::Item::Enum(i) => &i.attrs,
syn::Item::Union(i) => &i.attrs,
syn::Item::Fn(i) => &i.attrs,
syn::Item::Trait(i) => &i.attrs,
syn::Item::Type(i) => &i.attrs,
syn::Item::Const(i) => &i.attrs,
syn::Item::Static(i) => &i.attrs,
syn::Item::Impl(i) => &i.attrs,
syn::Item::Mod(i) => &i.attrs,
_ => &[],
};
self.scan_attrs(attrs, &target);
}
syn::visit::visit_item(self, item);
}
fn visit_macro(&mut self, mac: &'ast syn::Macro) {
if let Some(seg) = mac.path.segments.last() {
let name = seg.ident.to_string();
if self.generators.contains(&name) {
let line = seg.ident.span().start().line;
self.found.push(MacroInvocation {
macro_name: name,
line,
item_target: ItemTarget::Unknown { line },
});
}
}
syn::visit::visit_macro(self, mac);
}
}
pub fn generates_synthesis_pass(parsed_files: &[(PathBuf, syn::File)], report: &mut ScanReport) {
use std::collections::{HashMap, HashSet};
let mut by_macro: HashMap<String, Vec<String>> = HashMap::new();
for g in &report.generates_declarations {
by_macro
.entry(g.macro_name.clone())
.or_default()
.push(g.antigen_type.clone());
}
let generator_names: HashSet<String> = by_macro.keys().cloned().collect();
if generator_names.is_empty() {
return;
}
for (file_path, parsed) in parsed_files {
let mut visitor = GeneratesInvocationVisitor {
generators: &generator_names,
found: Vec::new(),
};
visitor.visit_file(parsed);
for inv in visitor.found {
let Some(antigen_types) = by_macro.get(&inv.macro_name) else {
continue;
};
let item_kind = format!("generated_{}", inv.macro_name);
for antigen_type in antigen_types {
let already = report.presentations.iter().any(|p| {
p.antigen_type == *antigen_type
&& p.file == *file_path
&& p.item_target == inv.item_target
});
if already {
continue;
}
report.presentations.push(Presentation {
antigen_type: antigen_type.clone(),
file: file_path.clone(),
line: inv.line,
item_kind: item_kind.clone(),
item_target: inv.item_target.clone(),
match_kind: MatchKind::ExplicitMarker,
canonical_path: None,
inherited_from: None,
structural_fingerprint: String::new(),
requires_predicate: None,
proof: None,
});
}
}
}
}
fn item_kind_and_target(item: &syn::Item) -> Option<(&'static str, ItemTarget)> {
match item {
syn::Item::Struct(s) => Some(("struct", ItemTarget::Struct(s.ident.to_string()))),
syn::Item::Enum(e) => Some(("enum", ItemTarget::Enum(e.ident.to_string()))),
syn::Item::Trait(t) => Some(("trait", ItemTarget::Trait(t.ident.to_string()))),
syn::Item::Fn(f) => Some(("fn", ItemTarget::Fn(f.sig.ident.to_string()))),
syn::Item::Type(t) => Some(("type", ItemTarget::TypeAlias(t.ident.to_string()))),
syn::Item::Impl(i) => {
let trait_path = i.trait_.as_ref().map(|(_, path, _)| render_path(path));
let target_type = render_type(&i.self_ty);
Some((
"impl",
ItemTarget::Impl {
trait_path,
target_type,
},
))
}
syn::Item::Const(c) => Some(("const", ItemTarget::Const(c.ident.to_string()))),
syn::Item::Static(s) => Some(("static", ItemTarget::Static(s.ident.to_string()))),
syn::Item::Union(u) => Some(("union", ItemTarget::Union(u.ident.to_string()))),
_ => None,
}
}
fn item_line(item: &syn::Item) -> usize {
use syn::spanned::Spanned;
item.span().start().line
}