ouroboros_macro/generate/
with_mut.rs

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
use crate::{
    info_structures::{FieldType, Options, StructInfo},
    utils::{replace_this_with_lifetime, uses_this_lifetime},
};
use itertools::Itertools;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{Error, Lifetime, WhereClause};

pub fn make_with_all_mut_function(
    info: &StructInfo,
    options: Options,
) -> Result<(TokenStream, TokenStream), Error> {
    let visibility = if options.do_pub_extras {
        info.vis.clone()
    } else {
        syn::parse_quote! { pub(super) }
    };
    let mut mut_fields = Vec::new();
    let mut mut_field_assignments = Vec::new();
    let mut lifetime_idents = Vec::new();
    // I don't think the reverse is necessary but it does make the expanded code more uniform.
    for field in info.fields.iter().rev() {
        let field_name = &field.name;
        let field_type = &field.typ;
        let lifetime = format_ident!("this{}", lifetime_idents.len());
        if uses_this_lifetime(quote! { #field_type }) || field.field_type == FieldType::Borrowed  {
            lifetime_idents.push(lifetime.clone());
        }
        let field_type = replace_this_with_lifetime(quote! { #field_type }, lifetime.clone());
        if field.field_type == FieldType::Tail {
            mut_fields.push(quote! { #visibility #field_name: &'outer_borrow mut #field_type });
            mut_field_assignments.push(quote! { #field_name: &mut this.#field_name });
        } else if field.field_type == FieldType::Borrowed {
            let ass = quote! { #field_name: unsafe {
                ::ouroboros::macro_help::change_lifetime(
                    &*this.#field_name
                )
            } };
            let lt = Lifetime::new(&format!("'{}", lifetime), Span::call_site());
            mut_fields.push(quote! { #visibility #field_name: &#lt #field_type });
            mut_field_assignments.push(ass);
        } else if field.field_type == FieldType::BorrowedMut {
            // Add nothing because we cannot borrow something that has already been mutably
            // borrowed.
        }
    }

    for (ty, ident) in info.generic_consumers() {
        mut_fields.push(quote! { #ident: ::core::marker::PhantomData<#ty> });
        mut_field_assignments.push(quote! { #ident: ::core::marker::PhantomData });
    }

    let mut new_generic_params = info.generic_params().clone();
    for lt in &lifetime_idents {
        let lt = Lifetime::new(&format!("'{}", lt), Span::call_site());
        new_generic_params.insert(0, syn::parse_quote! { #lt });
    }
    new_generic_params.insert(0, syn::parse_quote! { 'outer_borrow });
    let mut new_generic_args = info.generic_arguments();
    let mut lifetimes = Vec::new();
    for lt in &lifetime_idents {
        let lt = Lifetime::new(&format!("'{}", lt), Span::call_site());
        lifetimes.push(lt.clone());
        new_generic_args.insert(0, quote! { #lt });
    }
    new_generic_args.insert(0, quote! { 'outer_borrow });

    let mut_struct_documentation = format!(
        concat!(
            "A struct for holding mutable references to all ",
            "[tail fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions) in an instance of ",
            "[`{0}`]({0})."
        ),
        info.ident.to_string()
    );
    let fake_lifetime = Lifetime::new(&format!("'{}", info.fake_lifetime()), Span::call_site());
    let mut generic_where = if let Some(clause) = &info.generics.where_clause {
        clause.clone()
    } else {
        syn::parse_quote! { where }
    };
    for lt in &lifetime_idents {
        let lt = Lifetime::new(&format!("'{}", lt), Span::call_site());
        let extra: WhereClause = syn::parse_quote! { where #fake_lifetime: #lt };
        generic_where
            .predicates
            .extend(extra.predicates.into_iter());
    }
    for (outlives, lt) in lifetime_idents.iter().tuple_windows() {
        let lt = Lifetime::new(&format!("'{}", lt), Span::call_site());
        let outlives = Lifetime::new(&format!("'{}", outlives), Span::call_site());
        let extra: WhereClause = syn::parse_quote! { where #lt: #outlives };
        generic_where
            .predicates
            .extend(extra.predicates.into_iter());
    }
    let struct_defs = quote! {
        #[doc=#mut_struct_documentation]
        #visibility struct BorrowedMutFields <#new_generic_params> #generic_where { #(#mut_fields),* }
    };
    let borrowed_mut_fields_type = quote! { BorrowedMutFields<#(#new_generic_args),*> };
    let mut_documentation = concat!(
        "This method provides mutable references to all ",
        "[tail fields](https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html#definitions).",
    );
    let mut_documentation = if !options.do_no_doc {
        quote! {
            #[doc=#mut_documentation]
        }
    } else {
        quote! { #[doc(hidden)] }
    };
    let fn_defs = quote! {
        #mut_documentation
        #[inline(always)]
        #visibility fn with_mut <'outer_borrow, ReturnType>(
            &'outer_borrow mut self,
            user: impl for<#(#lifetimes),*> ::core::ops::FnOnce(#borrowed_mut_fields_type) -> ReturnType
        ) -> ReturnType {
            let this = unsafe { self.actual_data.assume_init_mut() };
            user(BorrowedMutFields {
                #(#mut_field_assignments),*
            })
        }
    };
    Ok((struct_defs, fn_defs))
}