use crate::core::ir::{ApiSurface, FunctionDef, TypeRef};
use ahash::{AHashMap, AHashSet};
fn is_simple_type(ty: &TypeRef) -> bool {
matches!(
ty,
TypeRef::Primitive(_)
| TypeRef::String
| TypeRef::Bytes
| TypeRef::Path
| TypeRef::Unit
| TypeRef::Duration
| TypeRef::Json
)
}
pub(super) fn resolve_newtypes(surface: &mut ApiSurface) {
let newtype_map: AHashMap<String, TypeRef> = surface
.types
.iter()
.filter(|t| t.fields.len() == 1 && t.fields[0].name == "_0" && is_simple_type(&t.fields[0].ty))
.map(|t| (t.name.clone(), t.fields[0].ty.clone()))
.collect();
if newtype_map.is_empty() {
return;
}
let newtype_rust_paths: AHashMap<String, String> = surface
.types
.iter()
.filter(|t| newtype_map.contains_key(&t.name))
.map(|t| (t.name.clone(), t.rust_path.replace('-', "_")))
.collect();
surface.types.retain(|t| !newtype_map.contains_key(&t.name));
for typ in &mut surface.types {
for field in &mut typ.fields {
if let TypeRef::Named(name) = &field.ty {
if let Some(rust_path) = newtype_rust_paths.get(name.as_str()) {
field.newtype_wrapper = Some(rust_path.clone());
}
}
if let TypeRef::Optional(inner) = &field.ty {
if let TypeRef::Named(name) = inner.as_ref() {
if let Some(rust_path) = newtype_rust_paths.get(name.as_str()) {
field.newtype_wrapper = Some(rust_path.clone());
}
}
}
if let TypeRef::Vec(inner) = &field.ty {
if let TypeRef::Named(name) = inner.as_ref() {
if let Some(rust_path) = newtype_rust_paths.get(name.as_str()) {
field.newtype_wrapper = Some(rust_path.clone());
}
}
}
resolve_typeref(&newtype_map, &mut field.ty);
}
for method in &mut typ.methods {
for param in &mut method.params {
if let TypeRef::Named(name) = ¶m.ty {
if let Some(rust_path) = newtype_rust_paths.get(name.as_str()) {
param.newtype_wrapper = Some(rust_path.clone());
}
}
resolve_typeref(&newtype_map, &mut param.ty);
}
if let TypeRef::Named(name) = &method.return_type {
if let Some(rust_path) = newtype_rust_paths.get(name.as_str()) {
method.return_newtype_wrapper = Some(rust_path.clone());
}
}
resolve_typeref(&newtype_map, &mut method.return_type);
}
}
for func in &mut surface.functions {
for param in &mut func.params {
if let TypeRef::Named(name) = ¶m.ty {
if let Some(rust_path) = newtype_rust_paths.get(name.as_str()) {
param.newtype_wrapper = Some(rust_path.clone());
}
}
resolve_typeref(&newtype_map, &mut param.ty);
}
if let TypeRef::Named(name) = &func.return_type {
if let Some(rust_path) = newtype_rust_paths.get(name.as_str()) {
func.return_newtype_wrapper = Some(rust_path.clone());
}
}
resolve_typeref(&newtype_map, &mut func.return_type);
}
for enum_def in &mut surface.enums {
for variant in &mut enum_def.variants {
for field in &mut variant.fields {
resolve_typeref(&newtype_map, &mut field.ty);
}
}
}
}
fn resolve_typeref(newtype_map: &AHashMap<String, TypeRef>, ty: &mut TypeRef) {
match ty {
TypeRef::Named(name) => {
if let Some(inner) = newtype_map.get(name.as_str()) {
*ty = inner.clone();
}
}
TypeRef::Optional(inner) => resolve_typeref(newtype_map, inner),
TypeRef::Vec(inner) => resolve_typeref(newtype_map, inner),
TypeRef::Map(k, v) => {
resolve_typeref(newtype_map, k);
resolve_typeref(newtype_map, v);
}
_ => {}
}
}
pub(super) fn resolve_trait_sources(surface: &mut ApiSurface) {
let mut trait_method_map: AHashMap<String, Vec<(String, String)>> = AHashMap::new();
let mut trait_methods_set: AHashMap<String, Vec<String>> = AHashMap::new();
for typ in &surface.types {
if !typ.is_trait {
continue;
}
let method_names: Vec<String> = typ.methods.iter().map(|m| m.name.clone()).collect();
trait_methods_set.insert(typ.name.clone(), method_names.clone());
for method_name in &method_names {
trait_method_map
.entry(method_name.clone())
.or_default()
.push((typ.name.clone(), typ.rust_path.replace('-', "_")));
}
}
if trait_method_map.is_empty() {
return;
}
for typ in &mut surface.types {
if typ.is_trait {
continue;
}
let unresolved_names: Vec<String> = typ
.methods
.iter()
.filter(|m| m.trait_source.is_none())
.map(|m| m.name.clone())
.collect();
for method in &mut typ.methods {
if method.trait_source.is_some() {
continue;
}
let Some(candidates) = trait_method_map.get(&method.name) else {
continue;
};
if candidates.len() == 1 {
method.trait_source = Some(candidates[0].1.clone());
} else {
let best = candidates.iter().max_by_key(|(trait_name, _)| {
trait_methods_set
.get(trait_name)
.map(|trait_methods| {
trait_methods
.iter()
.filter(|method_name| unresolved_names.contains(method_name))
.count()
})
.unwrap_or(0)
});
if let Some((_, rust_path)) = best {
method.trait_source = Some(rust_path.clone());
}
}
}
}
}
pub(crate) fn merge_same_named_function_cfgs(surface: &mut ApiSurface) {
let groups = collect_function_groups(&surface.functions);
let groups_to_merge = groups_to_merge(&groups, &surface.functions);
if groups_to_merge.is_empty() {
return;
}
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| surface.functions[i].cfg.as_deref()));
let canonical_idx = pick_canonical_entry(indices, &surface.functions);
let mut canonical = surface.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(surface.functions.len() - skipped_indices.len());
for (idx, function) in surface.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);
}
}
surface.functions = 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]
}