use std::path::PathBuf;
use im_rc::Vector;
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{punctuated::Punctuated, spanned::Spanned, MetaList, Token};
use crate::{attrs, Error, ErrorCase, Resolver};
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>,
multimodule_mode: bool,
) -> Result<(), Error> {
let mut multimodule_tmp_container: Vec<syn::Item> = if multimodule_mode {
Vec::with_capacity(content.len())
} else {
Vec::new()
};
'items_loop: for item in &mut *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),
}
}
_ => {
if multimodule_mode {
multimodule_tmp_container.push(item.clone());
}
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());
struct ExpandedModuleInfo {
result: Option<syn::File>,
dirs_attr: Vector<PathBuf>,
dirs_nat: Vector<PathBuf>,
injected_cfg: Option<syn::Meta>,
}
let mut expansion_candidates: Vec<ExpandedModuleInfo> = Vec::with_capacity(1);
let mut path_attrs: Vec<(PathBuf, 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;
}
}
let mut need_to_try_natural_file_locations = true;
let mut accumulated_cfgs = Vec::<syn::Meta>::new();
for (explicit_path, condition_in_cfg_attr_path) in path_attrs {
if condition_in_cfg_attr_path.is_none() && !expansion_candidates.is_empty() && !multimodule_mode {
return Err(err(ErrorCase::MultipleExplicitPathsSpecifiedForOneModule));
}
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);
let mut dirs_candidate = Vector::new();
if let Some(parent) = module_file_explicit.parent() {
dirs_candidate.push_back(parent.to_owned());
}
if let Some(cfg) = condition_in_cfg_attr_path {
let cfg: syn::Meta =
syn::parse2(cfg).map_err(|e| err(ErrorCase::SynParseError(e)))?;
if resolver
.check_cfg(cfg.clone())
.map_err(|e| err(ErrorCase::ErrorFromCallback(e)))?
{
if !expansion_candidates.is_empty() && !multimodule_mode {
return Err(err(ErrorCase::MultipleExplicitPathsSpecifiedForOneModule));
}
if !multimodule_mode {
need_to_try_natural_file_locations = false;
}
let result = resolver.resolve(mod_syn_path.clone(), module_file_explicit)?;
expansion_candidates.push(ExpandedModuleInfo {
result,
dirs_attr: dirs_candidate.clone(),
dirs_nat: dirs_candidate,
injected_cfg: Some(cfg.clone()),
});
accumulated_cfgs.push(cfg);
}
} else {
need_to_try_natural_file_locations = false;
let result = resolver.resolve(mod_syn_path.clone(), module_file_explicit)?;
expansion_candidates.push(ExpandedModuleInfo {
result,
dirs_attr: dirs_candidate.clone(),
dirs_nat: dirs_candidate,
injected_cfg: None,
});
}
}
assert!(multimodule_mode || expansion_candidates.len() <= 1);
if need_to_try_natural_file_locations {
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),
}
let result = 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(ref e1), Err(ref e2))
if multimodule_mode
&& matches!(
(&e1.inner, &e2.inner),
(
ErrorCase::FailedToOpenFile { .. },
ErrorCase::FailedToOpenFile { .. }
)
) =>
{
None
}
(Err(e), _) => return Err(e),
(_, Err(e)) => return Err(e),
};
let some_span = item_mod.span();
let throwaway_group = proc_macro2::Group::new(proc_macro2::Delimiter::Bracket, Default::default());
let some_delim_span = throwaway_group.delim_span();
let cfg = if multimodule_mode && !accumulated_cfgs.is_empty() {
let tokens_inside_any : TokenStream = Punctuated::<_,Token![,]>::from_iter(
accumulated_cfgs
.iter()
.map(|x| x.clone()),
).into_token_stream();
let tokens_inside_not : TokenStream = syn::Meta::List(
MetaList {
path: simple_path(some_span, "any"),
delimiter: syn::MacroDelimiter::Paren(syn::token::Paren { span: some_delim_span }),
tokens: tokens_inside_any,
},
).into_token_stream();
Some(syn::Meta::List(MetaList {
path: simple_path(some_span, "not"),
delimiter: syn::MacroDelimiter::Paren(syn::token::Paren { span: some_delim_span }),
tokens: tokens_inside_not,
}))
} else {
None
};
expansion_candidates.push(ExpandedModuleInfo {
result,
dirs_attr,
dirs_nat,
injected_cfg: cfg,
});
}
assert!(!expansion_candidates.is_empty());
if !multimodule_mode {
assert_eq!(expansion_candidates.len(), 1);
}
for ExpandedModuleInfo {
result,
dirs_attr,
dirs_nat,
injected_cfg: cfg,
} in expansion_candidates.into_iter()
{
if let Some(inner) = result {
let mut inner_items = inner.items;
let mut attrs_copy = attrs.clone();
let some_span = item_mod.span();
let throwaway_group = proc_macro2::Group::new(proc_macro2::Delimiter::Bracket, Default::default());
let some_delim_span = throwaway_group.delim_span();
if let Some(cfg) = cfg {
if multimodule_mode {
attrs_copy.push(syn::Attribute {
pound_token: syn::token::Pound { spans: [some_span] },
style: syn::AttrStyle::Outer,
bracket_token: syn::token::Bracket { span: some_delim_span },
meta : syn::Meta::List(syn::MetaList {
path: simple_path(some_span, "cfg"),
delimiter: syn::MacroDelimiter::Paren(syn::token::Paren { span: some_delim_span }),
tokens: cfg.into_token_stream(),
}),
})
}
}
for attr in inner.attrs {
attrs_copy.push(attr);
}
expand_impl(
&mut inner_items,
resolver,
inner_stack.clone(),
dirs_nat,
dirs_attr,
multimodule_mode,
)?;
let vis = item_mod.vis.clone();
let mod_token = item_mod.mod_token.clone();
let new_mod = syn::ItemMod {
attrs: attrs_copy,
vis,
mod_token,
ident: id.clone(),
content: Some((
syn::token::Brace(semicolon_after_module_declaration.span),
inner_items,
)),
semi: None,
unsafety: item_mod.unsafety,
};
if !multimodule_mode {
*item = syn::Item::Mod(new_mod);
continue 'items_loop;
} else {
multimodule_tmp_container.push(syn::Item::Mod(new_mod));
}
}
}
} if multimodule_mode {
*content = multimodule_tmp_container;
}
Ok(())
}
fn simple_path(span: proc_macro2::Span, name: &'static str) -> syn::Path {
syn::Path {
leading_colon: None,
segments: Punctuated::from_iter([syn::punctuated::Pair::new(
syn::PathSegment {
ident: syn::Ident::new(name, span),
arguments: syn::PathArguments::None,
},
None,
)]),
}
}