]> git.proxmox.com Git - rustc.git/blame - vendor/derive-new/src/lib.rs
New upstream version 1.72.1+dfsg1
[rustc.git] / vendor / derive-new / src / lib.rs
CommitLineData
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
111extern crate proc_macro;
112extern crate proc_macro2;
113#[macro_use]
114extern crate quote;
115extern crate syn;
116
117macro_rules! my_quote {
118 ($($t:tt)*) => (quote_spanned!(proc_macro2::Span::call_site() => $($t)*))
119}
120
121fn path_to_string(path: &syn::Path) -> String {
122 path.segments.iter().map(|s| s.ident.to_string()).collect::<Vec<String>>().join("::")
123}
124
125use proc_macro::TokenStream;
fe692bf9 126use proc_macro2::TokenStream as TokenStream2;
f20569fa
XL
127use syn::Token;
128
129#[proc_macro_derive(new, attributes(new))]
130pub 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
140fn 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
152fn 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
165fn 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
219fn 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
248enum FieldAttr {
249 Default,
250 Value(proc_macro2::TokenStream),
251}
252
253impl 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
336struct FieldExt<'a> {
337 ty: &'a syn::Type,
338 attr: Option<FieldAttr>,
339 ident: syn::Ident,
340 named: bool,
341}
342
343impl<'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
407fn 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
413fn 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
424fn 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]
455fn 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}