ouroboros_macro/generate/
with_each.rs

1use crate::info_structures::{FieldType, Options, StructInfo};
2use proc_macro2::TokenStream;
3use proc_macro2_diagnostics::Diagnostic;
4use quote::{format_ident, quote};
5use syn::Error;
6
7pub enum ProcessingError {
8    Syntax(Error),
9    Covariance(Vec<Diagnostic>),
10}
11
12pub fn make_with_functions(info: &StructInfo, options: Options) -> (Vec<TokenStream>, Vec<Diagnostic>) {
13    let mut users = Vec::new();
14    let mut errors = Vec::new();
15    for field in &info.fields {
16        let visibility = &field.vis;
17        let field_name = &field.name;
18        let field_type = &field.typ;
19        // If the field is not a tail, we need to serve up the same kind of reference that other
20        // fields in the struct may have borrowed to ensure safety.
21        if field.field_type == FieldType::Tail {
22            let user_name = format_ident!("with_{}", &field.name);
23            let documentation = format!(
24                concat!(
25                    "Provides an immutable reference to `{0}`. This method was generated because ",
26                    "`{0}` is a [tail field](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions)."
27                ),
28                field.name.to_string()
29            );
30            let documentation = if !options.do_no_doc {
31                quote! {
32                    #[doc=#documentation]
33                }
34            } else {
35                quote! { #[doc(hidden)] }
36            };
37            users.push(quote! {
38                #documentation
39                #[inline(always)]
40                #visibility fn #user_name <'outer_borrow, ReturnType>(
41                    &'outer_borrow self,
42                    user: impl for<'this> ::core::ops::FnOnce(&'outer_borrow #field_type) -> ReturnType,
43                ) -> ReturnType {
44                    let field = &unsafe { self.actual_data.assume_init_ref() }.#field_name;
45                    user(field)
46                }
47            });
48            if field.covariant == Some(true) {
49                let borrower_name = format_ident!("borrow_{}", &field.name);
50                users.push(quote! {
51                    #documentation
52                    #[inline(always)]
53                    #visibility fn #borrower_name<'this>(
54                        &'this self,
55                    ) -> &'this #field_type {
56                        &unsafe { self.actual_data.assume_init_ref() }.#field_name
57                    }
58                });
59            } else if field.covariant.is_none() {
60                errors.push(field.covariance_error());
61            }
62            // If it is not borrowed at all it's safe to allow mutably borrowing it.
63            let user_name = format_ident!("with_{}_mut", &field.name);
64            let documentation = format!(
65                concat!(
66                    "Provides a mutable reference to `{0}`. This method was generated because ",
67                    "`{0}` is a [tail field](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions). ",
68                    "No `borrow_{0}_mut` function was generated because Rust's borrow checker is ",
69                    "currently unable to guarantee that such a method would be used safely."
70                ),
71                field.name.to_string()
72            );
73            let documentation = if !options.do_no_doc {
74                quote! {
75                    #[doc=#documentation]
76                }
77            } else {
78                quote! { #[doc(hidden)] }
79            };
80            users.push(quote! {
81                #documentation
82                #[inline(always)]
83                #visibility fn #user_name <'outer_borrow, ReturnType>(
84                    &'outer_borrow mut self,
85                    user: impl for<'this> ::core::ops::FnOnce(&'outer_borrow mut #field_type) -> ReturnType,
86                ) -> ReturnType {
87                    let field = &mut unsafe { self.actual_data.assume_init_mut() }.#field_name;
88                    user(field)
89                }
90            });
91        } else if field.field_type == FieldType::Borrowed {
92            let user_name = format_ident!("with_{}", &field.name);
93            let documentation = format!(
94                concat!(
95                    "Provides limited immutable access to `{0}`. This method was generated ",
96                    "because the contents of `{0}` are immutably borrowed by other fields."
97                ),
98                field.name.to_string()
99            );
100            let documentation = if !options.do_no_doc {
101                quote! {
102                    #[doc=#documentation]
103                }
104            } else {
105                quote! { #[doc(hidden)] }
106            };
107            users.push(quote! {
108                #documentation
109                #[inline(always)]
110                #visibility fn #user_name <'outer_borrow, ReturnType>(
111                    &'outer_borrow self,
112                    user: impl for<'this> ::core::ops::FnOnce(&'outer_borrow #field_type) -> ReturnType,
113                ) -> ReturnType {
114                    let field = &unsafe { self.actual_data.assume_init_ref() }.#field_name;
115                    user(field)
116                }
117            });
118            if field.self_referencing {
119                if field.covariant == Some(false) {
120                    // Skip the other functions, they will cause compiler errors.
121                    continue;
122                } else if field.covariant.is_none() {
123                    errors.push(field.covariance_error());
124                }
125            }
126            let borrower_name = format_ident!("borrow_{}", &field.name);
127            users.push(quote! {
128                #documentation
129                #[inline(always)]
130                #visibility fn #borrower_name<'this>(
131                    &'this self,
132                ) -> &'this #field_type {
133                    &unsafe { self.actual_data.assume_init_ref() }.#field_name
134                }
135            });
136        } else if field.field_type == FieldType::BorrowedMut {
137            // Do not generate anything because if it is borrowed mutably once, we should not be able
138            // to get any other kinds of references to it.
139        }
140    }
141    (users, errors)
142}