use crate::core::ir::FunctionDef;
use ahash::{AHashMap, AHashSet};
pub fn dedup_same_name_functions(functions: &[FunctionDef]) -> Vec<FunctionDef> {
let groups = collect_function_groups(functions);
let groups_to_merge = groups_to_merge(&groups, functions);
if groups_to_merge.is_empty() {
return functions.to_vec();
}
let mut canonical_by_first_index: AHashMap<usize, FunctionDef> = AHashMap::new();
let mut skipped_indices: AHashSet<usize> = AHashSet::new();
for indices in &groups_to_merge {
let merged_cfg = merge_cfgs(indices.iter().map(|&i| functions[i].cfg.as_deref()));
let canonical_idx = pick_canonical_entry(indices, functions);
let mut canonical = functions[canonical_idx].clone();
canonical.cfg = merged_cfg;
let first_idx = *indices.iter().min().expect("merge group indices are non-empty");
canonical_by_first_index.insert(first_idx, canonical);
for &idx in indices {
if idx != first_idx {
skipped_indices.insert(idx);
}
}
}
let mut merged_functions = Vec::with_capacity(functions.len() - skipped_indices.len());
for (idx, function) in functions.iter().cloned().enumerate() {
if let Some(canonical) = canonical_by_first_index.remove(&idx) {
merged_functions.push(canonical);
} else if !skipped_indices.contains(&idx) {
merged_functions.push(function);
}
}
merged_functions
}
fn collect_function_groups(functions: &[FunctionDef]) -> AHashMap<String, Vec<usize>> {
let mut name_to_indices: AHashMap<String, Vec<usize>> = AHashMap::new();
for (idx, func) in functions.iter().enumerate() {
name_to_indices.entry(func.name.clone()).or_default().push(idx);
}
name_to_indices
}
fn groups_to_merge(groups: &AHashMap<String, Vec<usize>>, functions: &[FunctionDef]) -> Vec<Vec<usize>> {
groups
.values()
.filter(|indices| should_merge_cfg_group(indices, functions))
.cloned()
.collect()
}
fn should_merge_cfg_group(indices: &[usize], functions: &[FunctionDef]) -> bool {
if indices.len() <= 1 {
return false;
}
let first_cfg = &functions[indices[0]].cfg;
if !indices.iter().any(|&idx| &functions[idx].cfg != first_cfg) {
return false;
}
if real_variant_signatures_agree(indices, functions) {
return true;
}
warn_on_signature_disagreement(indices, functions);
false
}
fn real_variant_signatures_agree(indices: &[usize], functions: &[FunctionDef]) -> bool {
let mut real_signatures = indices.iter().filter_map(|&idx| {
let func = &functions[idx];
let all_underscore = !func.params.is_empty() && func.params.iter().all(|p| p.name.starts_with('_'));
(!all_underscore).then_some(func)
});
let Some(first) = real_signatures.next() else {
return true;
};
real_signatures.all(|func| signatures_match(first, func))
}
fn signatures_match(a: &FunctionDef, b: &FunctionDef) -> bool {
a.return_type == b.return_type
&& a.params.len() == b.params.len()
&& a.params.iter().zip(&b.params).all(|(p, q)| p.ty == q.ty)
}
fn warn_on_signature_disagreement(indices: &[usize], functions: &[FunctionDef]) {
let name = &functions[indices[0]].name;
let variants: Vec<String> = indices
.iter()
.map(|&idx| {
let func = &functions[idx];
format!(
"{rust_path} cfg={cfg:?} params={params:?} returns={returns:?}",
rust_path = func.rust_path,
cfg = func.cfg,
params = func.params.iter().map(|p| &p.ty).collect::<Vec<_>>(),
returns = func.return_type,
)
})
.collect();
tracing::warn!(
function = %name,
variants = ?variants,
"same-name functions under disjoint cfg gates disagree on signature; skipping cfg dedup \
for this group so every variant reaches the emitter unmerged rather than silently \
picking one signature as canonical for cfg branches it does not describe"
);
}
pub fn merge_cfgs<'a>(cfgs: impl Iterator<Item = Option<&'a str>>) -> Option<String> {
let mut distinct: Vec<&str> = Vec::new();
for cfg in cfgs {
{
let s = cfg?;
if !distinct.contains(&s) {
distinct.push(s);
}
}
}
match distinct.len() {
0 => None,
1 => Some(distinct[0].to_string()),
_ => Some(format!("any({})", distinct.join(", "))),
}
}
fn pick_canonical_entry(indices: &[usize], functions: &[FunctionDef]) -> usize {
for &idx in indices {
let func = &functions[idx];
let all_underscore = !func.params.is_empty() && func.params.iter().all(|p| p.name.starts_with('_'));
if !all_underscore {
return idx;
}
}
indices[0]
}
#[cfg(test)]
mod tests;