ouroboros_macro/generate/
try_constructor.rs

1use crate::{
2    info_structures::{ArgType, BuilderType, FieldType, Options, StructInfo},
3    utils::to_class_case,
4};
5use proc_macro2::{Ident, TokenStream};
6use quote::{format_ident, quote};
7use syn::Error;
8
9pub fn create_try_builder_and_constructor(
10    info: &StructInfo,
11    options: Options,
12    builder_type: BuilderType,
13) -> Result<(Ident, TokenStream, TokenStream), Error> {
14    let struct_name = info.ident.clone();
15    let generic_args = info.generic_arguments();
16
17    let visibility = if options.do_pub_extras {
18        info.vis.clone()
19    } else {
20        syn::parse_quote! { pub(super) }
21    };
22    let mut head_recover_code = Vec::new();
23    for field in &info.fields {
24        if !field.self_referencing {
25            let field_name = &field.name;
26            head_recover_code.push(quote! { #field_name });
27        }
28    }
29    for (_ty, ident) in info.generic_consumers() {
30        head_recover_code.push(quote! { #ident: ::core::marker::PhantomData });
31    }
32    let mut current_head_index = 0;
33
34    let builder_struct_name = match builder_type {
35        BuilderType::AsyncSend => format_ident!("{}AsyncSendTryBuilder", info.ident),
36        BuilderType::Async => format_ident!("{}AsyncTryBuilder", info.ident),
37        BuilderType::Sync => format_ident!("{}TryBuilder", info.ident),
38    };
39    let documentation = format!(
40        concat!(
41            "(See also [`{0}::try_build()`]({0}::try_build).) Like [`new`](Self::new), but ",
42            "builders for [self-referencing fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) ",
43            "can return results. If any of them fail, `Err` is returned. If all of them ",
44            "succeed, `Ok` is returned. The arguments are as follows:\n\n",
45            "| Argument | Suggested Use |\n| --- | --- |\n",
46        ),
47        builder_struct_name.to_string()
48    );
49    let or_recover_documentation = format!(
50        concat!(
51            "(See also [`{0}::try_build_or_recover()`]({0}::try_build_or_recover).) Like ",
52            "[`try_new`](Self::try_new), but all ",
53            "[head fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) ",
54            "are returned in the case of an error. The arguments are as follows:\n\n",
55            "| Argument | Suggested Use |\n| --- | --- |\n",
56        ),
57        builder_struct_name.to_string()
58    );
59    let builder_documentation = concat!(
60        "A more verbose but stable way to construct self-referencing structs. It is ",
61        "comparable to using `StructName { field1: value1, field2: value2 }` rather than ",
62        "`StructName::new(value1, value2)`. This has the dual benefit of making your code ",
63        "both easier to refactor and more readable. Call [`try_build()`](Self::try_build) or ",
64        "[`try_build_or_recover()`](Self::try_build_or_recover) to ",
65        "construct the actual struct. The fields of this struct should be used as follows:\n\n",
66        "| Field | Suggested Use |\n| --- | --- |\n",
67    )
68    .to_owned();
69    let build_fn_documentation = format!(
70        concat!(
71            "Calls [`{0}::try_new()`]({0}::try_new) using the provided values. This is ",
72            "preferable over calling `try_new()` directly for the reasons listed above. "
73        ),
74        info.ident.to_string()
75    );
76    let build_or_recover_fn_documentation = format!(
77        concat!(
78            "Calls [`{0}::try_new_or_recover()`]({0}::try_new_or_recover) using the provided ",
79            "values. This is preferable over calling `try_new_or_recover()` directly for the ",
80            "reasons listed above. "
81        ),
82        info.ident.to_string()
83    );
84    let mut doc_table = "".to_owned();
85    let mut or_recover_code: Vec<TokenStream> = Vec::new();
86    let mut params: Vec<TokenStream> = Vec::new();
87    let mut builder_struct_generic_producers: Vec<_> = info
88        .generic_params()
89        .iter()
90        .map(|param| quote! { #param })
91        .collect();
92    let mut builder_struct_generic_consumers = info.generic_arguments();
93    let mut builder_struct_fields = Vec::new();
94    let mut builder_struct_field_names = Vec::new();
95
96    for field in &info.fields {
97        let field_name = &field.name;
98
99        let arg_type = field.make_try_constructor_arg_type(info, builder_type)?;
100        if let ArgType::Plain(plain_type) = arg_type {
101            // No fancy builder function, we can just move the value directly into the struct.
102            params.push(quote! { #field_name: #plain_type });
103            builder_struct_fields.push(quote! { #field_name: #plain_type });
104            builder_struct_field_names.push(quote! { #field_name });
105            doc_table += &format!(
106                "| `{}` | Directly pass in the value this field should contain |\n",
107                field_name
108            );
109            if !field.self_referencing {
110                if field.is_borrowed() {
111                    head_recover_code[current_head_index] = quote! {
112                        #field_name: ::ouroboros::macro_help::unbox(#field_name)
113                    };
114                } else {
115                    head_recover_code[current_head_index] = quote! { #field_name };
116                }
117                current_head_index += 1;
118            }
119        } else if let ArgType::TraitBound(bound_type) = arg_type {
120            // Trait bounds are much trickier. We need a special syntax to accept them in the
121            // constructor, and generic parameters need to be added to the builder struct to make
122            // it work.
123            let builder_name = field.builder_name();
124            params.push(quote! { #builder_name : impl #bound_type });
125            // Ok so hear me out basically without this thing here my IDE thinks the rest of the
126            // code is a string and it all turns green.
127            {}
128            doc_table += &format!(
129                "| `{}` | Use a function or closure: `(",
130                builder_name
131            );
132            let mut builder_args = Vec::new();
133            for (index, borrow) in field.borrows.iter().enumerate() {
134                let borrowed_name = &info.fields[borrow.index].name;
135                builder_args.push(format_ident!("{}_illegal_static_reference", borrowed_name));
136                doc_table += &format!(
137                    "{}: &{}_",
138                    borrowed_name,
139                    if borrow.mutable { "mut " } else { "" },
140                );
141                if index < field.borrows.len() - 1 {
142                    doc_table += ", ";
143                }
144            }
145            doc_table += &format!(") -> Result<{}: _, Error_>` | \n", field_name);
146            let builder_value = if builder_type.is_async() {
147                quote! { #builder_name (#(#builder_args),*).await }
148            } else {
149                quote! { #builder_name (#(#builder_args),*) }
150            };
151            or_recover_code.push(quote! {
152                let #field_name = match #builder_value {
153                    ::core::result::Result::Ok(value) => value,
154                    ::core::result::Result::Err(err)
155                        => return ::core::result::Result::Err((err, Heads { #(#head_recover_code),* })),
156                };
157            });
158            let generic_type_name =
159                format_ident!("{}Builder_", to_class_case(field_name.to_string().as_str()));
160
161            builder_struct_generic_producers.push(quote! { #generic_type_name: #bound_type });
162            builder_struct_generic_consumers.push(quote! { #generic_type_name });
163            builder_struct_fields.push(quote! { #builder_name: #generic_type_name });
164            builder_struct_field_names.push(quote! { #builder_name });
165        }
166        if field.is_borrowed() {
167            let boxed = field.boxed();
168            if field.field_type == FieldType::BorrowedMut {
169                or_recover_code.push(quote! { let mut #field_name = #boxed; });
170            } else {
171                or_recover_code.push(quote! { let #field_name = #boxed; });
172            }
173        }
174
175        if field.field_type == FieldType::Borrowed {
176            or_recover_code.push(field.make_illegal_static_reference());
177        } else if field.field_type == FieldType::BorrowedMut {
178            or_recover_code.push(field.make_illegal_static_mut_reference());
179        }
180    }
181    let documentation = if !options.do_no_doc {
182        let documentation = documentation + &doc_table;
183        quote! {
184            #[doc=#documentation]
185        }
186    } else {
187        quote! { #[doc(hidden)] }
188    };
189    let or_recover_documentation = if !options.do_no_doc {
190        let or_recover_documentation = or_recover_documentation + &doc_table;
191        quote! {
192            #[doc=#or_recover_documentation]
193        }
194    } else {
195        quote! { #[doc(hidden)] }
196    };
197    let builder_documentation = if !options.do_no_doc {
198        let builder_documentation = builder_documentation + &doc_table;
199        quote! {
200            #[doc=#builder_documentation]
201        }
202    } else {
203        quote! { #[doc(hidden)] }
204    };
205    let or_recover_ident = match builder_type {
206        BuilderType::AsyncSend => quote! { try_new_or_recover_async_send },
207        BuilderType::Async => quote! { try_new_or_recover_async },
208        BuilderType::Sync => quote! { try_new_or_recover },
209    };
210    let or_recover_constructor_fn = if builder_type.is_async() {
211        quote! { async fn #or_recover_ident }
212    } else {
213        quote! { fn #or_recover_ident }
214    };
215    let constructor_fn = match builder_type {
216        BuilderType::AsyncSend => quote! { async fn try_new_async_send },
217        BuilderType::Async => quote! { async fn try_new_async },
218        BuilderType::Sync => quote! { fn try_new },
219    };
220    let constructor_code = if builder_type.is_async() {
221        quote! { #struct_name::#or_recover_ident(#(#builder_struct_field_names),*).await.map_err(|(error, _heads)| error) }
222    } else {
223        quote! { #struct_name::#or_recover_ident(#(#builder_struct_field_names),*).map_err(|(error, _heads)| error) }
224    };
225    let field_names: Vec<_> = info.fields.iter().map(|field| field.name.clone()).collect();
226    let internal_ident = &info.internal_ident;
227    let constructor_def = quote! {
228        #documentation
229        #visibility #constructor_fn<Error_>(#(#params),*) -> ::core::result::Result<#struct_name <#(#generic_args),*>, Error_> {
230            #constructor_code
231        }
232        #or_recover_documentation
233        #visibility #or_recover_constructor_fn<Error_>(#(#params),*) -> ::core::result::Result<#struct_name <#(#generic_args),*>, (Error_, Heads<#(#generic_args),*>)> {
234            #(#or_recover_code)*
235            ::core::result::Result::Ok(unsafe {
236                Self {
237                    actual_data: ::core::mem::MaybeUninit::new(#internal_ident {
238                        #(#field_names),*
239                    })
240                }
241            })
242        }
243    };
244    builder_struct_generic_producers.push(quote! { Error_ });
245    builder_struct_generic_consumers.push(quote! { Error_ });
246    let generic_where = &info.generics.where_clause;
247    let builder_fn = if builder_type.is_async() {
248        quote! { async fn try_build }
249    } else {
250        quote! { fn try_build }
251    };
252    let or_recover_builder_fn = if builder_type.is_async() {
253        quote! { async fn try_build_or_recover }
254    } else {
255        quote! { fn try_build_or_recover }
256    };
257    let builder_code = match builder_type {
258        BuilderType::AsyncSend => quote! {
259            #struct_name::try_new_async_send(
260                #(self.#builder_struct_field_names),*
261            ).await
262        },
263        BuilderType::Async => quote! {
264            #struct_name::try_new_async(
265                #(self.#builder_struct_field_names),*
266            ).await
267        },
268        BuilderType::Sync => quote! {
269            #struct_name::try_new(
270                #(self.#builder_struct_field_names),*
271            )
272        },
273    };
274    let or_recover_builder_code = match builder_type {
275        BuilderType::AsyncSend => quote! {
276            #struct_name::try_new_or_recover_async_send(
277                #(self.#builder_struct_field_names),*
278            ).await
279        },
280        BuilderType::Async => quote! {
281            #struct_name::try_new_or_recover_async(
282                #(self.#builder_struct_field_names),*
283            ).await
284        },
285        BuilderType::Sync => quote! {
286            #struct_name::try_new_or_recover(
287                #(self.#builder_struct_field_names),*
288            )
289        },
290    };
291    let builder_def = quote! {
292        #builder_documentation
293        #visibility struct #builder_struct_name <#(#builder_struct_generic_producers),*> #generic_where {
294            #(#visibility #builder_struct_fields),*
295        }
296        impl<#(#builder_struct_generic_producers),*> #builder_struct_name <#(#builder_struct_generic_consumers),*> #generic_where {
297            #[doc=#build_fn_documentation]
298            #visibility #builder_fn(self) -> ::core::result::Result<#struct_name <#(#generic_args),*>, Error_> {
299                #builder_code
300            }
301            #[doc=#build_or_recover_fn_documentation]
302            #visibility #or_recover_builder_fn(self) -> ::core::result::Result<#struct_name <#(#generic_args),*>, (Error_, Heads<#(#generic_args),*>)> {
303                #or_recover_builder_code
304            }
305        }
306    };
307    Ok((builder_struct_name, builder_def, constructor_def))
308}