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 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 let builder_name = field.builder_name();
124 params.push(quote! { #builder_name : impl #bound_type });
125 {}
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}