use quote::format_ident;
use syn::{
AngleBracketedGenericArguments, Attribute, GenericArgument, Ident, Item, Path, PathArguments,
PathSegment, Visibility, spanned::Spanned,
};
use crate::{
Options, alias,
item_data::{ItemData as _, Namespaces},
syn_util, visitor,
};
pub struct Telety<'item> {
options: Options,
item: &'item Item,
alias_map: alias::Map<'static>,
macro_ident: Ident,
visibility: Visibility,
}
impl<'item> Telety<'item> {
#[doc(hidden)]
pub fn new_with_options(item: &'item Item, options: Options) -> syn::Result<Self> {
if let Some(ident) = item.ident()
&& ident.namespaces.contains(Namespaces::Macro)
{
return Err(syn::Error::new(
item.span(),
"Cannot be applied to items in the macro namespace",
));
}
let Some(macro_ident) = options
.macro_ident
.as_ref()
.or(item.ident().map(|i| i.ident))
.cloned()
else {
return Err(syn::Error::new(
item.span(),
"Items without an identifier require a 'macro_ident' argument",
));
};
let Some(visibility) = options.visibility.as_ref().or(item.vis()).cloned() else {
return Err(syn::Error::new(
item.span(),
"Items without a visibility require a 'visibility' argument",
));
};
let unique_ident = Self::make_unique_ident(&options, ¯o_ident);
let parameters = item.generics().cloned().unwrap_or_default();
let self_type = {
let arguments = PathArguments::AngleBracketed(AngleBracketedGenericArguments {
colon2_token: None,
lt_token: Default::default(),
args: syn_util::generic_params_to_arguments(¶meters),
gt_token: Default::default(),
});
let mut path = options.converted_containing_path();
path.segments.push(PathSegment {
ident: item.ident().unwrap().ident.clone(),
arguments,
});
path
};
let module = alias::Module::from_named_item(item)?;
let mut alias_map = alias::Map::new_root(
options.telety_path.clone(),
options.converted_containing_path(),
module,
parameters.clone(),
unique_ident,
&options,
);
alias_map.set_self(&self_type)?;
let mut identify_visitor = visitor::identify_aliases::IdentifyAliases::new(&mut alias_map);
directed_visit::visit(
&mut directed_visit::syn::direct::FullDefault,
&mut identify_visitor,
item,
);
Ok(Self {
options,
item,
alias_map,
macro_ident,
visibility,
})
}
pub fn new(item: &'item Item) -> syn::Result<Self> {
let options = Options::from_attrs(item.attrs())?;
Self::new_with_options(item, options)
}
pub fn options(&self) -> &Options {
&self.options
}
pub fn alias_map(&self) -> &alias::Map<'_> {
&self.alias_map
}
#[doc(hidden)]
pub fn visibility(&self) -> &Visibility {
&self.visibility
}
pub fn generics_visitor<'a>(
&self,
generic_arguments: impl IntoIterator<Item = &'a GenericArgument>,
) -> syn::Result<visitor::ApplyGenericArguments<'_>> {
let Some(parameters) = self.item.generics() else {
return Err(syn::Error::new(
self.item.span(),
"Item kind does not have generic parameters",
));
};
visitor::ApplyGenericArguments::new(parameters, generic_arguments)
}
pub fn item(&self) -> &Item {
self.item
}
pub fn path(&self) -> Path {
let mut path = self.options.module_path.clone();
if let Some(ident) = self.item.ident() {
path.segments.push(PathSegment {
ident: ident.ident.clone(),
arguments: PathArguments::None,
});
}
path
}
pub fn attributes(&self) -> &[Attribute] {
self.item.attrs()
}
pub fn containing_mod_path(&self) -> Path {
self.options.converted_containing_path()
}
pub fn macro_ident(&self) -> &Ident {
&self.macro_ident
}
fn module_path_ident(options: &Options) -> Ident {
let mut iter = options.module_path.segments.iter();
let mut unique_ident = iter
.next()
.expect("Path must have at least one segment")
.ident
.clone();
for segment in iter {
let i = &segment.ident;
unique_ident = format_ident!("{unique_ident}_{i}");
}
unique_ident
}
fn make_unique_ident(options: &Options, suffix: &Ident) -> Ident {
let module_path_ident = Self::module_path_ident(options);
format_ident!("{module_path_ident}_{suffix}")
}
}