use std::collections::{BTreeMap, HashMap, HashSet};
use guppy::graph::{BuildTargetId, PackageGraph};
use guppy::{PackageId, Version};
use rustdoc_ir::Type;
use super::type_collection::{CTypeDefinition, CTypeKind, c_type_name};
use crate::analysis::extern_items::{ExternItems, FreeFunctionItem};
use crate::cli::generate::PackageTypeOverrides;
use crate::static_item::StaticItem;
pub struct PartitionedTypes {
pub per_crate: BTreeMap<PackageId, Vec<CTypeDefinition>>,
pub opaque_types: Vec<CTypeDefinition>,
}
pub struct HeaderDeps {
pub includes: Vec<String>,
pub forward_decls: Vec<CTypeDefinition>,
pub type_hints: Vec<CTypeDefinition>,
}
pub fn partition_types(
type_defs: Vec<CTypeDefinition>,
target_extern_items: &[(PackageId, ExternItems)],
overrides: &PackageTypeOverrides,
) -> PartitionedTypes {
let mut non_generics: Vec<CTypeDefinition> = Vec::new();
let mut generics: Vec<CTypeDefinition> = Vec::new();
let mut opaque_types: Vec<CTypeDefinition> = Vec::new();
for def in type_defs {
if overrides.skipped.contains(&def.defining_package) {
continue;
}
if overrides.opaque.contains(&def.defining_package) {
opaque_types.push(def);
continue;
}
if def.is_generic_instantiation {
generics.push(def);
} else {
non_generics.push(def);
}
}
let mut per_crate: BTreeMap<PackageId, Vec<CTypeDefinition>> = BTreeMap::new();
for (pkg_id, _) in target_extern_items {
per_crate.entry(pkg_id.clone()).or_default();
}
for def in non_generics {
per_crate
.entry(def.defining_package.clone())
.or_default()
.push(def);
}
let generic_by_name: HashMap<&str, Vec<usize>> = {
let mut map: HashMap<&str, Vec<usize>> = HashMap::new();
for (idx, def) in generics.iter().enumerate() {
map.entry(def.name.as_str()).or_default().push(idx);
}
map
};
let mut generic_assignments: HashMap<usize, HashSet<PackageId>> = HashMap::new();
for (pkg_id, defs) in &per_crate {
for def in defs {
let referenced = collect_by_value_type_names(def);
for name in &referenced {
if let Some(indices) = generic_by_name.get(name.as_str()) {
for &idx in indices {
generic_assignments
.entry(idx)
.or_default()
.insert(pkg_id.clone());
}
}
}
}
}
for (pkg_id, extern_items) in target_extern_items {
let fn_type_names = collect_fn_type_names(&extern_items.fns, &extern_items.statics);
for name in &fn_type_names {
if let Some(indices) = generic_by_name.get(name.as_str()) {
for &idx in indices {
generic_assignments
.entry(idx)
.or_default()
.insert(pkg_id.clone());
}
}
}
}
loop {
let mut new_assignments = false;
let snapshot: Vec<(usize, HashSet<PackageId>)> = generic_assignments
.iter()
.map(|(&idx, pkgs)| (idx, pkgs.clone()))
.collect();
for (idx, pkgs) in &snapshot {
let def = &generics[*idx];
let referenced = collect_by_value_type_names(def);
for name in &referenced {
if let Some(target_indices) = generic_by_name.get(name.as_str()) {
for &target_idx in target_indices {
let entry = generic_assignments.entry(target_idx).or_default();
for pkg in pkgs {
if entry.insert(pkg.clone()) {
new_assignments = true;
}
}
}
}
}
}
if !new_assignments {
break;
}
}
for (pkg_id, extern_items) in target_extern_items {
for func in &extern_items.fns {
for input in &func.function.header.inputs {
let mut ptr_names = Vec::new();
collect_pointer_names_from_type(&input.type_, &mut ptr_names);
for name in &ptr_names {
if let Some(indices) = generic_by_name.get(name.as_str()) {
for &idx in indices {
generic_assignments
.entry(idx)
.or_default()
.insert(pkg_id.clone());
}
}
}
}
if let Some(ref output) = func.function.header.output {
let mut ptr_names = Vec::new();
collect_pointer_names_from_type(output, &mut ptr_names);
for name in &ptr_names {
if let Some(indices) = generic_by_name.get(name.as_str()) {
for &idx in indices {
generic_assignments
.entry(idx)
.or_default()
.insert(pkg_id.clone());
}
}
}
}
}
}
for (pkg_id, defs) in &per_crate {
for def in defs {
let ptr_names = collect_pointer_type_names(def);
for name in &ptr_names {
if let Some(indices) = generic_by_name.get(name.as_str()) {
for &idx in indices {
generic_assignments
.entry(idx)
.or_default()
.insert(pkg_id.clone());
}
}
}
}
}
let by_value_generics: HashSet<usize> = {
let mut by_val: HashSet<usize> = HashSet::new();
for defs in per_crate.values() {
for def in defs {
let names = collect_by_value_type_names(def);
for name in &names {
if let Some(indices) = generic_by_name.get(name.as_str()) {
by_val.extend(indices);
}
}
}
}
for (_, extern_items) in target_extern_items {
let names = collect_fn_type_names(&extern_items.fns, &extern_items.statics);
for name in &names {
if let Some(indices) = generic_by_name.get(name.as_str()) {
by_val.extend(indices);
}
}
}
loop {
let mut added = false;
let snapshot: Vec<usize> = by_val.iter().copied().collect();
for idx in snapshot {
let names = collect_by_value_type_names(&generics[idx]);
for name in &names {
if let Some(indices) = generic_by_name.get(name.as_str()) {
for &i in indices {
if by_val.insert(i) {
added = true;
}
}
}
}
}
if !added {
break;
}
}
by_val
};
for (idx, def) in generics.into_iter().enumerate() {
if let Some(pkgs) = generic_assignments.remove(&idx) {
let is_by_value = by_value_generics.contains(&idx);
let pkgs_vec: Vec<PackageId> = pkgs.into_iter().collect();
for (i, pkg) in pkgs_vec.iter().enumerate() {
let kind = if is_by_value {
def.kind.clone()
} else {
forward_decl_kind(&def.kind)
};
if i + 1 < pkgs_vec.len() {
per_crate
.entry(pkg.clone())
.or_default()
.push(CTypeDefinition {
name: def.name.clone(),
original_name: def.original_name.clone(),
kind,
rustdoc_id: def.rustdoc_id.clone(),
defining_package: def.defining_package.clone(),
is_generic_instantiation: is_by_value,
usize_is_size_t: def.usize_is_size_t,
});
} else {
per_crate
.entry(pkg.clone())
.or_default()
.push(CTypeDefinition {
name: def.name.clone(),
original_name: def.original_name.clone(),
kind,
rustdoc_id: def.rustdoc_id.clone(),
defining_package: def.defining_package.clone(),
is_generic_instantiation: is_by_value,
usize_is_size_t: def.usize_is_size_t,
});
break;
}
}
}
}
let target_ids: HashSet<&PackageId> = target_extern_items.iter().map(|(id, _)| id).collect();
let opaque_only_crates: Vec<PackageId> = per_crate
.iter()
.filter(|(id, defs)| {
!target_ids.contains(id)
&& defs.iter().all(|d| {
matches!(
d.kind,
CTypeKind::OpaqueStruct | CTypeKind::OpaqueUnion | CTypeKind::Typedef(_)
)
})
})
.map(|(id, _)| id.clone())
.collect();
for id in opaque_only_crates {
if let Some(defs) = per_crate.remove(&id) {
opaque_types.extend(defs);
}
}
PartitionedTypes {
per_crate,
opaque_types,
}
}
pub fn compute_header_deps(
partitioned: &PartitionedTypes,
target_extern_items: &[(PackageId, ExternItems)],
overrides: &PackageTypeOverrides,
filenames: &HeaderFilenames,
lang_extension: &str,
) -> HashMap<PackageId, HeaderDeps> {
let packages_with_headers: HashSet<&PackageId> = partitioned.per_crate.keys().collect();
let mut type_to_package: HashMap<&str, &PackageId> = HashMap::new();
for (pkg_id, defs) in &partitioned.per_crate {
for def in defs {
if !def.is_generic_instantiation {
type_to_package.insert(&def.name, pkg_id);
if let Some(ref orig) = def.original_name {
type_to_package.insert(orig, pkg_id);
}
}
}
}
let mut result: HashMap<PackageId, HeaderDeps> = HashMap::new();
for (pkg_id, defs) in &partitioned.per_crate {
let mut by_value_from: HashSet<&PackageId> = HashSet::new();
let mut pointer_only_from: HashSet<&PackageId> = HashSet::new();
for def in defs {
let by_value_names = collect_by_value_type_names(def);
for name in &by_value_names {
if let Some(&dep_pkg) = type_to_package.get(name.as_str())
&& dep_pkg != pkg_id
{
by_value_from.insert(dep_pkg);
}
}
let ptr_names = collect_pointer_type_names(def);
for name in &ptr_names {
if let Some(&dep_pkg) = type_to_package.get(name.as_str())
&& dep_pkg != pkg_id
{
pointer_only_from.insert(dep_pkg);
}
}
}
if let Some((_, extern_items)) = target_extern_items.iter().find(|(id, _)| id == pkg_id) {
let fn_type_names = collect_fn_type_names(&extern_items.fns, &extern_items.statics);
for name in &fn_type_names {
if let Some(&dep_pkg) = type_to_package.get(name.as_str())
&& dep_pkg != pkg_id
{
by_value_from.insert(dep_pkg);
}
}
for func in &extern_items.fns {
let mut ptr_names = Vec::new();
for input in &func.function.header.inputs {
collect_pointer_names_from_type(&input.type_, &mut ptr_names);
}
if let Some(ref output) = func.function.header.output {
collect_pointer_names_from_type(output, &mut ptr_names);
}
for name in &ptr_names {
if let Some(&dep_pkg) = type_to_package.get(name.as_str())
&& dep_pkg != pkg_id
{
pointer_only_from.insert(dep_pkg);
}
}
}
}
pointer_only_from.retain(|p| !by_value_from.contains(p));
let mut includes: Vec<String> = by_value_from
.iter()
.filter(|p| packages_with_headers.contains(*p) && !overrides.opaque.contains(*p))
.map(|p| filenames.filename(p, lang_extension))
.collect();
includes.sort();
let mut forward_decls: Vec<CTypeDefinition> = Vec::new();
let mut all_referenced: HashSet<String> = defs
.iter()
.flat_map(collect_all_referenced_type_names)
.collect();
if let Some((_, extern_items)) = target_extern_items.iter().find(|(id, _)| id == pkg_id) {
all_referenced.extend(collect_fn_type_names(
&extern_items.fns,
&extern_items.statics,
));
for func in &extern_items.fns {
for input in &func.function.header.inputs {
let mut ptr_names = Vec::new();
collect_pointer_names_from_type(&input.type_, &mut ptr_names);
all_referenced.extend(ptr_names);
}
if let Some(ref output) = func.function.header.output {
let mut ptr_names = Vec::new();
collect_pointer_names_from_type(output, &mut ptr_names);
all_referenced.extend(ptr_names);
}
}
}
for opaque_def in &partitioned.opaque_types {
let matches = all_referenced.contains(&opaque_def.name)
|| opaque_def
.original_name
.as_ref()
.is_some_and(|orig| all_referenced.contains(orig));
if matches {
forward_decls.push(opaque_def.clone());
}
}
for dep_pkg in &pointer_only_from {
let mut ptr_names: HashSet<String> = HashSet::new();
for def in defs {
for n in collect_pointer_type_names(def) {
if type_to_package.get(n.as_str()) == Some(dep_pkg) {
ptr_names.insert(n);
}
}
}
if let Some((_, extern_items)) = target_extern_items.iter().find(|(id, _)| id == pkg_id)
{
for func in &extern_items.fns {
let mut names = Vec::new();
for input in &func.function.header.inputs {
collect_pointer_names_from_type(&input.type_, &mut names);
}
if let Some(ref output) = func.function.header.output {
collect_pointer_names_from_type(output, &mut names);
}
for n in names {
if type_to_package.get(n.as_str()) == Some(dep_pkg) {
ptr_names.insert(n);
}
}
}
}
let Some(dep_defs) = partitioned.per_crate.get(*dep_pkg) else {
continue;
};
for def in dep_defs {
let matches = ptr_names.contains(&def.name)
|| def
.original_name
.as_ref()
.is_some_and(|orig| ptr_names.contains(orig));
if matches {
forward_decls.push(CTypeDefinition {
kind: forward_decl_kind(&def.kind),
..def.clone()
});
}
}
}
let mut seen_names: HashSet<String> = HashSet::new();
forward_decls.retain(|d| seen_names.insert(d.name.clone()));
let mut type_hints: Vec<CTypeDefinition> = Vec::new();
for dep_pkg in by_value_from.iter().chain(pointer_only_from.iter()) {
if let Some(dep_defs) = partitioned.per_crate.get(*dep_pkg) {
type_hints.extend(dep_defs.iter().cloned());
}
}
result.insert(
pkg_id.clone(),
HeaderDeps {
includes,
forward_decls,
type_hints,
},
);
}
result
}
pub struct HeaderFilenames {
names: HashMap<PackageId, String>,
}
pub fn default_header_base_name(graph: &PackageGraph, pkg_id: &PackageId) -> Option<String> {
let meta = graph.metadata(pkg_id).ok()?;
let name = meta
.build_targets()
.find(|t| matches!(t.id(), BuildTargetId::Library))
.map(|t| t.name().to_owned())
.unwrap_or_else(|| meta.name().replace('-', "_"));
Some(name)
}
impl HeaderFilenames {
pub fn new(
package_ids: &[&PackageId],
graph: &PackageGraph,
renames: &HashMap<PackageId, String>,
) -> Result<Self, String> {
let mut renamed: HashMap<PackageId, String> = HashMap::new();
let mut defaults: Vec<(&PackageId, String, Version)> = Vec::new();
for &id in package_ids {
if let Some(rename) = renames.get(id) {
renamed.insert(id.clone(), rename.clone());
continue;
}
let Some(meta) = graph.metadata(id).ok() else {
continue;
};
let lib_name = meta
.build_targets()
.find(|t| matches!(t.id(), BuildTargetId::Library))
.map(|t| t.name().to_owned())
.unwrap_or_else(|| meta.name().replace('-', "_"));
defaults.push((id, lib_name, meta.version().clone()));
}
let mut name_counts: HashMap<&str, Vec<usize>> = HashMap::new();
for (i, (_, name, _)) in defaults.iter().enumerate() {
name_counts.entry(name.as_str()).or_default().push(i);
}
let mut names = HashMap::new();
for indices in name_counts.values() {
if indices.len() == 1 {
let i = indices[0];
let (id, ref name, _) = defaults[i];
names.insert(id.clone(), name.clone());
} else {
let entries: Vec<_> = indices
.iter()
.map(|&i| {
let (id, ref name, ref version) = defaults[i];
(id, name.clone(), version.clone())
})
.collect();
let major_unique = {
let majors: HashSet<_> = entries.iter().map(|(_, _, v)| v.major).collect();
majors.len() == entries.len()
};
for (id, name, version) in &entries {
let suffix = if major_unique {
format!("_{}", version.major)
} else {
let minor_unique = {
let minor_keys: HashSet<_> =
entries.iter().map(|(_, _, v)| (v.major, v.minor)).collect();
minor_keys.len() == entries.len()
};
if minor_unique {
format!("_{}_{}", version.major, version.minor)
} else {
format!("_{}_{}_{}", version.major, version.minor, version.patch)
}
};
names.insert((*id).clone(), format!("{name}{suffix}"));
}
}
}
for (id, rename) in renamed {
if let Some((other_id, _)) = names.iter().find(|(_, n)| **n == rename) {
let (renamed_label, other_label) = (
graph
.metadata(&id)
.map(|m| format!("{}@{}", m.name(), m.version()))
.unwrap_or_else(|_| id.repr().to_owned()),
graph
.metadata(other_id)
.map(|m| format!("{}@{}", m.name(), m.version()))
.unwrap_or_else(|_| other_id.repr().to_owned()),
);
return Err(format!(
"`header_name = \"{rename}\"` on `{renamed_label}` collides with \
the default header name for `{other_label}`; rename one of them"
));
}
names.insert(id, rename);
}
Ok(Self { names })
}
pub fn filename(&self, id: &PackageId, lang_extension: &str) -> String {
let base = self
.names
.get(id)
.expect("package not in HeaderFilenames map");
format!("{base}.{lang_extension}")
}
pub fn base_name(&self, id: &PackageId) -> &str {
self.names
.get(id)
.expect("package not in HeaderFilenames map")
}
}
fn forward_decl_kind(kind: &CTypeKind) -> CTypeKind {
match kind {
CTypeKind::Union(_) | CTypeKind::OpaqueUnion => CTypeKind::OpaqueUnion,
CTypeKind::Struct(_) | CTypeKind::OpaqueStruct => CTypeKind::OpaqueStruct,
CTypeKind::TaggedUnion(t) if t.repr.is_repr_c() => CTypeKind::OpaqueStruct,
CTypeKind::TaggedUnion(_) => CTypeKind::OpaqueUnion,
other => other.clone(),
}
}
fn collect_by_value_type_names(def: &CTypeDefinition) -> Vec<String> {
let mut names = Vec::new();
match &def.kind {
CTypeKind::Struct(s) => {
for field in &s.fields {
collect_by_value_names_from_type(&field.type_, &mut names);
}
}
CTypeKind::Union(u) => {
for field in &u.fields {
collect_by_value_names_from_type(&field.type_, &mut names);
}
}
CTypeKind::TaggedUnion(t) => {
for variant in &t.variants {
if let Some(ref body) = variant.body {
for field in &body.fields {
collect_by_value_names_from_type(&field.type_, &mut names);
}
}
}
}
CTypeKind::Typedef(td) => {
collect_by_value_names_from_type(&td.inner, &mut names);
}
CTypeKind::FieldlessEnum(_) | CTypeKind::OpaqueStruct | CTypeKind::OpaqueUnion => {}
}
names
}
fn collect_by_value_names_from_type(ty: &Type, names: &mut Vec<String>) {
match ty {
Type::Path(_) | Type::TypeAlias(_) => {
names.push(c_type_name(ty));
}
Type::Array(a) => collect_by_value_names_from_type(&a.element_type, names),
Type::Tuple(t) => {
for elem in &t.elements {
collect_by_value_names_from_type(elem, names);
}
}
_ => {}
}
}
fn collect_pointer_type_names(def: &CTypeDefinition) -> Vec<String> {
let mut names = Vec::new();
match &def.kind {
CTypeKind::Struct(s) => {
for field in &s.fields {
collect_pointer_names_from_type(&field.type_, &mut names);
}
}
CTypeKind::Union(u) => {
for field in &u.fields {
collect_pointer_names_from_type(&field.type_, &mut names);
}
}
CTypeKind::TaggedUnion(t) => {
for variant in &t.variants {
if let Some(ref body) = variant.body {
for field in &body.fields {
collect_pointer_names_from_type(&field.type_, &mut names);
}
}
}
}
CTypeKind::Typedef(td) => {
collect_pointer_names_from_type(&td.inner, &mut names);
}
CTypeKind::FieldlessEnum(_) | CTypeKind::OpaqueStruct | CTypeKind::OpaqueUnion => {}
}
names
}
fn collect_pointer_names_from_type(ty: &Type, names: &mut Vec<String>) {
match ty {
Type::RawPointer(p) => {
collect_pointed_names(&p.inner, names);
}
Type::Reference(r) => {
collect_pointed_names(&r.inner, names);
}
Type::Array(a) => collect_pointer_names_from_type(&a.element_type, names),
_ => {}
}
}
fn collect_pointed_names(ty: &Type, names: &mut Vec<String>) {
match ty {
Type::Path(_) | Type::TypeAlias(_) => {
names.push(c_type_name(ty));
}
Type::Array(a) => collect_pointed_names(&a.element_type, names),
Type::RawPointer(p) => collect_pointed_names(&p.inner, names),
Type::Reference(r) => collect_pointed_names(&r.inner, names),
_ => {}
}
}
fn collect_all_referenced_type_names(def: &CTypeDefinition) -> Vec<String> {
let mut names = collect_by_value_type_names(def);
names.extend(collect_pointer_type_names(def));
names
}
fn collect_fn_type_names(fns: &[FreeFunctionItem], statics: &[StaticItem]) -> Vec<String> {
let mut names = Vec::new();
for item in fns {
let func = &item.function;
for input in &func.header.inputs {
collect_by_value_names_from_type(&input.type_, &mut names);
}
if let Some(ref output) = func.header.output {
collect_by_value_names_from_type(output, &mut names);
}
}
for s in statics {
collect_by_value_names_from_type(&s.type_, &mut names);
}
names
}