telety_impl/
telety.rs

1use quote::format_ident;
2use syn::{
3    AngleBracketedGenericArguments, Attribute, GenericArgument, Ident, Item, Path, PathArguments,
4    PathSegment, Visibility, spanned::Spanned,
5};
6
7use crate::{
8    Options, alias,
9    item_data::{ItemData as _, Namespaces},
10    syn_util, visitor,
11};
12
13/// Wraps an [Item] which has the `#[telety]` attribute to provide additional information
14/// allowing it to be reflected outside its original context.
15pub struct Telety<'item> {
16    options: Options,
17    item: &'item Item,
18    alias_map: alias::Map<'static>,
19    macro_ident: Ident,
20    visibility: Visibility,
21}
22
23impl<'item> Telety<'item> {
24    #[doc(hidden)]
25    pub fn new_with_options(item: &'item Item, options: Options) -> syn::Result<Self> {
26        if let Some(ident) = item.ident()
27            && ident.namespaces.contains(Namespaces::Macro)
28        {
29            return Err(syn::Error::new(
30                item.span(),
31                "Cannot be applied to items in the macro namespace",
32            ));
33        }
34
35        let Some(macro_ident) = options
36            .macro_ident
37            .as_ref()
38            .or(item.ident().map(|i| i.ident))
39            .cloned()
40        else {
41            return Err(syn::Error::new(
42                item.span(),
43                "Items without an identifier require a 'macro_ident' argument",
44            ));
45        };
46
47        let Some(visibility) = options.visibility.as_ref().or(item.vis()).cloned() else {
48            return Err(syn::Error::new(
49                item.span(),
50                "Items without a visibility require a 'visibility' argument",
51            ));
52        };
53
54        let unique_ident = Self::make_unique_ident(&options, &macro_ident);
55
56        let parameters = item.generics().cloned().unwrap_or_default();
57
58        let self_type = {
59            let arguments = PathArguments::AngleBracketed(AngleBracketedGenericArguments {
60                colon2_token: None,
61                lt_token: Default::default(),
62                args: syn_util::generic_params_to_arguments(&parameters),
63                gt_token: Default::default(),
64            });
65            // Use the global path to alert user if the containing_path is incorrect
66            let mut path = options.converted_containing_path();
67            path.segments.push(PathSegment {
68                ident: item.ident().unwrap().ident.clone(),
69                arguments,
70            });
71
72            path
73        };
74
75        let module = alias::Module::from_named_item(item)?;
76
77        let mut alias_map = alias::Map::new_root(
78            options.telety_path.clone(),
79            options.converted_containing_path(),
80            module,
81            parameters.clone(),
82            unique_ident,
83            &options,
84        );
85        alias_map.set_self(&self_type)?;
86
87        // Identify all unique non-type parameter types and give them an index
88        // (based on order of appearance), stored in our map
89        let mut identify_visitor = visitor::identify_aliases::IdentifyAliases::new(&mut alias_map);
90        directed_visit::visit(
91            &mut directed_visit::syn::direct::FullDefault,
92            &mut identify_visitor,
93            item,
94        );
95
96        Ok(Self {
97            options,
98            item,
99            alias_map,
100            macro_ident,
101            visibility,
102        })
103    }
104
105    /// Generate telety information for the [Item].
106    /// The item must have a proper `#[telety(...)]` attribute.
107    /// Usually this item will come from the telety-generated macro with the same name as the item.
108    pub fn new(item: &'item Item) -> syn::Result<Self> {
109        let options = Options::from_attrs(item.attrs())?;
110
111        Self::new_with_options(item, options)
112    }
113
114    pub fn options(&self) -> &Options {
115        &self.options
116    }
117
118    /// Provides the [alias::Map] for this item, which describes the mapping
119    /// of types appearing in the item to the aliases created for them.
120    pub fn alias_map(&self) -> &alias::Map<'_> {
121        &self.alias_map
122    }
123
124    #[doc(hidden)]
125    pub fn visibility(&self) -> &Visibility {
126        &self.visibility
127    }
128
129    /// Create a visitor which substitutes generic parameters as if this type were monomorphized
130    /// with the provided generic arguments.
131    /// For example, if we have a type:
132    /// ```rust,ignore
133    /// #[telety(crate)]
134    /// struct S<T, U, V = T>(T, U, V);
135    /// ```
136    /// and provided the arguments `[i32, u64]`,
137    /// the visitor would replace types `T` with `i32`,
138    /// `U` with `u64`, and `V` with `i32`.
139    /// See [syn::visit_mut].
140    pub fn generics_visitor<'a>(
141        &self,
142        generic_arguments: impl IntoIterator<Item = &'a GenericArgument>,
143    ) -> syn::Result<visitor::ApplyGenericArguments<'_>> {
144        let Some(parameters) = self.item.generics() else {
145            return Err(syn::Error::new(
146                self.item.span(),
147                "Item kind does not have generic parameters",
148            ));
149        };
150
151        visitor::ApplyGenericArguments::new(parameters, generic_arguments)
152    }
153
154    /// The [Item] this describes
155    pub fn item(&self) -> &Item {
156        self.item
157    }
158
159    /// The [Path] to the item, using the crate name, not the `crate::` qualifier,
160    /// and no arguments on the item.
161    pub fn path(&self) -> Path {
162        let mut path = self.options.module_path.clone();
163        if let Some(ident) = self.item.ident() {
164            path.segments.push(PathSegment {
165                ident: ident.ident.clone(),
166                arguments: PathArguments::None,
167            });
168        }
169        path
170    }
171
172    /// The [Attribute]s on the [Item]
173    pub fn attributes(&self) -> &[Attribute] {
174        self.item.attrs()
175    }
176
177    /// The [Path] of the module containing this [Item].
178    /// Provided by argument to the telety attribute.
179    pub fn containing_mod_path(&self) -> Path {
180        self.options.converted_containing_path()
181    }
182
183    pub fn macro_ident(&self) -> &Ident {
184        &self.macro_ident
185    }
186
187    fn module_path_ident(options: &Options) -> Ident {
188        let mut iter = options.module_path.segments.iter();
189        let mut unique_ident = iter
190            .next()
191            .expect("Path must have at least one segment")
192            .ident
193            .clone();
194        for segment in iter {
195            let i = &segment.ident;
196            unique_ident = format_ident!("{unique_ident}_{i}");
197        }
198        unique_ident
199    }
200
201    fn make_unique_ident(options: &Options, suffix: &Ident) -> Ident {
202        let module_path_ident = Self::module_path_ident(options);
203        format_ident!("{module_path_ident}_{suffix}")
204    }
205}