synthez_codegen/
lib.rs

1#![cfg_attr(any(doc, test), doc = include_str!("../README.md"))]
2#![cfg_attr(not(any(doc, test)), doc = env!("CARGO_PKG_NAME"))]
3#![deny(nonstandard_style, rustdoc::all, trivial_casts, trivial_numeric_casts)]
4#![forbid(non_ascii_idents, unsafe_code)]
5#![warn(
6    clippy::absolute_paths,
7    clippy::allow_attributes,
8    clippy::allow_attributes_without_reason,
9    clippy::as_conversions,
10    clippy::as_pointer_underscore,
11    clippy::as_ptr_cast_mut,
12    clippy::assertions_on_result_states,
13    clippy::branches_sharing_code,
14    clippy::cfg_not_test,
15    clippy::clear_with_drain,
16    clippy::clone_on_ref_ptr,
17    clippy::coerce_container_to_any,
18    clippy::collection_is_never_read,
19    clippy::create_dir,
20    clippy::dbg_macro,
21    clippy::debug_assert_with_mut_call,
22    clippy::decimal_literal_representation,
23    clippy::default_union_representation,
24    clippy::derive_partial_eq_without_eq,
25    clippy::doc_include_without_cfg,
26    clippy::empty_drop,
27    clippy::empty_structs_with_brackets,
28    clippy::equatable_if_let,
29    clippy::empty_enum_variants_with_brackets,
30    clippy::exit,
31    clippy::expect_used,
32    clippy::fallible_impl_from,
33    clippy::filetype_is_file,
34    clippy::float_cmp_const,
35    clippy::fn_to_numeric_cast_any,
36    clippy::get_unwrap,
37    clippy::if_then_some_else_none,
38    clippy::imprecise_flops,
39    clippy::infinite_loop,
40    clippy::iter_on_empty_collections,
41    clippy::iter_on_single_items,
42    clippy::iter_over_hash_type,
43    clippy::iter_with_drain,
44    clippy::large_include_file,
45    clippy::large_stack_frames,
46    clippy::let_underscore_untyped,
47    clippy::literal_string_with_formatting_args,
48    clippy::lossy_float_literal,
49    clippy::map_err_ignore,
50    clippy::map_with_unused_argument_over_ranges,
51    clippy::mem_forget,
52    clippy::missing_assert_message,
53    clippy::missing_asserts_for_indexing,
54    clippy::missing_const_for_fn,
55    clippy::missing_docs_in_private_items,
56    clippy::module_name_repetitions,
57    clippy::multiple_inherent_impl,
58    clippy::multiple_unsafe_ops_per_block,
59    clippy::mutex_atomic,
60    clippy::mutex_integer,
61    clippy::needless_collect,
62    clippy::needless_pass_by_ref_mut,
63    clippy::needless_raw_strings,
64    clippy::non_zero_suggestions,
65    clippy::nonstandard_macro_braces,
66    clippy::option_if_let_else,
67    clippy::or_fun_call,
68    clippy::panic_in_result_fn,
69    clippy::partial_pub_fields,
70    clippy::pathbuf_init_then_push,
71    clippy::pedantic,
72    clippy::precedence_bits,
73    clippy::print_stderr,
74    clippy::print_stdout,
75    clippy::pub_without_shorthand,
76    clippy::rc_buffer,
77    clippy::rc_mutex,
78    clippy::read_zero_byte_vec,
79    clippy::redundant_clone,
80    clippy::redundant_test_prefix,
81    clippy::redundant_type_annotations,
82    clippy::renamed_function_params,
83    clippy::ref_patterns,
84    clippy::rest_pat_in_fully_bound_structs,
85    clippy::return_and_then,
86    clippy::same_name_method,
87    clippy::semicolon_inside_block,
88    clippy::set_contains_or_insert,
89    clippy::shadow_unrelated,
90    clippy::significant_drop_in_scrutinee,
91    clippy::significant_drop_tightening,
92    clippy::single_option_map,
93    clippy::str_to_string,
94    clippy::string_add,
95    clippy::string_lit_as_bytes,
96    clippy::string_lit_chars_any,
97    clippy::string_slice,
98    clippy::suboptimal_flops,
99    clippy::suspicious_operation_groupings,
100    clippy::suspicious_xor_used_as_pow,
101    clippy::tests_outside_test_module,
102    clippy::todo,
103    clippy::too_long_first_doc_paragraph,
104    clippy::trailing_empty_array,
105    clippy::transmute_undefined_repr,
106    clippy::trivial_regex,
107    clippy::try_err,
108    clippy::undocumented_unsafe_blocks,
109    clippy::unimplemented,
110    clippy::uninhabited_references,
111    clippy::unnecessary_safety_comment,
112    clippy::unnecessary_safety_doc,
113    clippy::unnecessary_self_imports,
114    clippy::unnecessary_struct_initialization,
115    clippy::unused_peekable,
116    clippy::unused_result_ok,
117    clippy::unused_trait_names,
118    clippy::unwrap_in_result,
119    clippy::unwrap_used,
120    clippy::use_debug,
121    clippy::use_self,
122    clippy::useless_let_if_seq,
123    clippy::verbose_file_reads,
124    clippy::volatile_composites,
125    clippy::while_float,
126    clippy::wildcard_enum_match_arm,
127    ambiguous_negative_literals,
128    closure_returning_async_block,
129    future_incompatible,
130    impl_trait_redundant_captures,
131    let_underscore_drop,
132    macro_use_extern_crate,
133    meta_variable_misuse,
134    missing_copy_implementations,
135    missing_debug_implementations,
136    missing_docs,
137    redundant_lifetimes,
138    rust_2018_idioms,
139    single_use_lifetimes,
140    unit_bindings,
141    unnameable_types,
142    unreachable_pub,
143    unstable_features,
144    unused,
145    variant_size_differences
146)]
147
148// TODO: Remove once tests run without complains about it.
149#[cfg(test)]
150mod for_docs_only {
151    use proc_macro2 as _;
152    use synthez as _;
153}
154
155use proc_macro::TokenStream;
156use synthez_core::codegen;
157
158/// Deriving of [`synthez::ParseAttrs`] along with a [`syn::parse::Parse`]
159/// implementation to parse [`syn::Attribute`]s into a custom defined struct.
160///
161/// # Field requirements
162///
163/// Each field should be wrapped into a [`field::Container`] implementor, which
164/// describes and influences the parsing logic. Use [`Required`]
165/// [`field::Container`] in case your parsing logic demands mandatory specifying
166/// of a value.
167///
168/// Type of the parsed valued (the one contained in a [`field::Container`]) must
169/// implement [`Parse`] and [`Spanned`] (vital for compile-time error
170/// reporting). You may use the [`Spanning`] wrapper in case it doesn't
171/// implement the latest.
172///
173/// # Arguments
174///
175/// ## `ident`, `value`, `map` or `nested` (mandatory)
176///
177/// Defines kind of parsing for a struct field.
178///
179/// ```rust
180/// # use std::collections::{HashMap, HashSet};
181/// #
182/// # use syn::parse_quote;
183/// # use synthez::{ParseAttrs, Spanning};
184/// #
185/// #[derive(Debug, Default, ParseAttrs, PartialEq)]
186/// struct MyAttrs {
187///     /// Will parse only `#[my_attr(ident)]`.
188///     #[parse(ident)]
189///     ident: Option<syn::Ident>,
190///
191///     /// Will parse `#[my_attr(value = <expr>)]`, `#[my_attr(value(<expr>))]`
192///     /// and `#[my_attr(value(<expr1>, <expr2>))]`.
193///     #[parse(value)]
194///     value: Vec<syn::Expr>,
195///
196///     /// Will parse `#[my_attr(value <lit>)]`, `#[my_attr(value(<lit>))]`
197///     /// and `#[my_attr(value(<lit1>, <lit2>))]`.
198///     #[parse(value(spaced))]
199///     value_spaced: HashSet<syn::Lit>,
200///
201///     /// Will parse `#[my_attr(map <ident> = <type>)]` only.
202///     #[parse(map)]
203///     map: HashMap<syn::Ident, syn::Type>,
204///
205///     /// Will parse `#[my_attr(nested(<arg1>, <arg2>))]` only.
206///     ///
207///     /// Note, we use [`Box`] here only because of recursive structure.
208///     #[parse(nested)]
209///     nested: Option<Spanning<Box<MyAttrs>>>,
210/// }
211///
212/// # fn main() {
213/// let input: syn::DeriveInput = parse_quote! {
214///     #[my_attr(ident)]
215///     #[my_attr(value = 2 * 2, value_spaced "some")]
216///     #[my_attr(map A = Option<u8>)]
217///     #[my_attr(map B = syn::Result<()>)]
218///     #[my_attr(nested(ident, value = "another"))]
219///     struct Dummy;
220/// };
221/// let my_attrs = MyAttrs::parse_attrs("my_attr", &input);
222///
223/// let expected_nested = MyAttrs {
224///     ident: Some(parse_quote!(ident)),
225///     value: vec![parse_quote!("another")],
226///     ..MyAttrs::default()
227/// };
228///
229/// assert!(my_attrs.is_ok());
230/// # let my_attrs = my_attrs.unwrap();
231/// assert_eq!(my_attrs.ident, Some(parse_quote!(ident)));
232/// assert_eq!(my_attrs.value, vec![parse_quote!(2 * 2)]);
233/// assert!(my_attrs.value_spaced.contains(&parse_quote!("some")));
234/// assert_eq!(my_attrs.map.len(), 2);
235/// assert_eq!(my_attrs.map[&parse_quote!(A)], parse_quote!(Option<u8>));
236/// assert_eq!(my_attrs.map[&parse_quote!(B)], parse_quote!(syn::Result<()>));
237/// assert_eq!(*my_attrs.nested.unwrap().into_inner(), expected_nested);
238/// # }
239/// ```
240///
241/// Only one such argument can be chosen for a single field.
242///
243/// ```rust,compile_fail
244/// # use synthez::ParseAttrs;
245/// #
246/// #[derive(Default, ParseAttrs)]
247/// struct Wrong {
248///     /// We cannot use two kinds of parsing simultaneously.
249///     #[parse(ident, value)]
250///     field: Option<syn::Ident>,
251/// }
252/// ```
253///
254/// ## `alias = <name>`, `aliases(<name1>, <name2>)` (optional)
255///
256/// Adds aliases for an attribute's argument in addition to its field ident.
257///
258/// ```rust
259/// # use syn::parse_quote;
260/// # use synthez::ParseAttrs;
261/// #
262/// #[derive(Default, ParseAttrs)]
263/// struct MyAttrs {
264///     #[parse(value, alias = value)]
265///     #[parse(aliases(vals, values))]
266///     val: Vec<syn::Lit>,
267/// }
268///
269/// # fn main() {
270/// let input: syn::DeriveInput = parse_quote! {
271///     #[my_attr(val = "foo")]
272///     #[my_attr(value = "bar")]
273///     #[my_attr(vals(1, 2), values(3, 4))]
274///     struct Dummy;
275/// };
276/// let my_attrs = MyAttrs::parse_attrs("my_attr", &input);
277///
278/// # assert!(my_attrs.is_ok());
279/// # let my_attrs = my_attrs.unwrap();
280/// assert_eq!(my_attrs.val.len(), 6);
281/// # }
282/// ```
283///
284/// ## `arg = <name>`, `args(<name1>, <name2>)` (optional)
285///
286/// Similar to `alias` argument, but excludes the field ident from possible
287/// names of a parsed attribute's argument. Can be used with `alias` argument
288/// simultaneously.
289///
290/// ```rust
291/// # use syn::parse_quote;
292/// # use synthez::ParseAttrs;
293/// #
294/// #[derive(Default, ParseAttrs)]
295/// struct MyAttrs {
296///     #[parse(value, arg = value)]
297///     #[parse(args(vals, values))]
298///     #[parse(alias = v_a_l)]
299///     val: Vec<syn::Lit>,
300/// }
301///
302/// # fn main() {
303/// let input: syn::DeriveInput = parse_quote! {
304///     #[my_attr(value = "foo")]
305///     #[my_attr(vals(1, 2), values(3, 4))]
306///     #[my_attr(v_a_l = "bar")]
307///     struct Dummy;
308/// };
309/// let my_attrs = MyAttrs::parse_attrs("my_attr", &input);
310///
311/// # assert!(my_attrs.is_ok());
312/// # let my_attrs = my_attrs.unwrap();
313/// assert_eq!(my_attrs.val.len(), 6);
314///
315/// let wrong: syn::DeriveInput = parse_quote! {
316///     #[my_attr(val = "foo")]
317///     struct Dummy;
318/// };
319/// let my_attrs = MyAttrs::parse_attrs("my_attr", &wrong);
320///
321/// assert!(my_attrs.is_err());
322/// # }
323/// ```
324///
325/// ## `dedup = <strategy>` (optional)
326///
327/// Defines deduplication strategy for the repeated same values during parsing.
328/// Can be one of the following:
329/// - `unique` (default): disallows duplicates;
330/// - `first`: takes first value and ignores subsequent ones;
331/// - `last`: takes last value and ignores previous ones.
332///
333/// ```rust
334/// # use syn::parse_quote;
335/// # use synthez::ParseAttrs;
336/// #
337/// #[derive(Default, ParseAttrs)]
338/// struct MyAttrs {
339///     /// Picks last appeared [`syn::Ident`] in attributes.
340///     #[parse(ident, dedup = last, alias = named)]
341///     name: Option<syn::Ident>,
342///
343///     /// Picks first value of `lit = <lit>` argument.
344///     #[parse(value, dedup = first)]
345///     lit: Option<syn::LitStr>,
346///
347///     /// Allows only one of `args`.
348///     #[parse(ident, dedup = unique, args(foo, bar, baz))]
349///     field: Option<syn::Ident>,
350/// }
351///
352/// # fn main() {
353/// let input: syn::DeriveInput = parse_quote! {
354///     #[my_attr(name, lit = "foo")]
355///     #[my_attr(named, lit = "bar")]
356///     #[my_attr(baz)]
357///     struct Dummy;
358/// };
359/// let my_attrs = MyAttrs::parse_attrs("my_attr", &input);
360///
361/// # assert!(my_attrs.is_ok());
362/// # let my_attrs = my_attrs.unwrap();
363/// assert_eq!(my_attrs.name, Some(parse_quote!(named)));
364/// assert_eq!(my_attrs.lit, Some(parse_quote!("foo")));
365/// assert_eq!(my_attrs.field, Some(parse_quote!(baz)));
366///
367/// let wrong: syn::DeriveInput = parse_quote! {
368///     #[my_attr(foo, bar)]
369///     #[my_attr(baz)]
370///     struct Dummy;
371/// };
372/// let my_attrs = MyAttrs::parse_attrs("my_attr", &wrong);
373///
374/// assert!(my_attrs.is_err());
375/// # }
376/// ```
377///
378/// ## `validate = <func>` (optional)
379///
380/// Allows to specify a function for additional validation of the parsed field
381/// value. The signature of the function should be the following:
382/// ```rust,ignore
383/// fn(&FieldType) -> syn::Result<()>
384/// ```
385///
386/// ```rust
387/// # use proc_macro2::Span;
388/// # use syn::parse_quote;
389/// # use synthez::ParseAttrs;
390/// #
391/// #[derive(Default, ParseAttrs)]
392/// struct MyAttrs {
393///     #[parse(value, validate = not_foo)]
394///     val: Option<syn::LitStr>,
395/// }
396///
397/// fn not_foo(lit: &Option<syn::LitStr>) -> syn::Result<()> {
398///     if lit.as_ref().map(syn::LitStr::value).as_deref() == Some("foo") {
399///         Err(syn::Error::new(Span::call_site(), "'foo' is not allowed"))
400///     } else {
401///         Ok(())
402///     }
403/// }
404///
405/// # fn main() {
406/// let wrong: syn::DeriveInput = parse_quote! {
407///     #[my_attr(val = "foo")]
408///     struct Dummy;
409/// };
410/// let my_attrs = MyAttrs::parse_attrs("my_attr", &wrong);
411///
412/// assert!(my_attrs.is_err());
413/// # }
414/// ```
415///
416/// ## `fallback = <func>` (optional)
417///
418/// Allows to specify a function producing a fallback value for the prased field
419/// value. The signature of the function should be the following:
420/// ```rust,ignore
421/// fn(&mut FieldType, ParsedInputType) -> syn::Result<()>
422/// ```
423///
424/// This fallback function is invoked every time the field is parsed, despite
425/// the kind of values it contains, so it's responsibility of the fallback
426/// function to determine whether applying fallback value is actually required.
427///
428/// Note, that this argument accepts expressions, so you may use
429/// [`field::if_empty()`] in a combination with a parse function to receive the
430/// required signature. In such case the parse function has a way more obvious
431/// signature:
432/// ```rust,ignore
433/// fn(ParsedInputType) -> syn::Result<ValueType>
434/// ```
435///
436/// ```rust
437/// # use syn::parse_quote;
438/// use synthez::{ParseAttrs, field, parse};
439///
440/// #[derive(Default, ParseAttrs)]
441/// struct MyAttrs {
442///     /// `fallback` will use doc comment as a value, if no `desc` argument is
443///     /// provided.
444///     #[parse(value, fallback = field::if_empty(parse::attr::doc))]
445///     desc: Option<syn::LitStr>,
446/// }
447///
448/// # fn main() {
449/// let from_attr: syn::DeriveInput = parse_quote! {
450///     /// bar
451///     #[my_attr(desc = "foo")]
452///     struct Dummy;
453/// };
454/// let my_attrs = MyAttrs::parse_attrs("my_attr", &from_attr);
455///
456/// # assert!(my_attrs.is_ok());
457/// # let my_attrs = my_attrs.unwrap();
458/// assert_eq!(my_attrs.desc, Some(parse_quote!("foo")));
459///
460/// let from_doc: syn::DeriveInput = parse_quote! {
461///     /// bar
462///     struct Dummy;
463/// };
464/// let my_attrs = MyAttrs::parse_attrs("my_attr", &from_doc);
465///
466/// # assert!(my_attrs.is_ok());
467/// # let my_attrs = my_attrs.unwrap();
468/// assert_eq!(my_attrs.desc, Some(parse_quote!("bar")));
469/// # }
470/// ```
471///
472/// [`field::Container`]: synthez_core::field::Container
473/// [`field::if_empty()`]: synthez_core::field::if_empty
474/// [`Parse`]: syn::parse::Parse
475/// [`Required`]: synthez_core::Required
476/// [`Spanned`]: syn::spanned::Spanned
477/// [`Spanning`]: synthez_core::Spanning
478/// [`synthez::ParseAttrs`]: synthez_core::ParseAttrs
479#[proc_macro_derive(ParseAttrs, attributes(parse))]
480pub fn derive_parse_attrs(input: TokenStream) -> TokenStream {
481    syn::parse(input)
482        .and_then(codegen::parse_attrs::derive)
483        .unwrap_or_else(syn::Error::into_compile_error)
484        .into()
485}
486
487/// Deriving of a [`quote::ToTokens`] implementation.
488///
489/// # Arguments
490///
491/// ## `append` (mandatory)
492///
493/// Specifies methods to form [`ToTokens`]' output with.
494///
495/// ```rust
496/// # use synthez::{proc_macro2::TokenStream, quote::quote, ToTokens};
497/// #
498/// #[derive(ToTokens)]
499/// #[to_tokens(append(foo_tokens, baz_tokens))]
500/// struct Dummy;
501///
502/// impl Dummy {
503///     fn foo_tokens(&self) -> TokenStream {
504///         quote! {
505///             impl Foo for String {}
506///         }
507///     }
508///
509///     fn baz_tokens(&self) -> TokenStream {
510///         quote! {
511///             impl Baz for String {}
512///         }
513///     }
514/// }
515///
516/// # fn main() {
517/// let dummy = Dummy;
518///
519/// assert_eq!(
520///     quote! { #dummy }.to_string(),
521///     quote! {
522///         impl Foo for String {}
523///         impl Baz for String {}
524///     }
525///     .to_string(),
526/// );
527/// # }
528/// ```
529///
530/// [`quote::ToTokens`]: synthez_core::quote::ToTokens
531/// [`ToTokens`]: synthez_core::quote::ToTokens
532#[proc_macro_derive(ToTokens, attributes(to_tokens))]
533pub fn derive_to_tokens(input: TokenStream) -> TokenStream {
534    syn::parse(input)
535        .and_then(|i| codegen::to_tokens::derive(&i))
536        .unwrap_or_else(syn::Error::into_compile_error)
537        .into()
538}