Skip to main content

decycle_impl/
process_module.rs

1use proc_macro2::Span;
2use proc_macro2::TokenStream;
3use proc_macro_error::*;
4use std::collections::HashSet;
5use syn::*;
6use template_quote::quote;
7
8fn check_submodule(module: &ItemMod) {
9    use syn::visit::Visit;
10    struct Visitor;
11    impl<'ast> Visit<'ast> for Visitor {
12        fn visit_attribute(&mut self, i: &'ast syn::Attribute) {
13            if crate::is_decycle_attribute(i) {
14                abort!(&i, "#[decycle] is not supported in nested modules")
15            }
16            syn::visit::visit_attribute(self, i);
17        }
18    }
19    Visitor.visit_item_mod(module);
20}
21
22fn process_trait_path(item: &Item) -> Vec<Path> {
23    match item {
24        Item::Trait(ItemTrait { ident, .. }) | Item::TraitAlias(ItemTraitAlias { ident, .. }) => {
25            vec![crate::ident_to_path(ident)]
26        }
27        Item::Use(ItemUse { tree, .. }) => {
28            fn process_use_tree(tree: &UseTree) -> Vec<Path> {
29                match tree {
30                    UseTree::Path(UsePath { tree, .. }) => process_use_tree(tree),
31                    UseTree::Name(UseName { ident })
32                    | UseTree::Rename(UseRename { rename: ident, .. }) => {
33                        vec![crate::ident_to_path(ident)]
34                    }
35                    UseTree::Glob(use_glob) => {
36                        abort!(use_glob, "glob is not supported in #[decycle] use")
37                    }
38                    UseTree::Group(UseGroup { items, .. }) => {
39                        items.iter().flat_map(process_use_tree).collect()
40                    }
41                }
42            }
43            process_use_tree(tree)
44        }
45        _ => unreachable!(),
46    }
47}
48
49pub fn process_module(
50    mut module: ItemMod,
51    decycle: &Path,
52    recurse_level: usize,
53    support_infinite_cycle: bool,
54) -> TokenStream {
55    let contents = &mut module
56        .content
57        .as_mut()
58        .unwrap_or_else(|| abort!(&module.semi, "needs content"))
59        .1;
60    let (traits, working_list): (Vec<_>, Vec<_>) = contents.iter_mut().fold(
61        Default::default(),
62        |(mut traits, mut working_list), item| {
63            match item {
64                Item::Trait(ItemTrait { attrs, .. })
65                | Item::TraitAlias(ItemTraitAlias { attrs, .. })
66                | Item::Use(ItemUse { attrs, .. }) => {
67                    // detect and remove #[decycle] attribute
68                    let mut old_attrs = std::mem::take(attrs).into_iter();
69                    let mut flag = false;
70                    attrs.extend((&mut old_attrs).take_while(|attr| {
71                        if crate::is_decycle_attribute(attr) {
72                            flag = true;
73                        }
74                        !flag
75                    }));
76                    attrs.extend(old_attrs);
77                    if flag {
78                        if let Item::Trait(item_trait) = item {
79                            traits.push(item_trait.clone());
80                        } else {
81                            working_list.extend(process_trait_path(item));
82                        }
83                    }
84                }
85                _ => (),
86            }
87            (traits, working_list)
88        },
89    );
90    proc_macro_error::set_dummy(
91        quote! { #{&module.ident} {#(for content in contents.clone()) { #content }} },
92    );
93    for item in contents.iter() {
94        match item {
95            Item::Mod(item_mod) => {
96                check_submodule(item_mod);
97            }
98            Item::Macro(_) => abort!(&item, "macro is not supported in #[decycle] module"),
99            _ => (),
100        }
101    }
102    if traits.is_empty() && working_list.is_empty() {
103        abort!(
104            Span::call_site(),
105            "cannot detect traits nor `use` statement annotated witn #[decycle]"
106        )
107    }
108    let all_traits: HashSet<Ident> = working_list
109        .iter()
110        .filter_map(|path| path.segments.last().map(|seg| seg.ident.clone()))
111        .chain(traits.iter().map(|ItemTrait { ident, .. }| ident.clone()))
112        .collect();
113    let (raw_contents, contents): (Vec<_>, Vec<_>) = contents
114        .iter()
115        .map(|content| {
116            if let Item::Impl(
117                item_impl @ ItemImpl {
118                    trait_: Some((_, trait_path, _)),
119                    ..
120                },
121            ) = content
122            {
123                // Check the trait_path contains just one segment
124                if trait_path.segments.len() == 1 {
125                    if let Some(seg) = trait_path.segments.first() {
126                        if all_traits.contains(&seg.ident) {
127                            // The item is impl of a trait annotated with #[decycle]
128                            return (None, Some(item_impl.clone()));
129                        }
130                    }
131                }
132            }
133            (Some(content), None)
134        })
135        .fold(
136            Default::default(),
137            |(mut raw_contents, mut contents), (raw_content, content)| {
138                raw_contents.extend(raw_content);
139                contents.extend(content);
140                (raw_contents, contents)
141            },
142        );
143    let first_path = working_list.first().cloned();
144    let mut args = crate::finalize::FinalizeArgs {
145        working_list,
146        traits,
147        contents,
148        recurse_level,
149        support_infinite_cycle,
150    };
151    args.working_list.push(parse_quote!(#decycle::__finalize));
152    quote! {
153        #(for attr in &module.attrs) { #attr }
154        #{&module.vis} #{&module.unsafety} #{&module.mod_token} #{&module.ident} {
155
156            #(for raw_content in raw_contents) { #raw_content }
157
158            #(if let Some(first_path) = first_path) {
159                #first_path! { #args }
160            }
161            #(else) {
162                #{ crate::finalize::finalize(args) }
163            }
164        }
165    }
166}