blob: f614add373996c8a2f3ffe937f16853d5d768f2a [file] [log] [blame]
David Tolnaybc047bb2020-11-27 14:30:12 -08001use crate::syntax::{derive, Enum, Struct, Trait};
David Tolnayf84c98b2020-11-27 12:59:42 -08002use proc_macro2::{Ident, Span, TokenStream};
David Tolnayd8ad9702020-11-27 12:43:59 -08003use quote::{quote, quote_spanned, ToTokens};
4
David Tolnayb960ed22020-11-27 14:34:30 -08005pub use crate::syntax::derive::*;
6
David Tolnaya3619e92020-11-27 14:13:15 -08007pub fn expand_struct(strct: &Struct, actual_derives: &mut Option<TokenStream>) -> TokenStream {
David Tolnayd8ad9702020-11-27 12:43:59 -08008 let mut expanded = TokenStream::new();
David Tolnay21466df2020-11-27 14:04:42 -08009 let mut traits = Vec::new();
David Tolnayd8ad9702020-11-27 12:43:59 -080010
David Tolnay2c24e5c2020-11-27 12:47:41 -080011 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)),
David Tolnayf84c98b2020-11-27 12:59:42 -080016 Trait::Debug => expanded.extend(struct_debug(strct, span)),
David Tolnaya6a9e942020-11-27 17:22:35 -080017 Trait::Default => expanded.extend(struct_default(strct, span)),
David Tolnay21466df2020-11-27 14:04:42 -080018 Trait::Eq => traits.push(quote_spanned!(span=> ::std::cmp::Eq)),
David Tolnay7da38202020-11-27 17:36:16 -080019 Trait::Hash => expanded.extend(struct_hash(strct, span)),
David Tolnay577135e2020-11-27 16:23:53 -080020 Trait::Ord => expanded.extend(struct_ord(strct, span)),
David Tolnay21466df2020-11-27 14:04:42 -080021 Trait::PartialEq => traits.push(quote_spanned!(span=> ::std::cmp::PartialEq)),
David Tolnay577135e2020-11-27 16:23:53 -080022 Trait::PartialOrd => expanded.extend(struct_partial_ord(strct, span)),
David Tolnay2c24e5c2020-11-27 12:47:41 -080023 }
24 }
25
David Tolnay21466df2020-11-27 14:04:42 -080026 if traits.is_empty() {
27 *actual_derives = None;
28 } else {
29 *actual_derives = Some(quote!(#[derive(#(#traits),*)]));
30 }
David Tolnaya3619e92020-11-27 14:13:15 -080031
David Tolnay2c24e5c2020-11-27 12:47:41 -080032 expanded
33}
34
David Tolnayb3d7bb12020-11-27 14:11:41 -080035pub fn expand_enum(enm: &Enum, actual_derives: &mut Option<TokenStream>) -> TokenStream {
David Tolnayfbc46692020-11-27 11:33:29 -080036 let mut expanded = TokenStream::new();
David Tolnay21466df2020-11-27 14:04:42 -080037 let mut traits = Vec::new();
David Tolnayfbc46692020-11-27 11:33:29 -080038 let mut has_copy = false;
39 let mut has_clone = false;
David Tolnay21466df2020-11-27 14:04:42 -080040 let mut has_eq = false;
41 let mut has_partial_eq = false;
David Tolnayfbc46692020-11-27 11:33:29 -080042
43 for derive in &enm.derives {
44 let span = derive.span;
45 match derive.what {
46 Trait::Copy => {
47 expanded.extend(enum_copy(enm, span));
48 has_copy = true;
49 }
50 Trait::Clone => {
51 expanded.extend(enum_clone(enm, span));
52 has_clone = true;
53 }
David Tolnayf84c98b2020-11-27 12:59:42 -080054 Trait::Debug => expanded.extend(enum_debug(enm, span)),
David Tolnaya6a9e942020-11-27 17:22:35 -080055 Trait::Default => unreachable!(),
David Tolnay21466df2020-11-27 14:04:42 -080056 Trait::Eq => {
57 traits.push(quote_spanned!(span=> ::std::cmp::Eq));
58 has_eq = true;
59 }
David Tolnay7da38202020-11-27 17:36:16 -080060 Trait::Hash => expanded.extend(enum_hash(enm, span)),
David Tolnay577135e2020-11-27 16:23:53 -080061 Trait::Ord => expanded.extend(enum_ord(enm, span)),
David Tolnay21466df2020-11-27 14:04:42 -080062 Trait::PartialEq => {
63 traits.push(quote_spanned!(span=> ::std::cmp::PartialEq));
64 has_partial_eq = true;
65 }
David Tolnay577135e2020-11-27 16:23:53 -080066 Trait::PartialOrd => expanded.extend(enum_partial_ord(enm, span)),
David Tolnayfbc46692020-11-27 11:33:29 -080067 }
68 }
69
70 let span = enm.name.rust.span();
71 if !has_copy {
72 expanded.extend(enum_copy(enm, span));
73 }
74 if !has_clone {
75 expanded.extend(enum_clone(enm, span));
76 }
David Tolnay21466df2020-11-27 14:04:42 -080077 if !has_eq {
David Tolnayb3d7bb12020-11-27 14:11:41 -080078 // Required to be derived in order for the enum's "variants" to be
79 // usable in patterns.
David Tolnay21466df2020-11-27 14:04:42 -080080 traits.push(quote!(::std::cmp::Eq));
81 }
82 if !has_partial_eq {
83 traits.push(quote!(::std::cmp::PartialEq));
84 }
85
86 *actual_derives = Some(quote!(#[derive(#(#traits),*)]));
David Tolnayb3d7bb12020-11-27 14:11:41 -080087
David Tolnayfbc46692020-11-27 11:33:29 -080088 expanded
89}
90
David Tolnay2c24e5c2020-11-27 12:47:41 -080091fn struct_copy(strct: &Struct, span: Span) -> TokenStream {
92 let ident = &strct.name.rust;
93
94 quote_spanned! {span=>
95 impl ::std::marker::Copy for #ident {}
96 }
97}
98
99fn struct_clone(strct: &Struct, span: Span) -> TokenStream {
100 let ident = &strct.name.rust;
101
David Tolnaybc047bb2020-11-27 14:30:12 -0800102 let body = if derive::contains(&strct.derives, Trait::Copy) {
David Tolnay2c24e5c2020-11-27 12:47:41 -0800103 quote!(*self)
104 } else {
105 let fields = strct.fields.iter().map(|field| &field.ident);
106 let values = strct.fields.iter().map(|field| {
107 let ident = &field.ident;
108 let ty = field.ty.to_token_stream();
109 let span = ty.into_iter().last().unwrap().span();
110 quote_spanned!(span=> &self.#ident)
111 });
112 quote_spanned!(span=> #ident {
113 #(#fields: ::std::clone::Clone::clone(#values),)*
114 })
115 };
116
117 quote_spanned! {span=>
118 impl ::std::clone::Clone for #ident {
119 fn clone(&self) -> Self {
120 #body
David Tolnayd8ad9702020-11-27 12:43:59 -0800121 }
122 }
123 }
David Tolnayd8ad9702020-11-27 12:43:59 -0800124}
David Tolnayfbc46692020-11-27 11:33:29 -0800125
David Tolnayf84c98b2020-11-27 12:59:42 -0800126fn struct_debug(strct: &Struct, span: Span) -> TokenStream {
127 let ident = &strct.name.rust;
128 let struct_name = ident.to_string();
129 let fields = strct.fields.iter().map(|field| &field.ident);
130 let field_names = fields.clone().map(Ident::to_string);
131
132 quote_spanned! {span=>
133 impl ::std::fmt::Debug for #ident {
134 fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
135 formatter.debug_struct(#struct_name)
136 #(.field(#field_names, &self.#fields))*
137 .finish()
138 }
139 }
140 }
141}
142
David Tolnaya6a9e942020-11-27 17:22:35 -0800143fn struct_default(strct: &Struct, span: Span) -> TokenStream {
144 let ident = &strct.name.rust;
145 let fields = strct.fields.iter().map(|field| &field.ident);
146
147 quote_spanned! {span=>
148 impl ::std::default::Default for #ident {
149 fn default() -> Self {
150 #ident {
151 #(
152 #fields: ::std::default::Default::default(),
153 )*
154 }
155 }
156 }
157 }
158}
159
David Tolnay7da38202020-11-27 17:36:16 -0800160fn struct_hash(strct: &Struct, span: Span) -> TokenStream {
161 let ident = &strct.name.rust;
162 let fields = strct.fields.iter().map(|field| &field.ident);
163
164 quote_spanned! {span=>
165 impl ::std::hash::Hash for #ident {
166 fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
167 #(
168 ::std::hash::Hash::hash(&self.#fields, state);
169 )*
170 }
171 }
172 }
173}
174
David Tolnay577135e2020-11-27 16:23:53 -0800175fn struct_ord(strct: &Struct, span: Span) -> TokenStream {
176 let ident = &strct.name.rust;
177 let fields = strct.fields.iter().map(|field| &field.ident);
178
179 quote_spanned! {span=>
180 impl ::std::cmp::Ord for #ident {
181 fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
182 #(
183 match ::std::cmp::Ord::cmp(&self.#fields, &other.#fields) {
184 ::std::cmp::Ordering::Equal => {}
185 ordering => return ordering,
186 }
187 )*
188 ::std::cmp::Ordering::Equal
189 }
190 }
191 }
192}
193
194fn struct_partial_ord(strct: &Struct, span: Span) -> TokenStream {
195 let ident = &strct.name.rust;
196
197 let body = if derive::contains(&strct.derives, Trait::Ord) {
198 quote! {
199 ::std::option::Option::Some(::std::cmp::Ord::cmp(self, other))
200 }
201 } else {
202 let fields = strct.fields.iter().map(|field| &field.ident);
203 quote! {
204 #(
205 match ::std::cmp::PartialOrd::partial_cmp(&self.#fields, &other.#fields) {
206 ::std::option::Option::Some(::std::cmp::Ordering::Equal) => {}
207 ordering => return ordering,
208 }
209 )*
210 ::std::option::Option::Some(::std::cmp::Ordering::Equal)
211 }
212 };
213
214 quote_spanned! {span=>
215 impl ::std::cmp::PartialOrd for #ident {
216 fn partial_cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> {
217 #body
218 }
219 }
220 }
221}
222
David Tolnayfbc46692020-11-27 11:33:29 -0800223fn enum_copy(enm: &Enum, span: Span) -> TokenStream {
224 let ident = &enm.name.rust;
225
226 quote_spanned! {span=>
227 impl ::std::marker::Copy for #ident {}
228 }
229}
230
231fn enum_clone(enm: &Enum, span: Span) -> TokenStream {
232 let ident = &enm.name.rust;
233
234 quote_spanned! {span=>
235 impl ::std::clone::Clone for #ident {
236 fn clone(&self) -> Self {
237 *self
238 }
239 }
240 }
241}
David Tolnayf84c98b2020-11-27 12:59:42 -0800242
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.ident;
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 ::std::fmt::Debug for #ident {
256 fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
257 match *self {
258 #(#variants)*
259 _ => ::std::write!(formatter, #fallback, self.repr),
260 }
261 }
262 }
263 }
264}
David Tolnay577135e2020-11-27 16:23:53 -0800265
David Tolnay7da38202020-11-27 17:36:16 -0800266fn enum_hash(enm: &Enum, span: Span) -> TokenStream {
267 let ident = &enm.name.rust;
268
269 quote_spanned! {span=>
270 impl ::std::hash::Hash for #ident {
271 fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
272 ::std::hash::Hash::hash(&self.repr, state);
273 }
274 }
275 }
276}
277
David Tolnay577135e2020-11-27 16:23:53 -0800278fn enum_ord(enm: &Enum, span: Span) -> TokenStream {
279 let ident = &enm.name.rust;
280
281 quote_spanned! {span=>
282 impl ::std::cmp::Ord for #ident {
283 pub fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
284 ::std::cmp::Ord::cmp(&self.repr, &other.repr)
285 }
286 }
287 }
288}
289
290fn enum_partial_ord(enm: &Enum, span: Span) -> TokenStream {
291 let ident = &enm.name.rust;
292
293 quote_spanned! {span=>
294 impl ::std::cmp::PartialOrd for #ident {
295 pub fn cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> {
296 ::std::cmp::PartialOrd::cmp(&self.repr, &other.repr)
297 }
298 }
299 }
300}