]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //!# A custom derive implementation for `#[derive(new)]` |
2 | //! | |
3 | //!A `derive(new)` attribute creates a `new` constructor function for the annotated | |
4 | //!type. That function takes an argument for each field in the type giving a | |
5 | //!trivial constructor. This is useful since as your type evolves you can make the | |
6 | //!constructor non-trivial (and add or remove fields) without changing client code | |
7 | //!(i.e., without breaking backwards compatibility). It is also the most succinct | |
8 | //!way to initialise a struct or an enum. | |
9 | //! | |
10 | //!Implementation uses macros 1.1 custom derive (which works in stable Rust from | |
11 | //!1.15 onwards). | |
12 | //! | |
13 | //!## Examples | |
14 | //! | |
15 | //!Cargo.toml: | |
16 | //! | |
17 | //!```toml | |
18 | //![dependencies] | |
19 | //!derive-new = "0.5" | |
20 | //!``` | |
21 | //! | |
22 | //!Include the macro: | |
23 | //! | |
24 | //!```rust | |
25 | //!#[macro_use] | |
26 | //!extern crate derive_new; | |
27 | //!fn main() {} | |
28 | //!``` | |
29 | //! | |
30 | //!Generating constructor for a simple struct: | |
31 | //! | |
32 | //!```rust | |
33 | //!#[macro_use] | |
34 | //!extern crate derive_new; | |
35 | //!#[derive(new)] | |
36 | //!struct Bar { | |
37 | //! a: i32, | |
38 | //! b: String, | |
39 | //!} | |
40 | //! | |
41 | //!fn main() { | |
42 | //! let _ = Bar::new(42, "Hello".to_owned()); | |
43 | //!} | |
44 | //!``` | |
45 | //! | |
46 | //!Default values can be specified either via `#[new(default)]` attribute which removes | |
47 | //!the argument from the constructor and populates the field with `Default::default()`, | |
48 | //!or via `#[new(value = "..")]` which initializes the field with a given expression: | |
49 | //! | |
50 | //!```rust | |
51 | //!#[macro_use] | |
52 | //!extern crate derive_new; | |
53 | //!#[derive(new)] | |
54 | //!struct Foo { | |
55 | //! x: bool, | |
56 | //! #[new(value = "42")] | |
57 | //! y: i32, | |
58 | //! #[new(default)] | |
59 | //! z: Vec<String>, | |
60 | //!} | |
61 | //! | |
62 | //!fn main() { | |
63 | //! let _ = Foo::new(true); | |
64 | //!} | |
65 | //!``` | |
66 | //! | |
67 | //!Generic types are supported; in particular, `PhantomData<T>` fields will be not | |
68 | //!included in the argument list and will be intialized automatically: | |
69 | //! | |
70 | //!```rust | |
71 | //!#[macro_use] | |
72 | //!extern crate derive_new; | |
73 | //!use std::marker::PhantomData; | |
74 | //! | |
75 | //!#[derive(new)] | |
76 | //!struct Generic<'a, T: Default, P> { | |
77 | //! x: &'a str, | |
78 | //! y: PhantomData<P>, | |
79 | //! #[new(default)] | |
80 | //! z: T, | |
81 | //!} | |
82 | //! | |
83 | //!fn main() { | |
84 | //! let _ = Generic::<i32, u8>::new("Hello"); | |
85 | //!} | |
86 | //!``` | |
87 | //! | |
88 | //!For enums, one constructor method is generated for each variant, with the type | |
89 | //!name being converted to snake case; otherwise, all features supported for | |
90 | //!structs work for enum variants as well: | |
91 | //! | |
92 | //!```rust | |
93 | //!#[macro_use] | |
94 | //!extern crate derive_new; | |
95 | //!#[derive(new)] | |
96 | //!enum Enum { | |
97 | //! FirstVariant, | |
98 | //! SecondVariant(bool, #[new(default)] u8), | |
99 | //! ThirdVariant { x: i32, #[new(value = "vec![1]")] y: Vec<u8> } | |
100 | //!} | |
101 | //! | |
102 | //!fn main() { | |
103 | //! let _ = Enum::new_first_variant(); | |
104 | //! let _ = Enum::new_second_variant(true); | |
105 | //! let _ = Enum::new_third_variant(42); | |
106 | //!} | |
107 | //!``` | |
108 | #![crate_type = "proc-macro"] | |
109 | #![recursion_limit = "192"] | |
110 | ||
111 | extern crate proc_macro; | |
112 | extern crate proc_macro2; | |
113 | #[macro_use] | |
114 | extern crate quote; | |
115 | extern crate syn; | |
116 | ||
117 | macro_rules! my_quote { | |
118 | ($($t:tt)*) => (quote_spanned!(proc_macro2::Span::call_site() => $($t)*)) | |
119 | } | |
120 | ||
121 | fn path_to_string(path: &syn::Path) -> String { | |
122 | path.segments.iter().map(|s| s.ident.to_string()).collect::<Vec<String>>().join("::") | |
123 | } | |
124 | ||
125 | use proc_macro::TokenStream; | |
fe692bf9 | 126 | use proc_macro2::TokenStream as TokenStream2; |
f20569fa XL |
127 | use syn::Token; |
128 | ||
129 | #[proc_macro_derive(new, attributes(new))] | |
130 | pub fn derive(input: TokenStream) -> TokenStream { | |
131 | let ast: syn::DeriveInput = syn::parse(input).expect("Couldn't parse item"); | |
132 | let result = match ast.data { | |
133 | syn::Data::Enum(ref e) => new_for_enum(&ast, e), | |
134 | syn::Data::Struct(ref s) => new_for_struct(&ast, &s.fields, None), | |
135 | syn::Data::Union(_) => panic!("doesn't work with unions yet"), | |
136 | }; | |
137 | result.into() | |
138 | } | |
139 | ||
140 | fn new_for_struct( | |
141 | ast: &syn::DeriveInput, | |
142 | fields: &syn::Fields, | |
143 | variant: Option<&syn::Ident>, | |
144 | ) -> proc_macro2::TokenStream { | |
145 | match *fields { | |
146 | syn::Fields::Named(ref fields) => new_impl(&ast, Some(&fields.named), true, variant), | |
147 | syn::Fields::Unit => new_impl(&ast, None, false, variant), | |
148 | syn::Fields::Unnamed(ref fields) => new_impl(&ast, Some(&fields.unnamed), false, variant), | |
149 | } | |
150 | } | |
151 | ||
152 | fn new_for_enum(ast: &syn::DeriveInput, data: &syn::DataEnum) -> proc_macro2::TokenStream { | |
153 | if data.variants.is_empty() { | |
154 | panic!("#[derive(new)] cannot be implemented for enums with zero variants"); | |
155 | } | |
156 | let impls = data.variants.iter().map(|v| { | |
157 | if v.discriminant.is_some() { | |
158 | panic!("#[derive(new)] cannot be implemented for enums with discriminants"); | |
159 | } | |
160 | new_for_struct(ast, &v.fields, Some(&v.ident)) | |
161 | }); | |
162 | my_quote!(#(#impls)*) | |
163 | } | |
164 | ||
165 | fn new_impl( | |
166 | ast: &syn::DeriveInput, | |
167 | fields: Option<&syn::punctuated::Punctuated<syn::Field, Token![,]>>, | |
168 | named: bool, | |
169 | variant: Option<&syn::Ident>, | |
170 | ) -> proc_macro2::TokenStream { | |
171 | let name = &ast.ident; | |
172 | let unit = fields.is_none(); | |
173 | let empty = Default::default(); | |
174 | let fields: Vec<_> = fields | |
175 | .unwrap_or(&empty) | |
176 | .iter() | |
177 | .enumerate() | |
178 | .map(|(i, f)| FieldExt::new(f, i, named)) | |
179 | .collect(); | |
180 | let args = fields.iter().filter(|f| f.needs_arg()).map(|f| f.as_arg()); | |
181 | let inits = fields.iter().map(|f| f.as_init()); | |
182 | let inits = if unit { | |
183 | my_quote!() | |
184 | } else if named { | |
185 | my_quote![{ #(#inits),* }] | |
186 | } else { | |
187 | my_quote![( #(#inits),* )] | |
188 | }; | |
189 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); | |
190 | let (mut new, qual, doc) = match variant { | |
191 | None => ( | |
192 | syn::Ident::new("new", proc_macro2::Span::call_site()), | |
193 | my_quote!(), | |
194 | format!("Constructs a new `{}`.", name), | |
195 | ), | |
196 | Some(ref variant) => ( | |
197 | syn::Ident::new( | |
198 | &format!("new_{}", to_snake_case(&variant.to_string())), | |
199 | proc_macro2::Span::call_site(), | |
200 | ), | |
201 | my_quote!(::#variant), | |
202 | format!("Constructs a new `{}::{}`.", name, variant), | |
203 | ), | |
204 | }; | |
205 | new.set_span(proc_macro2::Span::call_site()); | |
206 | let lint_attrs = collect_parent_lint_attrs(&ast.attrs); | |
207 | let lint_attrs = my_quote![#(#lint_attrs),*]; | |
208 | my_quote! { | |
209 | impl #impl_generics #name #ty_generics #where_clause { | |
210 | #[doc = #doc] | |
211 | #lint_attrs | |
212 | pub fn #new(#(#args),*) -> Self { | |
213 | #name #qual #inits | |
214 | } | |
215 | } | |
216 | } | |
217 | } | |
218 | ||
219 | fn collect_parent_lint_attrs(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> { | |
220 | fn is_lint(item: &syn::Meta) -> bool { | |
221 | if let syn::Meta::List(ref l) = *item { | |
222 | let path = &l.path; | |
223 | return path.is_ident("allow") || path.is_ident("deny") || path.is_ident("forbid") || path.is_ident("warn") | |
224 | } | |
225 | false | |
226 | } | |
227 | ||
228 | fn is_cfg_attr_lint(item: &syn::Meta) -> bool { | |
229 | if let syn::Meta::List(ref l) = *item { | |
230 | if l.path.is_ident("cfg_attr") && l.nested.len() == 2 { | |
231 | if let syn::NestedMeta::Meta(ref item) = l.nested[1] { | |
232 | return is_lint(item); | |
233 | } | |
234 | } | |
235 | } | |
236 | false | |
237 | } | |
238 | ||
239 | attrs | |
240 | .iter() | |
241 | .filter_map(|a| a.parse_meta().ok().map(|m| (m, a))) | |
242 | .filter(|&(ref m, _)| is_lint(m) || is_cfg_attr_lint(m)) | |
243 | .map(|p| p.1) | |
244 | .cloned() | |
245 | .collect() | |
246 | } | |
247 | ||
248 | enum FieldAttr { | |
249 | Default, | |
250 | Value(proc_macro2::TokenStream), | |
251 | } | |
252 | ||
253 | impl FieldAttr { | |
254 | pub fn as_tokens(&self) -> proc_macro2::TokenStream { | |
255 | match *self { | |
256 | FieldAttr::Default => { | |
257 | if cfg!(feature = "std") { | |
258 | my_quote!(::std::default::Default::default()) | |
259 | } else { | |
260 | my_quote!(::core::default::Default::default()) | |
261 | } | |
262 | } | |
263 | FieldAttr::Value(ref s) => my_quote!(#s), | |
264 | } | |
265 | } | |
266 | ||
267 | pub fn parse(attrs: &[syn::Attribute]) -> Option<FieldAttr> { | |
268 | use syn::{AttrStyle, Meta, NestedMeta}; | |
269 | ||
270 | let mut result = None; | |
271 | for attr in attrs.iter() { | |
272 | match attr.style { | |
273 | AttrStyle::Outer => {} | |
274 | _ => continue, | |
275 | } | |
276 | let last_attr_path = attr | |
277 | .path | |
278 | .segments | |
279 | .iter() | |
280 | .last() | |
281 | .expect("Expected at least one segment where #[segment[::segment*](..)]"); | |
282 | if (*last_attr_path).ident != "new" { | |
283 | continue; | |
284 | } | |
285 | let meta = match attr.parse_meta() { | |
286 | Ok(meta) => meta, | |
287 | Err(_) => continue, | |
288 | }; | |
289 | let list = match meta { | |
290 | Meta::List(l) => l, | |
291 | _ if meta.path().is_ident("new") => { | |
292 | panic!("Invalid #[new] attribute, expected #[new(..)]") | |
293 | } | |
294 | _ => continue, | |
295 | }; | |
296 | if result.is_some() { | |
297 | panic!("Expected at most one #[new] attribute"); | |
298 | } | |
299 | for item in list.nested.iter() { | |
300 | match *item { | |
301 | NestedMeta::Meta(Meta::Path(ref path)) => { | |
302 | if path.is_ident("default") { | |
303 | result = Some(FieldAttr::Default); | |
304 | } else { | |
305 | panic!("Invalid #[new] attribute: #[new({})]", path_to_string(&path)); | |
306 | } | |
307 | } | |
308 | NestedMeta::Meta(Meta::NameValue(ref kv)) => { | |
309 | if let syn::Lit::Str(ref s) = kv.lit { | |
310 | if kv.path.is_ident("value") { | |
fe692bf9 | 311 | let tokens = lit_str_to_token_stream(s).ok().expect(&format!( |
f20569fa XL |
312 | "Invalid expression in #[new]: `{}`", |
313 | s.value() | |
314 | )); | |
315 | result = Some(FieldAttr::Value(tokens)); | |
316 | } else { | |
317 | panic!("Invalid #[new] attribute: #[new({} = ..)]", path_to_string(&kv.path)); | |
318 | } | |
319 | } else { | |
320 | panic!("Non-string literal value in #[new] attribute"); | |
321 | } | |
322 | } | |
323 | NestedMeta::Meta(Meta::List(ref l)) => { | |
324 | panic!("Invalid #[new] attribute: #[new({}(..))]", path_to_string(&l.path)); | |
325 | } | |
326 | NestedMeta::Lit(_) => { | |
327 | panic!("Invalid #[new] attribute: literal value in #[new(..)]"); | |
328 | } | |
329 | } | |
330 | } | |
331 | } | |
332 | result | |
333 | } | |
334 | } | |
335 | ||
336 | struct FieldExt<'a> { | |
337 | ty: &'a syn::Type, | |
338 | attr: Option<FieldAttr>, | |
339 | ident: syn::Ident, | |
340 | named: bool, | |
341 | } | |
342 | ||
343 | impl<'a> FieldExt<'a> { | |
344 | pub fn new(field: &'a syn::Field, idx: usize, named: bool) -> FieldExt<'a> { | |
345 | FieldExt { | |
346 | ty: &field.ty, | |
347 | attr: FieldAttr::parse(&field.attrs), | |
348 | ident: if named { | |
349 | field.ident.clone().unwrap() | |
350 | } else { | |
351 | syn::Ident::new(&format!("f{}", idx), proc_macro2::Span::call_site()) | |
352 | }, | |
353 | named: named, | |
354 | } | |
355 | } | |
356 | ||
357 | pub fn has_attr(&self) -> bool { | |
358 | self.attr.is_some() | |
359 | } | |
360 | ||
361 | pub fn is_phantom_data(&self) -> bool { | |
362 | match *self.ty { | |
363 | syn::Type::Path(syn::TypePath { | |
364 | qself: None, | |
365 | ref path, | |
366 | }) => path | |
367 | .segments | |
368 | .last() | |
369 | .map(|x| x.ident == "PhantomData") | |
370 | .unwrap_or(false), | |
371 | _ => false, | |
372 | } | |
373 | } | |
374 | ||
375 | pub fn needs_arg(&self) -> bool { | |
376 | !self.has_attr() && !self.is_phantom_data() | |
377 | } | |
378 | ||
379 | pub fn as_arg(&self) -> proc_macro2::TokenStream { | |
380 | let f_name = &self.ident; | |
381 | let ty = &self.ty; | |
382 | my_quote!(#f_name: #ty) | |
383 | } | |
384 | ||
385 | pub fn as_init(&self) -> proc_macro2::TokenStream { | |
386 | let f_name = &self.ident; | |
387 | let init = if self.is_phantom_data() { | |
388 | if cfg!(feature = "std") { | |
389 | my_quote!(::std::marker::PhantomData) | |
390 | } else { | |
391 | my_quote!(::core::marker::PhantomData) | |
392 | } | |
393 | } else { | |
394 | match self.attr { | |
395 | None => my_quote!(#f_name), | |
396 | Some(ref attr) => attr.as_tokens(), | |
397 | } | |
398 | }; | |
399 | if self.named { | |
400 | my_quote!(#f_name: #init) | |
401 | } else { | |
402 | my_quote!(#init) | |
403 | } | |
404 | } | |
405 | } | |
406 | ||
fe692bf9 FG |
407 | fn lit_str_to_token_stream(s: &syn::LitStr) -> Result<TokenStream2, proc_macro2::LexError> { |
408 | let code = s.value(); | |
409 | let ts: TokenStream2 = code.parse()?; | |
410 | Ok(set_ts_span_recursive(ts, &s.span())) | |
411 | } | |
412 | ||
413 | fn set_ts_span_recursive(ts: TokenStream2, span: &proc_macro2::Span) -> TokenStream2 { | |
414 | ts.into_iter().map(|mut tt| { | |
415 | tt.set_span(span.clone()); | |
416 | if let proc_macro2::TokenTree::Group(group) = &mut tt { | |
417 | let stream = set_ts_span_recursive(group.stream(), span); | |
418 | *group = proc_macro2::Group::new(group.delimiter(), stream); | |
419 | } | |
420 | tt | |
421 | }).collect() | |
422 | } | |
423 | ||
f20569fa XL |
424 | fn to_snake_case(s: &str) -> String { |
425 | let (ch, next, mut acc): (Option<char>, Option<char>, String) = | |
426 | s.chars() | |
427 | .fold((None, None, String::new()), |(prev, ch, mut acc), next| { | |
428 | if let Some(ch) = ch { | |
429 | if let Some(prev) = prev { | |
430 | if ch.is_uppercase() { | |
431 | if prev.is_lowercase() | |
432 | || prev.is_numeric() | |
433 | || (prev.is_uppercase() && next.is_lowercase()) | |
434 | { | |
435 | acc.push('_'); | |
436 | } | |
437 | } | |
438 | } | |
439 | acc.extend(ch.to_lowercase()); | |
440 | } | |
441 | (ch, Some(next), acc) | |
442 | }); | |
443 | if let Some(next) = next { | |
444 | if let Some(ch) = ch { | |
445 | if (ch.is_lowercase() || ch.is_numeric()) && next.is_uppercase() { | |
446 | acc.push('_'); | |
447 | } | |
448 | } | |
449 | acc.extend(next.to_lowercase()); | |
450 | } | |
451 | acc | |
452 | } | |
453 | ||
454 | #[test] | |
455 | fn test_to_snake_case() { | |
456 | assert_eq!(to_snake_case(""), ""); | |
457 | assert_eq!(to_snake_case("a"), "a"); | |
458 | assert_eq!(to_snake_case("B"), "b"); | |
459 | assert_eq!(to_snake_case("BC"), "bc"); | |
460 | assert_eq!(to_snake_case("Bc"), "bc"); | |
461 | assert_eq!(to_snake_case("bC"), "b_c"); | |
462 | assert_eq!(to_snake_case("Fred"), "fred"); | |
463 | assert_eq!(to_snake_case("CARGO"), "cargo"); | |
464 | assert_eq!(to_snake_case("_Hello"), "_hello"); | |
465 | assert_eq!(to_snake_case("QuxBaz"), "qux_baz"); | |
466 | assert_eq!(to_snake_case("FreeBSD"), "free_bsd"); | |
467 | assert_eq!(to_snake_case("specialK"), "special_k"); | |
468 | assert_eq!(to_snake_case("hello1World"), "hello1_world"); | |
469 | assert_eq!(to_snake_case("Keep_underscore"), "keep_underscore"); | |
470 | assert_eq!(to_snake_case("ThisISNotADrill"), "this_is_not_a_drill"); | |
471 | } |