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}