ouroboros_macro/
lib.rs

1extern crate proc_macro;
2
3mod covariance_detection;
4mod generate;
5mod info_structures;
6mod parse;
7mod utils;
8
9use crate::{
10    generate::{
11        constructor::create_builder_and_constructor, derives::create_derives,
12        into_heads::make_into_heads, struc::create_internal_struct_def,
13        summon_checker::generate_checker_summoner,
14        try_constructor::create_try_builder_and_constructor, type_asserts::make_type_asserts,
15        with::make_with_all_function, with_each::make_with_functions,
16    },
17    info_structures::Options,
18    parse::parse_struct,
19};
20use generate::{
21    drop::create_drop_impl, struc::create_actual_struct_def, with_mut::make_with_all_mut_function,
22};
23use heck::ToSnakeCase;
24use info_structures::BuilderType;
25use proc_macro::TokenStream;
26use proc_macro2::TokenStream as TokenStream2;
27use proc_macro2::TokenTree;
28use quote::{format_ident, quote};
29use syn::{Error, ItemStruct};
30
31fn self_referencing_impl(
32    original_struct_def: &ItemStruct,
33    options: Options,
34) -> Result<TokenStream, Error> {
35    let struct_name = &original_struct_def.ident;
36    let mod_name = format_ident!("ouroboros_impl_{}", struct_name.to_string().to_snake_case());
37    let visibility = &original_struct_def.vis;
38
39    let info = parse_struct(original_struct_def)?;
40
41    let actual_struct_def = create_actual_struct_def(&info)?;
42    let internal_struct_def = create_internal_struct_def(&info)?;
43    let drop_impl = create_drop_impl(&info)?;
44
45    let borrowchk_summoner = generate_checker_summoner(&info)?;
46
47    let (builder_struct_name, builder_def, constructor_def) =
48        create_builder_and_constructor(&info, options, BuilderType::Sync)?;
49    let (async_builder_struct_name, async_builder_def, async_constructor_def) =
50        create_builder_and_constructor(&info, options, BuilderType::Async)?;
51    let (async_send_builder_struct_name, async_send_builder_def, async_send_constructor_def) =
52        create_builder_and_constructor(&info, options, BuilderType::AsyncSend)?;
53    let (try_builder_struct_name, try_builder_def, try_constructor_def) =
54        create_try_builder_and_constructor(&info, options, BuilderType::Sync)?;
55    let (async_try_builder_struct_name, async_try_builder_def, async_try_constructor_def) =
56        create_try_builder_and_constructor(&info, options, BuilderType::Async)?;
57    let (
58        async_send_try_builder_struct_name,
59        async_send_try_builder_def,
60        async_send_try_constructor_def,
61    ) = create_try_builder_and_constructor(&info, options, BuilderType::AsyncSend)?;
62
63    let (with_defs, with_errors) = make_with_functions(&info, options);
64    let with_errors = with_errors
65        .into_iter()
66        .map(|err| err.emit_as_item_tokens())
67        .collect::<Vec<_>>();
68    let (with_all_struct_def, with_all_fn_def) = make_with_all_function(&info, options)?;
69    let (with_all_mut_struct_def, with_all_mut_fn_def) =
70        make_with_all_mut_function(&info, options)?;
71    let (heads_struct_def, into_heads_fn) = make_into_heads(&info, options);
72
73    let impls = create_derives(&info)?;
74
75    // These check that types like Box, Arc, and Rc refer to those types in the std lib and have not
76    // been overridden.
77    let type_asserts_def = make_type_asserts(&info);
78
79    let extra_visibility = if options.do_pub_extras {
80        visibility.clone()
81    } else {
82        syn::Visibility::Inherited
83    };
84
85    let generic_params = info.generic_params();
86    let generic_args = info.generic_arguments();
87    let generic_where = &info.generics.where_clause;
88    Ok(TokenStream::from(quote! {
89        #[doc="Encapsulates implementation details for a self-referencing struct. This module is only visible when using --document-private-items."]
90        mod #mod_name {
91            use super::*;
92            #[doc="The self-referencing struct."]
93            #actual_struct_def
94            #internal_struct_def
95            #drop_impl
96            #borrowchk_summoner
97            #builder_def
98            #async_builder_def
99            #async_send_builder_def
100            #try_builder_def
101            #async_try_builder_def
102            #async_send_try_builder_def
103            #with_all_struct_def
104            #with_all_mut_struct_def
105            #(#with_errors)*
106            #heads_struct_def
107            #impls
108            impl <#generic_params> #struct_name <#(#generic_args),*> #generic_where {
109                #constructor_def
110                #async_constructor_def
111                #async_send_constructor_def
112                #try_constructor_def
113                #async_try_constructor_def
114                #async_send_try_constructor_def
115                #(#with_defs)*
116                #with_all_fn_def
117                #with_all_mut_fn_def
118                #into_heads_fn
119            }
120            #type_asserts_def
121        }
122        #visibility use #mod_name :: #struct_name;
123        #extra_visibility use #mod_name :: #builder_struct_name;
124        #extra_visibility use #mod_name :: #async_builder_struct_name;
125        #extra_visibility use #mod_name :: #async_send_builder_struct_name;
126        #extra_visibility use #mod_name :: #try_builder_struct_name;
127        #extra_visibility use #mod_name :: #async_try_builder_struct_name;
128        #extra_visibility use #mod_name :: #async_send_try_builder_struct_name;
129    }))
130}
131
132#[proc_macro_attribute]
133pub fn self_referencing(attr: TokenStream, item: TokenStream) -> TokenStream {
134    let mut options = Options {
135        do_no_doc: false,
136        do_pub_extras: false,
137    };
138    let mut expecting_comma = false;
139    for token in <TokenStream as std::convert::Into<TokenStream2>>::into(attr).into_iter() {
140        if let TokenTree::Ident(ident) = &token {
141            if expecting_comma {
142                return Error::new(token.span(), "Unexpected identifier, expected comma.")
143                    .to_compile_error()
144                    .into();
145            }
146            match &ident.to_string()[..] {
147                "no_doc" => options.do_no_doc = true,
148                "pub_extras" => options.do_pub_extras = true,
149                _ => {
150                    return Error::new_spanned(
151                        ident,
152                        "Unknown identifier, expected 'no_doc' or 'pub_extras'.",
153                    )
154                    .to_compile_error()
155                    .into()
156                }
157            }
158            expecting_comma = true;
159        } else if let TokenTree::Punct(punct) = &token {
160            if !expecting_comma {
161                return Error::new(token.span(), "Unexpected punctuation, expected identifier.")
162                    .to_compile_error()
163                    .into();
164            }
165            if punct.as_char() != ',' {
166                return Error::new(token.span(), "Unknown punctuation, expected comma.")
167                    .to_compile_error()
168                    .into();
169            }
170            expecting_comma = false;
171        } else {
172            return Error::new(token.span(), "Unknown syntax, expected identifier.")
173                .to_compile_error()
174                .into();
175        }
176    }
177    let original_struct_def: ItemStruct = syn::parse_macro_input!(item);
178    match self_referencing_impl(&original_struct_def, options) {
179        Ok(content) => content,
180        Err(err) => err.to_compile_error().into(),
181    }
182}