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}