Skip to main content

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