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;
indices.iter().any(|&idx| &functions[idx].cfg != first_cfg)
}
fn merge_cfgs<'a>(cfgs: impl Iterator<Item = Option<&'a str>>) -> Option<String> {
let mut distinct: Vec<&str> = Vec::new();
for cfg in cfgs {
match cfg {
None => return None, Some(s) => {
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;