use std::path::{Path, PathBuf};
use crate::core::ir::ApiSurface;
use super::helpers::{ReexportKind, collect_reexport_map, is_pub};
use super::reexports::{UseFilter, collect_use_names};
pub(super) fn module_children_dir(source: &Path) -> PathBuf {
let parent = source.parent().unwrap_or_else(|| Path::new("."));
match source.file_name().and_then(|n| n.to_str()) {
Some("mod.rs") | Some("lib.rs") | Some("main.rs") => parent.to_path_buf(),
_ => match source.file_stem().and_then(|s| s.to_str()) {
Some(stem) if !stem.is_empty() => parent.join(stem),
_ => parent.to_path_buf(),
},
}
}
fn find_module_declaring_file(module_dir: &Path) -> Option<PathBuf> {
for candidate_name in ["mod.rs", "lib.rs", "main.rs"] {
let candidate = module_dir.join(candidate_name);
if candidate.exists() {
return Some(candidate);
}
}
let name = module_dir.file_name()?.to_str()?;
let sibling = module_dir.parent()?.join(format!("{name}.rs"));
if sibling.exists() { Some(sibling) } else { None }
}
pub(super) fn apply_parent_reexport_shortening(
source: &Path,
crate_name: &str,
module_path: &str,
surface: &mut ApiSurface,
types_before: usize,
enums_before: usize,
fns_before: usize,
) {
let parent_dir = match source.parent() {
Some(p) => p,
None => return,
};
let parent_content = match find_module_declaring_file(parent_dir) {
Some(declaring_file) if declaring_file != source => std::fs::read_to_string(&declaring_file).ok(),
_ => None,
};
let Some(content) = parent_content else {
return;
};
let Ok(parent_file) = syn::parse_file(&content) else {
return;
};
let mod_name = source.file_stem().and_then(|s| s.to_str()).unwrap_or("");
if mod_name.is_empty() || mod_name == "mod" {
return;
}
let reexport_map = collect_reexport_map(&parent_file.items);
let mut reexported_names = std::collections::HashSet::new();
for item in &parent_file.items {
if let syn::Item::Use(item_use) = item
&& is_pub(&item_use.vis)
&& let syn::UseTree::Path(use_path) = &item_use.tree
&& use_path.ident == mod_name
{
match collect_use_names(&use_path.tree) {
UseFilter::All => {
let parent_module_path = module_path.rsplit_once("::").map(|(p, _)| p).unwrap_or("");
let parent_prefix = if parent_module_path.is_empty() {
crate_name.to_string()
} else {
format!("{crate_name}::{parent_module_path}")
};
for ty in &mut surface.types[types_before..] {
ty.rust_path = format!("{parent_prefix}::{}", ty.name);
}
for en in &mut surface.enums[enums_before..] {
en.rust_path = format!("{parent_prefix}::{}", en.name);
}
for func in &mut surface.functions[fns_before..] {
func.rust_path = format!("{parent_prefix}::{}", func.name);
}
return;
}
UseFilter::Names(names) => {
reexported_names.extend(names);
}
}
}
}
if let Some(ReexportKind::Names(names)) = reexport_map.get(mod_name) {
reexported_names.extend(names.iter().cloned());
} else if matches!(reexport_map.get(mod_name), Some(ReexportKind::Glob)) {
let parent_module_path = module_path.rsplit_once("::").map(|(p, _)| p).unwrap_or("");
let parent_prefix = if parent_module_path.is_empty() {
crate_name.to_string()
} else {
format!("{crate_name}::{parent_module_path}")
};
for ty in &mut surface.types[types_before..] {
ty.rust_path = format!("{parent_prefix}::{}", ty.name);
}
for en in &mut surface.enums[enums_before..] {
en.rust_path = format!("{parent_prefix}::{}", en.name);
}
for func in &mut surface.functions[fns_before..] {
func.rust_path = format!("{parent_prefix}::{}", func.name);
}
return;
}
if reexported_names.is_empty() {
return;
}
let parent_module_path = module_path.rsplit_once("::").map(|(p, _)| p).unwrap_or("");
let parent_prefix = if parent_module_path.is_empty() {
crate_name.to_string()
} else {
format!("{crate_name}::{parent_module_path}")
};
for ty in &mut surface.types[types_before..] {
if reexported_names.contains(&ty.name) {
ty.rust_path = format!("{parent_prefix}::{}", ty.name);
}
}
for en in &mut surface.enums[enums_before..] {
if reexported_names.contains(&en.name) {
en.rust_path = format!("{parent_prefix}::{}", en.name);
}
}
for func in &mut surface.functions[fns_before..] {
if reexported_names.contains(&func.name) {
func.rust_path = format!("{parent_prefix}::{}", func.name);
}
}
}
pub(super) fn derive_module_path(source: &Path, crate_src_dir: Option<&Path>) -> String {
let Some(root) = crate_src_dir else {
return String::new();
};
let root_canonical = std::fs::canonicalize(root).unwrap_or_else(|_| root.to_path_buf());
let source_canonical = std::fs::canonicalize(source).unwrap_or_else(|_| source.to_path_buf());
let Ok(relative) = source_canonical.strip_prefix(&root_canonical) else {
return String::new();
};
let mut segments = Vec::new();
for component in relative.iter() {
let s = component.to_string_lossy();
if s == "lib.rs" || s == "main.rs" {
return String::new();
} else if s == "mod.rs" {
continue;
} else if let Some(stem) = s.strip_suffix(".rs") {
segments.push(stem.to_string());
} else {
segments.push(s.to_string());
}
}
segments.join("::")
}
pub(super) fn validate_no_private_path_leaks(
surface: &ApiSurface,
crate_name: &str,
private_module_paths: &ahash::AHashSet<String>,
) -> anyhow::Result<()> {
if private_module_paths.is_empty() {
return Ok(());
}
let mut offenders: Vec<String> = Vec::new();
let crate_prefix = format!("{crate_name}::");
let mut check = |rust_path: &str| {
let Some(rest) = rust_path.strip_prefix(&crate_prefix) else {
return;
};
let segments: Vec<&str> = rest.split("::").collect();
for end in 1..segments.len() {
let prefix = segments[..end].join("::");
if private_module_paths.contains(&prefix) {
offenders.push(format!("{rust_path} (via private module `{crate_name}::{prefix}`)"));
return;
}
}
};
for ty in &surface.types {
check(&ty.rust_path);
}
for en in &surface.enums {
check(&en.rust_path);
}
for func in &surface.functions {
check(&func.rust_path);
}
if offenders.is_empty() {
return Ok(());
}
offenders.sort();
anyhow::bail!(
"alef: unreachable private-module path(s) in extracted API surface -- these items are \
only reachable through a module the crate declared without `pub`, and no bridging \
`pub use` could be resolved all the way to a public path. Emitting them would produce \
a generated binding that cannot compile (rustc E0433). Re-export the item directly from \
every module on its path, or exclude it explicitly:\n {}",
offenders.join("\n ")
);
}