use std::collections::HashSet;
use std::path::{Path, PathBuf};
use crate::errors::{
dual_backed_module_error, missing_module_file_error, unknown_module_error,
unparseable_source_error, unreadable_source_error,
};
use crate::resolve::strip_raw;
use crate::syn_util::{
FlatItem, cfg_attr_path_values, direct_path_value, flatten_transparent_macros,
flatten_with_body_nested_impls, has_cfg_attr,
};
#[cfg(test)]
use crate::syn_util::flatten_transparent_macro_items;
fn module_segments(module: &str) -> Vec<String> {
module
.split("::")
.map(strip_raw)
.enumerate()
.filter(|(i, seg)| !(*i == 0 && seg == "crate"))
.map(|(_, seg)| seg)
.filter(|seg| !seg.is_empty())
.collect()
}
pub(crate) fn resolve_module_items_with_files(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<Vec<(syn::Item, PathBuf, usize)>, String> {
let branches = resolve_module_branches(src_dir, root_file, module, crate_package)?;
let mut items = Vec::new();
for (branch_index, (branch_items, file, ..)) in branches.iter().enumerate() {
let (flat, nested_impls) = flatten_with_body_nested_impls(branch_items);
let plain: Vec<syn::Item> = flat.into_iter().map(|f| f.item).collect();
items.extend(
plain
.into_iter()
.chain(nested_impls)
.map(|item| (item, file.clone(), branch_index)),
);
}
Ok(items)
}
pub(crate) fn resolve_module_items_with_cfg_tags(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<Vec<(FlatItem, PathBuf, usize)>, String> {
let branches = resolve_module_branches(src_dir, root_file, module, crate_package)?;
let mut items = Vec::new();
for (branch_index, (branch_items, file, ..)) in branches.iter().enumerate() {
let (flat, nested_impls) = flatten_with_body_nested_impls(branch_items);
let nested_impls = nested_impls.into_iter().map(FlatItem::plain);
items.extend(
flat.into_iter()
.chain(nested_impls)
.map(|flat| (flat, file.clone(), branch_index)),
);
}
Ok(items)
}
#[cfg(test)]
pub(crate) fn resolve_module_file(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<PathBuf, String> {
resolve_module_root(src_dir, root_file, module, crate_package).map(|(_items, file, _, _)| file)
}
#[cfg(test)]
pub(crate) fn resolve_module_root(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<(Vec<syn::Item>, PathBuf, PathBuf, PathBuf), String> {
let branches = resolve_module_branches(src_dir, root_file, module, crate_package)?;
let mut items = Vec::new();
for (branch_items, ..) in &branches {
items.extend(flatten_transparent_macro_items(branch_items));
}
let (_, file, child_dir, path_base) = &branches[0];
Ok((items, file.clone(), child_dir.clone(), path_base.clone()))
}
#[allow(clippy::type_complexity)]
pub(crate) fn resolve_module_branches(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<Vec<(Vec<syn::Item>, PathBuf, PathBuf, PathBuf)>, String> {
let root = read_parse(root_file)?;
let segments = module_segments(module);
let initial = Branch {
items: root.items,
current_file: root_file.to_path_buf(),
child_dir: src_dir.to_path_buf(),
path_base: src_dir.to_path_buf(),
};
let branches = descend(vec![initial], &segments, module, crate_package)?;
Ok(branches
.into_iter()
.map(|b| (b.items, b.current_file, b.child_dir, b.path_base))
.collect())
}
struct Branch {
items: Vec<syn::Item>,
current_file: PathBuf,
child_dir: PathBuf,
path_base: PathBuf,
}
fn push_inline_mod_branches(
branch: &Branch,
flat_items: &[FlatItem],
seg: &str,
next_branches: &mut Vec<Branch>,
) -> Result<(), String> {
for flat in flat_items {
if let syn::Item::Mod(module_item) = &flat.item {
if strip_raw(&module_item.ident.to_string()) != *seg {
continue;
}
let Some((_, inner)) = &module_item.content else {
continue; };
let conventional = branch.child_dir.join(seg);
let bases: Vec<std::path::PathBuf> = match direct_path_value(&module_item.attrs)
.map(|rel| branch.path_base.join(rel))
{
Some(relocated) => vec![relocated],
None => {
let present: Vec<std::path::PathBuf> = cfg_attr_path_values(&module_item.attrs)
.into_iter()
.map(|rel| branch.path_base.join(rel))
.chain(std::iter::once(conventional.clone()))
.collect();
let mut present: Vec<PathBuf> = {
let mut kept = Vec::new();
for base in present {
if xingbiao::is_directory(&base)? {
kept.push(base);
}
}
kept
};
present.sort();
present.dedup();
if present.is_empty() {
vec![conventional]
} else {
present
}
}
};
for inline_dir in bases {
next_branches.push(Branch {
items: inner.clone(),
current_file: branch.current_file.clone(),
child_dir: inline_dir.clone(),
path_base: inline_dir,
});
}
}
}
Ok(())
}
fn push_file_form_branches(
branch: &Branch,
flat_items: &[FlatItem],
seg: &str,
module: &str,
crate_package: &str,
next_branches: &mut Vec<Branch>,
) -> Result<(), String> {
let mut file_forms: Vec<(Vec<syn::Item>, PathBuf, PathBuf, PathBuf)> = Vec::new();
let mut seen_files: HashSet<PathBuf> = HashSet::new();
for flat in flat_items {
if let syn::Item::Mod(module_item) = &flat.item {
if module_item.content.is_some() {
continue; }
if strip_raw(&module_item.ident.to_string()) != *seg {
continue;
}
let cfg_conditional = flat.in_transparent_arm || has_cfg_attr(&module_item.attrs);
if let Some(rel) = direct_path_value(&module_item.attrs) {
let file = branch.path_base.join(&rel);
if !xingbiao::is_regular_file(&file)? {
if cfg_conditional {
continue;
}
return Err(missing_module_file_error(module, crate_package));
}
if !xingbiao::try_visit(&mut seen_files, &file)? {
continue;
}
let parsed = read_parse(&file)?;
let next_dir = file
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| branch.child_dir.clone());
file_forms.push((parsed.items, file, next_dir.clone(), next_dir));
continue;
}
let cfg_attr_targets = cfg_attr_path_values(&module_item.attrs);
let mut has_backing_source = false;
for rel in &cfg_attr_targets {
let file = branch.path_base.join(rel);
if xingbiao::is_regular_file(&file)? {
has_backing_source = true;
if xingbiao::try_visit(&mut seen_files, &file)? {
let parsed = read_parse(&file)?;
let next_dir = file
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| branch.child_dir.clone());
file_forms.push((parsed.items, file, next_dir.clone(), next_dir));
}
}
}
let file = match locate_module_file(&branch.child_dir, seg)? {
ModuleFile::One(file) => file,
ModuleFile::Ambiguous { flat, nested } => {
return Err(dual_backed_module_error(
module,
seg,
crate_package,
&flat,
&nested,
));
}
ModuleFile::Absent => {
if has_backing_source || cfg_conditional {
continue;
}
return Err(missing_module_file_error(module, crate_package));
}
};
if !xingbiao::try_visit(&mut seen_files, &file)? {
continue;
}
let parsed = read_parse(&file)?;
let own_dir = file
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| branch.child_dir.join(seg));
file_forms.push((parsed.items, file, own_dir, branch.child_dir.join(seg)));
}
}
for (file_items, file, path_base, child_dir) in file_forms {
next_branches.push(Branch {
items: file_items,
current_file: file,
child_dir,
path_base,
});
}
Ok(())
}
fn descend(
branches: Vec<Branch>,
segments: &[String],
module: &str,
crate_package: &str,
) -> Result<Vec<Branch>, String> {
let Some(seg) = segments.first() else {
return Ok(branches);
};
let mut next_branches = Vec::new();
for branch in &branches {
let flat_items = flatten_transparent_macros(&branch.items);
push_inline_mod_branches(branch, &flat_items, seg, &mut next_branches)?;
push_file_form_branches(
branch,
&flat_items,
seg,
module,
crate_package,
&mut next_branches,
)?;
}
if next_branches.is_empty() {
return Err(unknown_module_error(module, crate_package));
}
descend(next_branches, &segments[1..], module, crate_package)
}
pub(crate) enum ModuleFile {
Absent,
One(PathBuf),
Ambiguous { flat: PathBuf, nested: PathBuf },
}
pub(crate) fn locate_module_file(child_dir: &Path, seg: &str) -> Result<ModuleFile, String> {
let flat = child_dir.join(format!("{seg}.rs"));
let nested = child_dir.join(seg).join("mod.rs");
match (
xingbiao::is_regular_file(&flat)?,
xingbiao::is_regular_file(&nested)?,
) {
(true, true) => Ok(ModuleFile::Ambiguous { flat, nested }),
(true, false) => Ok(ModuleFile::One(flat)),
(false, true) => Ok(ModuleFile::One(nested)),
(false, false) => Ok(ModuleFile::Absent),
}
}
pub(crate) fn read_parse(file: &Path) -> Result<syn::File, String> {
let text = std::fs::read_to_string(file)
.map_err(|err| unreadable_source_error(file, &err.to_string()))?;
syn::parse_file(&text).map_err(|err| unparseable_source_error(file, &err.to_string()))
}