turbo_tasks_macros_shared/
expand.rs

1use proc_macro2::{Ident, TokenStream};
2use quote::quote;
3use syn::{
4    Data, DataEnum, DataStruct, DeriveInput, Field, Fields, FieldsNamed, FieldsUnnamed,
5    spanned::Spanned,
6};
7
8/// Handles the expansion of a struct/enum into a match statement that accesses
9/// every field for procedural code generation.
10///
11/// Requires several Fn helpers which perform expand different structures:
12///
13/// - `expand_named` handles the expansion of a struct or enum variant with named fields (e.g.
14///   `struct Foo { bar: u32 }`, `Foo::Bar { baz: u32 }`).
15/// - `expand_unnamed` handles the expansion of a struct or enum variant with unnamed fields (e.g.
16///   `struct Foo(u32)`, `Foo::Bar(u32)`).
17/// - `expand_unit` handles the expansion of a unit struct or enum (e.g. `struct Foo;`, `Foo::Bar`).
18///
19/// These helpers should themselves call [generate_destructuring] to generate
20/// the destructure necessary to access the fields of the value.
21pub fn match_expansion<
22    EN: Fn(TokenStream, &FieldsNamed) -> (TokenStream, TokenStream),
23    EU: Fn(TokenStream, &FieldsUnnamed) -> (TokenStream, TokenStream),
24    U: Fn(TokenStream) -> TokenStream,
25>(
26    derive_input: &DeriveInput,
27    expand_named: &EN,
28    expand_unnamed: &EU,
29    expand_unit: &U,
30) -> TokenStream {
31    let ident = &derive_input.ident;
32    let expand_unit = move |ident| (TokenStream::new(), expand_unit(ident));
33    match &derive_input.data {
34        Data::Enum(DataEnum { variants, .. }) => {
35            let (idents, (variants_fields_capture, expansion)): (Vec<_>, (Vec<_>, Vec<_>)) =
36                variants
37                    .iter()
38                    .map(|variant| {
39                        let variants_idents = &variant.ident;
40                        let ident = quote! { #ident::#variants_idents };
41                        (
42                            ident.clone(),
43                            expand_fields(
44                                ident,
45                                &variant.fields,
46                                expand_named,
47                                expand_unnamed,
48                                expand_unit,
49                            ),
50                        )
51                    })
52                    .unzip();
53
54            if idents.is_empty() {
55                let (_, expansion) = expand_unit(quote! { #ident });
56                quote! {
57                    #expansion
58                }
59            } else {
60                quote! {
61                    match self {
62                        #(
63                            #idents #variants_fields_capture => #expansion,
64                        )*
65                    }
66                }
67            }
68        }
69        Data::Struct(DataStruct { fields, .. }) => {
70            let (captures, expansion) = expand_fields(
71                quote! { #ident },
72                fields,
73                expand_named,
74                expand_unnamed,
75                expand_unit,
76            );
77
78            if fields.is_empty() {
79                assert!(captures.is_empty());
80                // a match expression here doesn't make sense as there's no fields to capture,
81                // just pass through the inner expression.
82                expansion
83            } else {
84                match fields {
85                    Fields::Named(_) | Fields::Unnamed(_) => quote! {
86                        match self {
87                            #ident #captures => #expansion
88                        }
89                    },
90                    Fields::Unit => unreachable!(),
91                }
92            }
93        }
94        _ => {
95            derive_input
96                .span()
97                .unwrap()
98                .error("unsupported syntax")
99                .emit();
100
101            quote! {}
102        }
103    }
104}
105
106/// Formats the fields of any structure or enum variant.
107///
108/// Empty lists of named or unnamed fields are treated as unit structs, as they
109/// are semantically identical, and the `expand_unit` codepath can usually
110/// generate better code.
111pub fn expand_fields<
112    'ident,
113    'fields,
114    EN: Fn(TokenStream, &'fields FieldsNamed) -> R,
115    EU: Fn(TokenStream, &'fields FieldsUnnamed) -> R,
116    U: Fn(TokenStream) -> R,
117    R,
118>(
119    ident: TokenStream,
120    fields: &'fields Fields,
121    expand_named: EN,
122    expand_unnamed: EU,
123    expand_unit: U,
124) -> R {
125    if fields.is_empty() {
126        // any empty struct (regardless of the syntax used during declaration) is
127        // equivalent to a unit struct
128        return expand_unit(ident);
129    }
130    match fields {
131        Fields::Named(named) => expand_named(ident, named),
132        Fields::Unnamed(unnamed) => expand_unnamed(ident, unnamed),
133        Fields::Unit => unreachable!(),
134    }
135}
136
137/// Generates a match arm destructuring pattern for the given fields.
138///
139/// If no `filter_field` function is provided, all fields are included in the
140/// pattern. If a `filter_field` function is provided, only fields for which
141/// the function returns `true` are included in the pattern. If any field is
142/// ignored, a wildcard pattern is added to the end of the pattern, making it
143/// non-exhaustive.
144///
145/// Returns both the capture pattern token stream and the name of the bound
146/// identifiers corresponding to the input fields.
147pub fn generate_destructuring<'a, I: Fn(&Field) -> bool>(
148    fields: impl ExactSizeIterator<Item = &'a Field>,
149    filter_field: &I,
150) -> (TokenStream, Vec<TokenStream>) {
151    let fields_len = fields.len();
152    let (captures, fields_idents): (Vec<_>, Vec<_>) = fields
153        // We need to enumerate first to capture the indexes of the fields before filtering has
154        // changed them.
155        .enumerate()
156        .filter(|(_i, field)| filter_field(field))
157        .map(|(i, field)| match &field.ident {
158            Some(ident) => (quote! { #ident }, quote! { #ident }),
159            None => {
160                let ident = Ident::new(&format!("field_{i}"), field.span());
161                let index = syn::Index::from(i);
162                (quote! { #index: #ident }, quote! { #ident })
163            }
164        })
165        .unzip();
166    // Only add the wildcard pattern if we're ignoring some fields.
167    let wildcard = if fields_idents.len() != fields_len {
168        quote! { .. }
169    } else {
170        quote! {}
171    };
172    (
173        quote! {
174            { #(#captures,)* #wildcard }
175        },
176        fields_idents,
177    )
178}
179
180/// Generates an exhaustive match arm destructuring pattern for the given
181/// fields. This is equivalent to calling [`generate_destructuring`] with a
182/// `filter_field` function that always returns `true`.
183///
184/// Returns both the capture pattern token stream and the name of the bound
185/// identifiers corresponding to the input fields.
186pub fn generate_exhaustive_destructuring<'a>(
187    fields: impl ExactSizeIterator<Item = &'a Field>,
188) -> (TokenStream, Vec<TokenStream>) {
189    generate_destructuring(fields, &|_| true)
190}