proc_micro/
lib.rs

1//! # Proc Micro
2//!
3//! Small conveniences for high-quality macros.
4//!
5//! ## Use
6//!
7//! ```term
8//! $ cargo add proc_micro
9//! $ cargo add strum --features=derive
10//! ```
11//!
12//! ## Errors
13//!
14//! Normal rust code returns on the first error. Great macros accumulate as many
15//! errors as they can and show them all at once.
16//!
17//! - [MaybeError]: A container that holds zero or more [syn::Error]-s. When
18//!   it holds error(s) they can be accumulated into a single [syn::Error] (which
19//!   is a data structure for holding one or more macro errors).
20//! - [OkMaybe]: An alternative for Result that allows for returning both data
21//!   and (maybe) errors at the same time. Use Result when an error means
22//!   the data is unusable (untrustable) or [OkMaybe] when an error means
23//!   the partial data might still be useful in generating additional error.
24//!   information. The caller can convert an [OkMaybe] into a [Result], but cannot
25//!   convert a [Result] into an [OkMaybe].
26//!
27//! ## Enum powered Attribute parsing
28//!
29//! These helpers work with attributes defined as enums with this library:
30//!
31//! - [WithSpan]: Holds the attributes you parsed and their spans
32//! - [parse_attrs]: Turns a [syn::Attribute] into a [Vec] of your parsed enums
33//! - [unique]: Guarantee each enum attribute is only listed once
34//! - [check_exclusive]: Check if an exclusive attribute is used in conjunction with others.
35//! - [known_attribute]: Convienence function for parsing an enum discriminant
36//!
37//! ## Tutorial
38//!
39//! Here's how you define a macro attribute that has a namespace of `my_macro` and
40//! accepts `rename = <string>` and `ignore` attributes using the [strum crate](https://crates.io/crates/strum):
41//!
42//! ```rust
43//! # #[allow(dead_code)]
44//! const NAMESPACE: proc_micro::AttrNamespace =
45//!     proc_micro::AttrNamespace("my_macro");
46//!
47//! #[derive(strum::EnumDiscriminants, Debug, PartialEq)]
48//! #[strum_discriminants(
49//!     name(KnownAttribute),
50//!     derive(strum::EnumIter, strum::Display, strum::EnumString, Hash)
51//! )]
52//! enum ParseAttribute {
53//!     // #[my_macro(rename = "<string>")]
54//!     #[allow(non_camel_case_types)]
55//!     rename(String),
56//!     // #[my_macro(ignore)]
57//!     #[allow(non_camel_case_types)]
58//!     ignore,
59//! }
60//!
61//! impl syn::parse::Parse for ParseAttribute {
62//!     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
63//!         let ident = input.parse::<syn::Ident>()?;
64//!         match proc_micro::known_attribute(&ident)? {
65//!             KnownAttribute::rename => {
66//!                 input.parse::<syn::Token![=]>()?;
67//!                 Ok(ParseAttribute::rename(
68//!                     input.parse::<syn::LitStr>()?.value(),
69//!                 ))
70//!             }
71//!             KnownAttribute::ignore => Ok(ParseAttribute::ignore),
72//!         }
73//!     }
74//! }
75//! ```
76//!
77//! Each parsed attribute is stored in our enum while the discriminant can be
78//! used as a lookup. By representing attributes as an enum, we can be confident
79//! our code handles attribute additions or modifications exhaustively.
80//!
81//! This also provides a platform for unit testing attribute logic:
82//!
83//! ```
84#![doc = include_str!("./fixtures/hidden_parse_attribute_rename_ignore.md")]
85//! let attribute = syn::parse_str::<ParseAttribute>(
86//!     "rename = \"Ruby version\""
87//! ).unwrap();
88//! assert_eq!(ParseAttribute::rename("Ruby version".to_string()), attribute);
89//! ```
90//!
91//! Then within your macro code you can convert many comma separated attributes
92//! into enums while accumulating errors:
93//!
94//! ```
95#![doc = include_str!("./fixtures/hidden_parse_attribute_rename_ignore.md")]
96//! # use proc_micro::WithSpan;
97//! # const NAMESPACE: proc_micro::AttrNamespace =
98//! #    proc_micro::AttrNamespace("my_macro");
99//!
100//! let mut errors = proc_micro::MaybeError::new();
101//!
102//! let field: syn::Field = syn::parse_quote! {
103//!     #[my_macro(ignore, rename = "Ruby version")]
104//!     version: String
105//! };
106//!
107//! let attributes: Vec<WithSpan<ParseAttribute>> = proc_micro::parse_attrs(
108//!     &NAMESPACE, &field.attrs
109//! ).push_unwrap(&mut errors);
110//!
111//! assert_eq!(2, attributes.len());
112//! assert!(matches!(attributes.first(), Some(WithSpan(ParseAttribute::ignore, _))));
113//! assert!(errors.is_empty());
114//! ```
115//!
116//! Use this result with other helpers to validate your attribute requirements.
117//! For example [unique] requires that attributes are specified at most once i.e.
118//! `#[my_macro(ignore, ignore)]` is incorrect. And [check_exclusive] is called
119//! for attributes that must be used exclusively, i.e. using "ignore" with any
120//! other attribute is in valid as they would have no effect. And you can use
121//! the returned [WithSpan] information to build your own custom [syn::Error]
122//! errors.
123//!
124//! ```
125#![doc = include_str!("./fixtures/hidden_parse_attribute_rename_ignore.md")]
126//! # use proc_micro::WithSpan;
127//! # const NAMESPACE: proc_micro::AttrNamespace =
128//! #    proc_micro::AttrNamespace("my_macro");
129//!
130//! use proc_micro::OkMaybe;
131//!
132//! // Make a structure to store your parsed configuration
133//! #[derive(Debug, Clone)]
134//! struct FieldConfig {
135//!     # #[allow(dead_code)]
136//!     ignore: bool,
137//!     # #[allow(dead_code)]
138//!     rename: Option<String>
139//! }
140//!
141//! // Use our building blocks to implement your desired logic
142//! fn field_config(field: &syn::Field) -> OkMaybe<FieldConfig, syn::Error> {
143//!     let mut rename_config = None;
144//!     let mut ignore_config = false;
145//!     let mut errors = proc_micro::MaybeError::new();
146//!
147//!     let attributes: Vec<WithSpan<ParseAttribute>> = proc_micro::parse_attrs(
148//!         &NAMESPACE, &field.attrs
149//!     ).push_unwrap(&mut errors);
150//!
151//!     proc_micro::check_exclusive(KnownAttribute::ignore, &attributes)
152//!         .push_unwrap(&mut errors);
153//!     let mut unique = proc_micro::unique(attributes)
154//!         .push_unwrap(&mut errors);
155//!
156//!     for (_, WithSpan(attribute, _)) in unique.drain() {
157//!         match attribute {
158//!             ParseAttribute::ignore => ignore_config = true,
159//!             ParseAttribute::rename(name) => rename_config = Some(name),
160//!         }
161//!     }
162//!
163//!     OkMaybe(
164//!         FieldConfig {
165//!             ignore: ignore_config,
166//!             rename: rename_config
167//!         },
168//!         errors.maybe()
169//!     )
170//! }
171//!
172//! // No problems
173//! let _config = field_config(&syn::parse_quote! {
174//!     #[my_macro(rename = "Ruby version")]
175//!     version: String
176//! }).to_result().unwrap();
177//!
178//! // Problem with `check_exclusive`
179//! let result = field_config(&syn::parse_quote! {
180//!     #[my_macro(rename = "Ruby version", ignore)]
181//!     version: String
182//! }).to_result();
183//!
184//!  assert!(result.is_err(), "Expected to be err but is {result:?}");
185//!  let err = result.err().unwrap();
186//!  assert_eq!(vec![
187//!     "Exclusive attribute. Remove either `ignore` or `rename`".to_string(),
188//!     "cannot be used with `ignore`".to_string()],
189//!     err.into_iter().map(|e| e.to_string()).collect::<Vec<String>>()
190//!  );
191//!
192//! // Problem with `unique`
193//! let result = field_config(&syn::parse_quote! {
194//!     #[my_macro(ignore, ignore)]
195//!     version: String
196//! }).to_result();
197//!
198//! assert!(result.is_err(), "Expected to be err but is {result:?}");
199//! let err = result.err().unwrap();
200//! assert_eq!(vec![
201//!     "Duplicate attribute: `ignore`".to_string(),
202//!     "previously `ignore` defined here".to_string()],
203//!     err.into_iter().map(|e| e.to_string()).collect::<Vec<String>>()
204//! );
205//!
206//! // Multiple problems `unique` and unknown attribute
207//! let result = field_config(&syn::parse_quote! {
208//!     #[my_macro(ignore, ignore)]
209//!     #[my_macro(unknown)]
210//!     version: String
211//! }).to_result();
212//!
213//! assert!(result.is_err(), "Expected to be err but is {result:?}");
214//! let err = result.err().unwrap();
215//! assert_eq!(vec![
216//!     "Unknown attribute: `unknown`. Must be one of `rename`, `ignore`".to_string(),
217//!     "Duplicate attribute: `ignore`".to_string(),
218//!     "previously `ignore` defined here".to_string(),
219//!    ],
220//!     err.into_iter().map(|e| e.to_string()).collect::<Vec<String>>()
221//! );
222//! ```
223#![doc(test(attr(warn(unused))))]
224
225mod attr;
226mod error;
227
228pub use attr::{AttrNamespace, WithSpan, check_exclusive, known_attribute, parse_attrs, unique};
229pub use error::MaybeError;
230pub use error::OkMaybe;