ouroboros_macro/generate/
with_mut.rs

1use crate::{
2    info_structures::{FieldType, Options, StructInfo},
3    utils::{replace_this_with_lifetime, uses_this_lifetime},
4};
5use proc_macro2::{Span, TokenStream};
6use quote::{format_ident, quote};
7use syn::{Error, Lifetime, WhereClause};
8
9pub fn make_with_all_mut_function(
10    info: &StructInfo,
11    options: Options,
12) -> Result<(TokenStream, TokenStream), Error> {
13    let visibility = if options.do_pub_extras {
14        info.vis.clone()
15    } else {
16        syn::parse_quote! { pub(super) }
17    };
18    let mut mut_fields = Vec::new();
19    let mut mut_field_assignments = Vec::new();
20    let mut lifetime_idents = Vec::new();
21    // I don't think the reverse is necessary but it does make the expanded code more uniform.
22    for (index, field) in info.fields.iter().rev().enumerate() {
23        let field_name = &field.name;
24        let original_field_type = &field.typ;
25        let lifetime = format_ident!("this{}", index);
26        let field_type = replace_this_with_lifetime(quote! { #original_field_type }, lifetime.clone());
27        if field.field_type == FieldType::Tail {
28            mut_fields.push(quote! { #visibility #field_name: &'outer_borrow mut #field_type });
29            mut_field_assignments.push(quote! { #field_name: &mut this.#field_name });
30            if uses_this_lifetime(quote! { #original_field_type }) {
31                lifetime_idents.push(lifetime.clone());
32            }
33        } else if field.field_type == FieldType::Borrowed {
34            let ass = quote! { #field_name: unsafe {
35                ::ouroboros::macro_help::change_lifetime(
36                    &*this.#field_name
37                )
38            } };
39            let lt = Lifetime::new(&format!("'{}", lifetime), Span::call_site());
40            mut_fields.push(quote! { #visibility #field_name: &#lt #field_type });
41            mut_field_assignments.push(ass);
42            lifetime_idents.push(lifetime.clone());
43        } else if field.field_type == FieldType::BorrowedMut {
44            // Add nothing because we cannot borrow something that has already been mutably
45            // borrowed.
46        }
47    }
48
49    for (ty, ident) in info.generic_consumers() {
50        mut_fields.push(quote! { #ident: ::core::marker::PhantomData<#ty> });
51        mut_field_assignments.push(quote! { #ident: ::core::marker::PhantomData });
52    }
53
54    let mut new_generic_params = info.generic_params().clone();
55    for lt in &lifetime_idents {
56        let lt = Lifetime::new(&format!("'{}", lt), Span::call_site());
57        new_generic_params.insert(0, syn::parse_quote! { #lt });
58    }
59    new_generic_params.insert(0, syn::parse_quote! { 'outer_borrow });
60    let mut new_generic_args = info.generic_arguments();
61    let mut lifetimes = Vec::new();
62    for lt in &lifetime_idents {
63        let lt = Lifetime::new(&format!("'{}", lt), Span::call_site());
64        lifetimes.push(lt.clone());
65        new_generic_args.insert(0, quote! { #lt });
66    }
67    new_generic_args.insert(0, quote! { 'outer_borrow });
68
69    let mut_struct_documentation = format!(
70        concat!(
71            "A struct for holding mutable references to all ",
72            "[tail fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) in an instance of ",
73            "[`{0}`]({0})."
74        ),
75        info.ident.to_string()
76    );
77    let fake_lifetime = Lifetime::new(&format!("'{}", info.fake_lifetime()), Span::call_site());
78    let mut generic_where = if let Some(clause) = &info.generics.where_clause {
79        clause.clone()
80    } else {
81        syn::parse_quote! { where }
82    };
83    for lt in &lifetime_idents {
84        let lt = Lifetime::new(&format!("'{}", lt), Span::call_site());
85        let extra: WhereClause = syn::parse_quote! { where #fake_lifetime: #lt };
86        generic_where
87            .predicates
88            .extend(extra.predicates.into_iter());
89    }
90    for idents in lifetime_idents.windows(2) {
91        let lt = Lifetime::new(&format!("'{}", idents[1]), Span::call_site());
92        let outlives = Lifetime::new(&format!("'{}", idents[0]), Span::call_site());
93        let extra: WhereClause = syn::parse_quote! { where #lt: #outlives };
94        generic_where
95            .predicates
96            .extend(extra.predicates.into_iter());
97    }
98    let struct_defs = quote! {
99        #[doc=#mut_struct_documentation]
100        #visibility struct BorrowedMutFields <#new_generic_params> #generic_where { #(#mut_fields),* }
101    };
102    let borrowed_mut_fields_type = quote! { BorrowedMutFields<#(#new_generic_args),*> };
103    let mut_documentation = concat!(
104        "This method provides mutable references to all ",
105        "[tail fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions).",
106    );
107    let mut_documentation = if !options.do_no_doc {
108        quote! {
109            #[doc=#mut_documentation]
110        }
111    } else {
112        quote! { #[doc(hidden)] }
113    };
114    let fn_defs = quote! {
115        #mut_documentation
116        #[inline(always)]
117        #visibility fn with_mut <'outer_borrow, ReturnType>(
118            &'outer_borrow mut self,
119            user: impl for<#(#lifetimes),*> ::core::ops::FnOnce(#borrowed_mut_fields_type) -> ReturnType
120        ) -> ReturnType {
121            let this = unsafe { self.actual_data.assume_init_mut() };
122            user(BorrowedMutFields {
123                #(#mut_field_assignments),*
124            })
125        }
126    };
127    Ok((struct_defs, fn_defs))
128}