cxxbridge_macro/
derive.rs

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