xref: /third_party/rust/crates/cxx/macro/src/derive.rs (revision 33d722a9)
1use crate::syntax::{derive, Enum, Struct, Trait};
2use proc_macro2::{Ident, Span, TokenStream};
3use quote::{quote, quote_spanned, ToTokens};
4
5pub use crate::syntax::derive::*;
6
7pub fn expand_struct(strct: &Struct, actual_derives: &mut Option<TokenStream>) -> TokenStream {
8    let mut expanded = TokenStream::new();
9    let mut traits = Vec::new();
10
11    for derive in &strct.derives {
12        let span = derive.span;
13        match derive.what {
14            Trait::Copy => expanded.extend(struct_copy(strct, span)),
15            Trait::Clone => expanded.extend(struct_clone(strct, span)),
16            Trait::Debug => expanded.extend(struct_debug(strct, span)),
17            Trait::Default => expanded.extend(struct_default(strct, span)),
18            Trait::Eq => traits.push(quote_spanned!(span=> ::cxx::core::cmp::Eq)),
19            Trait::ExternType => unreachable!(),
20            Trait::Hash => traits.push(quote_spanned!(span=> ::cxx::core::hash::Hash)),
21            Trait::Ord => expanded.extend(struct_ord(strct, span)),
22            Trait::PartialEq => traits.push(quote_spanned!(span=> ::cxx::core::cmp::PartialEq)),
23            Trait::PartialOrd => expanded.extend(struct_partial_ord(strct, span)),
24            Trait::Serialize => traits.push(quote_spanned!(span=> ::serde::Serialize)),
25            Trait::Deserialize => traits.push(quote_spanned!(span=> ::serde::Deserialize)),
26        }
27    }
28
29    if traits.is_empty() {
30        *actual_derives = None;
31    } else {
32        *actual_derives = Some(quote!(#[derive(#(#traits),*)]));
33    }
34
35    expanded
36}
37
38pub fn expand_enum(enm: &Enum, actual_derives: &mut Option<TokenStream>) -> TokenStream {
39    let mut expanded = TokenStream::new();
40    let mut traits = Vec::new();
41    let mut has_copy = false;
42    let mut has_clone = false;
43    let mut has_eq = false;
44    let mut has_partial_eq = false;
45
46    for derive in &enm.derives {
47        let span = derive.span;
48        match derive.what {
49            Trait::Copy => {
50                expanded.extend(enum_copy(enm, span));
51                has_copy = true;
52            }
53            Trait::Clone => {
54                expanded.extend(enum_clone(enm, span));
55                has_clone = true;
56            }
57            Trait::Debug => expanded.extend(enum_debug(enm, span)),
58            Trait::Default => unreachable!(),
59            Trait::Eq => {
60                traits.push(quote_spanned!(span=> ::cxx::core::cmp::Eq));
61                has_eq = true;
62            }
63            Trait::ExternType => unreachable!(),
64            Trait::Hash => traits.push(quote_spanned!(span=> ::cxx::core::hash::Hash)),
65            Trait::Ord => expanded.extend(enum_ord(enm, span)),
66            Trait::PartialEq => {
67                traits.push(quote_spanned!(span=> ::cxx::core::cmp::PartialEq));
68                has_partial_eq = true;
69            }
70            Trait::PartialOrd => expanded.extend(enum_partial_ord(enm, span)),
71            Trait::Serialize => traits.push(quote_spanned!(span=> ::serde::Serialize)),
72            Trait::Deserialize => traits.push(quote_spanned!(span=> ::serde::Deserialize)),
73        }
74    }
75
76    let span = enm.name.rust.span();
77    if !has_copy {
78        expanded.extend(enum_copy(enm, span));
79    }
80    if !has_clone {
81        expanded.extend(enum_clone(enm, span));
82    }
83    if !has_eq {
84        // Required to be derived in order for the enum's "variants" to be
85        // usable in patterns.
86        traits.push(quote!(::cxx::core::cmp::Eq));
87    }
88    if !has_partial_eq {
89        traits.push(quote!(::cxx::core::cmp::PartialEq));
90    }
91
92    *actual_derives = Some(quote!(#[derive(#(#traits),*)]));
93
94    expanded
95}
96
97fn struct_copy(strct: &Struct, span: Span) -> TokenStream {
98    let ident = &strct.name.rust;
99    let generics = &strct.generics;
100
101    quote_spanned! {span=>
102        impl #generics ::cxx::core::marker::Copy for #ident #generics {}
103    }
104}
105
106fn struct_clone(strct: &Struct, span: Span) -> TokenStream {
107    let ident = &strct.name.rust;
108    let generics = &strct.generics;
109
110    let body = if derive::contains(&strct.derives, Trait::Copy) {
111        quote!(*self)
112    } else {
113        let fields = strct.fields.iter().map(|field| &field.name.rust);
114        let values = strct.fields.iter().map(|field| {
115            let ident = &field.name.rust;
116            let ty = field.ty.to_token_stream();
117            let span = ty.into_iter().last().unwrap().span();
118            quote_spanned!(span=> &self.#ident)
119        });
120        quote_spanned!(span=> #ident {
121            #(#fields: ::cxx::core::clone::Clone::clone(#values),)*
122        })
123    };
124
125    quote_spanned! {span=>
126        #[allow(clippy::expl_impl_clone_on_copy)]
127        impl #generics ::cxx::core::clone::Clone for #ident #generics {
128            fn clone(&self) -> Self {
129                #body
130            }
131        }
132    }
133}
134
135fn struct_debug(strct: &Struct, span: Span) -> TokenStream {
136    let ident = &strct.name.rust;
137    let generics = &strct.generics;
138    let struct_name = ident.to_string();
139    let fields = strct.fields.iter().map(|field| &field.name.rust);
140    let field_names = fields.clone().map(Ident::to_string);
141
142    quote_spanned! {span=>
143        impl #generics ::cxx::core::fmt::Debug for #ident #generics {
144            fn fmt(&self, formatter: &mut ::cxx::core::fmt::Formatter<'_>) -> ::cxx::core::fmt::Result {
145                formatter.debug_struct(#struct_name)
146                    #(.field(#field_names, &self.#fields))*
147                    .finish()
148            }
149        }
150    }
151}
152
153fn struct_default(strct: &Struct, span: Span) -> TokenStream {
154    let ident = &strct.name.rust;
155    let generics = &strct.generics;
156    let fields = strct.fields.iter().map(|field| &field.name.rust);
157
158    quote_spanned! {span=>
159        #[allow(clippy::derivable_impls)] // different spans than the derived impl
160        impl #generics ::cxx::core::default::Default for #ident #generics {
161            fn default() -> Self {
162                #ident {
163                    #(
164                        #fields: ::cxx::core::default::Default::default(),
165                    )*
166                }
167            }
168        }
169    }
170}
171
172fn struct_ord(strct: &Struct, span: Span) -> TokenStream {
173    let ident = &strct.name.rust;
174    let generics = &strct.generics;
175    let fields = strct.fields.iter().map(|field| &field.name.rust);
176
177    quote_spanned! {span=>
178        impl #generics ::cxx::core::cmp::Ord for #ident #generics {
179            fn cmp(&self, other: &Self) -> ::cxx::core::cmp::Ordering {
180                #(
181                    match ::cxx::core::cmp::Ord::cmp(&self.#fields, &other.#fields) {
182                        ::cxx::core::cmp::Ordering::Equal => {}
183                        ordering => return ordering,
184                    }
185                )*
186                ::cxx::core::cmp::Ordering::Equal
187            }
188        }
189    }
190}
191
192fn struct_partial_ord(strct: &Struct, span: Span) -> TokenStream {
193    let ident = &strct.name.rust;
194    let generics = &strct.generics;
195
196    let body = if derive::contains(&strct.derives, Trait::Ord) {
197        quote! {
198            ::cxx::core::option::Option::Some(::cxx::core::cmp::Ord::cmp(self, other))
199        }
200    } else {
201        let fields = strct.fields.iter().map(|field| &field.name.rust);
202        quote! {
203            #(
204                match ::cxx::core::cmp::PartialOrd::partial_cmp(&self.#fields, &other.#fields) {
205                    ::cxx::core::option::Option::Some(::cxx::core::cmp::Ordering::Equal) => {}
206                    ordering => return ordering,
207                }
208            )*
209            ::cxx::core::option::Option::Some(::cxx::core::cmp::Ordering::Equal)
210        }
211    };
212
213    quote_spanned! {span=>
214        impl #generics ::cxx::core::cmp::PartialOrd for #ident #generics {
215            fn partial_cmp(&self, other: &Self) -> ::cxx::core::option::Option<::cxx::core::cmp::Ordering> {
216                #body
217            }
218        }
219    }
220}
221
222fn enum_copy(enm: &Enum, span: Span) -> TokenStream {
223    let ident = &enm.name.rust;
224
225    quote_spanned! {span=>
226        impl ::cxx::core::marker::Copy for #ident {}
227    }
228}
229
230fn enum_clone(enm: &Enum, span: Span) -> TokenStream {
231    let ident = &enm.name.rust;
232
233    quote_spanned! {span=>
234        #[allow(clippy::expl_impl_clone_on_copy)]
235        impl ::cxx::core::clone::Clone for #ident {
236            fn clone(&self) -> Self {
237                *self
238            }
239        }
240    }
241}
242
243fn enum_debug(enm: &Enum, span: Span) -> TokenStream {
244    let ident = &enm.name.rust;
245    let variants = enm.variants.iter().map(|variant| {
246        let variant = &variant.name.rust;
247        let name = variant.to_string();
248        quote_spanned! {span=>
249            #ident::#variant => formatter.write_str(#name),
250        }
251    });
252    let fallback = format!("{}({{}})", ident);
253
254    quote_spanned! {span=>
255        impl ::cxx::core::fmt::Debug for #ident {
256            fn fmt(&self, formatter: &mut ::cxx::core::fmt::Formatter<'_>) -> ::cxx::core::fmt::Result {
257                match *self {
258                    #(#variants)*
259                    _ => ::cxx::core::write!(formatter, #fallback, self.repr),
260                }
261            }
262        }
263    }
264}
265
266fn enum_ord(enm: &Enum, span: Span) -> TokenStream {
267    let ident = &enm.name.rust;
268
269    quote_spanned! {span=>
270        impl ::cxx::core::cmp::Ord for #ident {
271            fn cmp(&self, other: &Self) -> ::cxx::core::cmp::Ordering {
272                ::cxx::core::cmp::Ord::cmp(&self.repr, &other.repr)
273            }
274        }
275    }
276}
277
278fn enum_partial_ord(enm: &Enum, span: Span) -> TokenStream {
279    let ident = &enm.name.rust;
280
281    quote_spanned! {span=>
282        impl ::cxx::core::cmp::PartialOrd for #ident {
283            fn partial_cmp(&self, other: &Self) -> ::cxx::core::option::Option<::cxx::core::cmp::Ordering> {
284                ::cxx::core::cmp::PartialOrd::partial_cmp(&self.repr, &other.repr)
285            }
286        }
287    }
288}
289