use std::path::PathBuf;
use im_rc::Vector;
use proc_macro2::{TokenStream, TokenTree};
use crate::{attrs, Error, Resolver, ErrorCase};
pub(crate) fn expand_impl<R: Resolver>(
content: &mut Vec<syn::Item>,
resolver: &mut R,
modules_stack: Vector<syn::Ident>,
relative_path_where_to_look_for_nested_modules_naturally: Vector<PathBuf>,
relative_path_where_to_look_for_nested_modules_when_using_path_attribute: Vector<PathBuf>,
) -> Result<(), Error> {
'items_loop: for item in content {
let (item_mod, semicolon_after_module_declaration) = match item {
syn::Item::Mod(ref mut item_mod) => {
match (&item_mod.content, item_mod.semi) {
(None, None) => panic!("A module without both `{{}}` and `;`?"),
(Some(_), Some(_)) => panic!("A module with both `{{}}` and `;`?"),
(Some(_), None) => continue, (None, Some(semi)) => (item_mod, semi),
}
}
_ => continue,
};
let id = item_mod.ident.clone();
let mut inner_stack = modules_stack.clone();
inner_stack.push_back(id.clone());
let chunk = PathBuf::from(format!("{}", id));
let chunk_rs = PathBuf::from(format!("{}.rs", id));
let mut dirs_nat = relative_path_where_to_look_for_nested_modules_naturally.clone();
let mut dirs_attr =
relative_path_where_to_look_for_nested_modules_when_using_path_attribute.clone();
let len_hint = dirs_nat.len()
+ std::convert::identity::<usize>(dirs_attr.iter().map(|x| x.as_os_str().len()).sum())
+ 8
+ chunk.as_os_str().len();
let mut module_file_nomod = PathBuf::with_capacity(len_hint);
let mut module_file_mod = PathBuf::with_capacity(len_hint);
for x in &dirs_nat {
module_file_mod.push(x);
module_file_nomod.push(x);
}
module_file_mod.push(&chunk);
module_file_mod.push("mod.rs");
module_file_nomod.push(&chunk_rs);
let mod_syn_path = syn::Path {
leading_colon: None,
segments: syn::punctuated::Punctuated::from_iter(inner_stack.iter().map(|x| {
syn::PathSegment {
ident: x.clone(),
arguments: syn::PathArguments::None,
}
})),
};
let err = |c| Error {
module: mod_syn_path.clone(),
inner: c,
};
let mut attrs = Vec::with_capacity(item_mod.attrs.len());
let mut inner: Option<Option<syn::File>> = None;
let mut dirs_candidate = Vector::new();
let mut path_attrs: Vec<(Vec<TokenTree>, Option<TokenStream>)> = Vec::new();
let mut cfg_attrs: Vec<TokenStream> = Vec::new();
attrs::read_and_process_attributes(
&item_mod.attrs,
&mut path_attrs,
&mut attrs,
&mut cfg_attrs,
)
.map_err(|e| Error {
module: mod_syn_path.clone(),
inner: ErrorCase::AttrParseError(e),
})?;
for cfg in cfg_attrs {
let cfg: syn::Meta = syn::parse2(cfg).map_err(|e| err(ErrorCase::SynParseError(e)))?;
if !resolver
.check_cfg(cfg)
.map_err(|e| Error {
module: mod_syn_path.clone(),
inner: ErrorCase::ErrorFromCallback(e),
})?
{
continue 'items_loop;
}
}
for (tt, cfg) in path_attrs {
if cfg.is_none() && inner.is_some() {
return Err(err(ErrorCase::MultipleExplicitPathsSpecifiedForOneModule));
}
let explicit_path = attrs::extract_path_from_attr(tt, &mod_syn_path)?;
let mut module_file_explicit = PathBuf::with_capacity(len_hint);
for x in &dirs_attr {
module_file_explicit.push(x);
}
module_file_explicit.push(explicit_path);
dirs_candidate = Vector::new();
if let Some(parent) = module_file_explicit.parent() {
dirs_candidate.push_back(parent.to_owned());
}
if let Some(cfg) = cfg {
let cfg: syn::Meta = syn::parse2(cfg).map_err(|e| err(ErrorCase::SynParseError(e)))?;
if resolver
.check_cfg(cfg)
.map_err(|e| err(ErrorCase::ErrorFromCallback(e)))?
{
if inner.is_some() {
return Err(err(ErrorCase::MultipleExplicitPathsSpecifiedForOneModule));
}
inner = Some(resolver.resolve(mod_syn_path.clone(), module_file_explicit)?);
}
} else {
inner = Some(resolver.resolve(mod_syn_path.clone(), module_file_explicit)?);
}
}
let inner = if let Some(i) = inner {
dirs_attr = dirs_candidate.clone();
dirs_nat = dirs_candidate;
i
} else {
let inner_nomod = resolver.resolve(mod_syn_path.clone(), module_file_nomod);
match inner_nomod {
Ok(_) => (),
Err(Error{inner: ErrorCase::FailedToOpenFile { .. }, ..}) => (),
Err(e) => return Err(e),
}
let inner_mod = resolver.resolve(mod_syn_path.clone(), module_file_mod.clone());
match inner_mod {
Ok(_) => (),
Err(Error{inner: ErrorCase::FailedToOpenFile { .. }, ..}) => (),
Err(e) => return Err(e),
}
match (inner_nomod, inner_mod) {
(Ok(Some(_)), Ok(Some(_))) => {
return Err(err(ErrorCase::BothModRsAndNameRsPresent))
}
(Ok(None), Ok(None)) => None,
(Ok(Some(x)), _) => {
dirs_attr = dirs_nat.clone();
dirs_nat.push_back(PathBuf::from(chunk.clone()));
Some(x)
}
(_, Ok(Some(x))) => {
dirs_nat.push_back(PathBuf::from(chunk.clone()));
dirs_attr = dirs_nat.clone();
Some(x)
}
(Err(e), _) => return Err(e),
(_, Err(e)) => return Err(e),
}
};
if let Some(inner) = inner {
let mut items = inner.items;
for attr in inner.attrs {
attrs.push(attr);
}
expand_impl(&mut items, resolver, inner_stack, dirs_nat, dirs_attr)?;
let new_mod = syn::ItemMod {
attrs,
vis: item_mod.vis.clone(),
mod_token: item_mod.mod_token,
ident: id,
content: Some((
syn::token::Brace(semicolon_after_module_declaration.span),
items,
)),
semi: None,
};
*item = syn::Item::Mod(new_mod);
}
} Ok(())
}