1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
extern crate proc_macro;

mod covariance_detection;
mod generate;
mod info_structures;
mod parse;
mod utils;

use crate::{
    generate::{
        constructor::create_builder_and_constructor, derives::create_derives,
        into_heads::make_into_heads, struc::create_actual_struct_def,
        summon_checker::generate_checker_summoner,
        try_constructor::create_try_builder_and_constructor, type_asserts::make_type_asserts,
        with_all::make_with_all_function, with_each::make_with_functions,
    },
    info_structures::Options,
    parse::parse_struct,
};
use inflector::Inflector;
use info_structures::BuilderType;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::TokenTree;
use proc_macro_error::proc_macro_error;
use quote::{format_ident, quote};
use syn::{Error, ItemStruct};

fn self_referencing_impl(
    original_struct_def: &ItemStruct,
    options: Options,
) -> Result<TokenStream, Error> {
    let struct_name = &original_struct_def.ident;
    let mod_name = format_ident!("ouroboros_impl_{}", struct_name.to_string().to_snake_case());
    let visibility = &original_struct_def.vis;

    let info = parse_struct(original_struct_def)?;

    let actual_struct_def = create_actual_struct_def(&info)?;

    let borrowchk_summoner = generate_checker_summoner(&info)?;

    let (builder_struct_name, builder_def, constructor_def) =
        create_builder_and_constructor(&info, options, BuilderType::Sync)?;
    let (async_builder_struct_name, async_builder_def, async_constructor_def) =
        create_builder_and_constructor(&info, options, BuilderType::Async)?;
    let (async_send_builder_struct_name, async_send_builder_def, async_send_constructor_def) =
        create_builder_and_constructor(&info, options, BuilderType::AsyncSend)?;
    let (try_builder_struct_name, try_builder_def, try_constructor_def) =
        create_try_builder_and_constructor(&info, options, BuilderType::Sync)?;
    let (async_try_builder_struct_name, async_try_builder_def, async_try_constructor_def) =
        create_try_builder_and_constructor(&info, options, BuilderType::Async)?;
    let (async_send_try_builder_struct_name, async_send_try_builder_def, async_send_try_constructor_def) =
        create_try_builder_and_constructor(&info, options, BuilderType::AsyncSend)?;

    let with_defs = make_with_functions(&info, options)?;
    let (with_all_struct_defs, with_all_fn_defs) = make_with_all_function(&info, options)?;
    let (heads_struct_def, into_heads_fn) = make_into_heads(&info, options);

    let impls = create_derives(&info)?;

    // These check that types like Box, Arc, and Rc refer to those types in the std lib and have not
    // been overridden.
    let type_asserts_def = make_type_asserts(&info);

    let extra_visibility = if options.do_pub_extras {
        visibility.clone()
    } else {
        syn::Visibility::Inherited
    };

    let generic_params = info.generic_params();
    let generic_args = info.generic_arguments();
    let generic_where = &info.generics.where_clause;
    Ok(TokenStream::from(quote! {
        #[doc="Encapsulates implementation details for a self-referencing struct. This module is only visible when using --document-private-items."]
        mod #mod_name {
            use super::*;
            #[doc="The self-referencing struct."]
            #actual_struct_def
            #borrowchk_summoner
            #builder_def
            #async_builder_def
            #async_send_builder_def
            #try_builder_def
            #async_try_builder_def
            #async_send_try_builder_def
            #with_all_struct_defs
            #heads_struct_def
            #impls
            impl <#generic_params> #struct_name <#(#generic_args),*> #generic_where {
                #constructor_def
                #async_constructor_def
                #async_send_constructor_def
                #try_constructor_def
                #async_try_constructor_def
                #async_send_try_constructor_def
                #(#with_defs)*
                #with_all_fn_defs
                #into_heads_fn
            }
            #type_asserts_def
        }
        #visibility use #mod_name :: #struct_name;
        #extra_visibility use #mod_name :: #builder_struct_name;
        #extra_visibility use #mod_name :: #async_builder_struct_name;
        #extra_visibility use #mod_name :: #async_send_builder_struct_name;
        #extra_visibility use #mod_name :: #try_builder_struct_name;
        #extra_visibility use #mod_name :: #async_try_builder_struct_name;
        #extra_visibility use #mod_name :: #async_send_try_builder_struct_name;
    }))
}

#[proc_macro_error]
#[proc_macro_attribute]
pub fn self_referencing(attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut options = Options {
        do_no_doc: false,
        do_pub_extras: false,
    };
    let mut expecting_comma = false;
    for token in <TokenStream as std::convert::Into<TokenStream2>>::into(attr).into_iter() {
        if let TokenTree::Ident(ident) = &token {
            if expecting_comma {
                return Error::new(token.span(), "Unexpected identifier, expected comma.")
                    .to_compile_error()
                    .into();
            }
            match &ident.to_string()[..] {
                "no_doc" => options.do_no_doc = true,
                "pub_extras" => options.do_pub_extras = true,
                _ => {
                    return Error::new_spanned(
                        &ident,
                        "Unknown identifier, expected 'no_doc' or 'pub_extras'.",
                    )
                    .to_compile_error()
                    .into()
                }
            }
            expecting_comma = true;
        } else if let TokenTree::Punct(punct) = &token {
            if !expecting_comma {
                return Error::new(token.span(), "Unexpected punctuation, expected identifier.")
                    .to_compile_error()
                    .into();
            }
            if punct.as_char() != ',' {
                return Error::new(token.span(), "Unknown punctuation, expected comma.")
                    .to_compile_error()
                    .into();
            }
            expecting_comma = false;
        } else {
            return Error::new(token.span(), "Unknown syntax, expected identifier.")
                .to_compile_error()
                .into();
        }
    }
    let original_struct_def: ItemStruct = syn::parse_macro_input!(item);
    match self_referencing_impl(&original_struct_def, options) {
        Ok(content) => content,
        Err(err) => err.to_compile_error().into(),
    }
}