1 //! Facility for interpreting structured content inside of an `Attribute`.
3 use crate::ext
::IdentExt
;
5 use crate::parse
::{Error, ParseStream, Parser, Result}
;
6 use crate::path
::{Path, PathSegment}
;
7 use crate::punctuated
::Punctuated
;
8 use proc_macro2
::Ident
;
11 /// Make a parser that is usable with `parse_macro_input!` in a
12 /// `#[proc_macro_attribute]` macro.
14 /// *Warning:* When parsing attribute args **other than** the
15 /// `proc_macro::TokenStream` input of a `proc_macro_attribute`, you do **not**
16 /// need this function. In several cases your callers will get worse error
17 /// messages if you use this function, because the surrounding delimiter's span
18 /// is concealed from attribute macros by rustc. Use
19 /// [`Attribute::parse_nested_meta`] instead.
21 /// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta
25 /// This example implements an attribute macro whose invocations look like this:
28 /// # const IGNORE: &str = stringify! {
29 /// #[tea(kind = "EarlGrey", hot)]
30 /// struct Picard {...}
34 /// The "parameters" supported by the attribute are:
38 /// - `with(sugar, milk, ...)`, a comma-separated list of ingredients
41 /// # extern crate proc_macro;
43 /// use proc_macro::TokenStream;
44 /// use syn::{parse_macro_input, LitStr, Path};
46 /// # const IGNORE: &str = stringify! {
47 /// #[proc_macro_attribute]
49 /// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream {
50 /// let mut kind: Option<LitStr> = None;
51 /// let mut hot: bool = false;
52 /// let mut with: Vec<Path> = Vec::new();
53 /// let tea_parser = syn::meta::parser(|meta| {
54 /// if meta.path.is_ident("kind") {
55 /// kind = Some(meta.value()?.parse()?);
57 /// } else if meta.path.is_ident("hot") {
60 /// } else if meta.path.is_ident("with") {
61 /// meta.parse_nested_meta(|meta| {
62 /// with.push(meta.path);
66 /// Err(meta.error("unsupported tea property"))
70 /// parse_macro_input!(args with tea_parser);
71 /// eprintln!("kind={kind:?} hot={hot} with={with:?}");
74 /// # TokenStream::new()
78 /// The `syn::meta` library will take care of dealing with the commas including
79 /// trailing commas, and producing sensible error messages on unexpected input.
82 /// error: expected `,`
83 /// --> src/main.rs:3:37
85 /// 3 | #[tea(kind = "EarlGrey", with(sugar = "lol", milk))]
91 /// Same as above but we factor out most of the logic into a separate function.
94 /// # extern crate proc_macro;
96 /// use proc_macro::TokenStream;
97 /// use syn::meta::ParseNestedMeta;
98 /// use syn::parse::{Parser, Result};
99 /// use syn::{parse_macro_input, LitStr, Path};
101 /// # const IGNORE: &str = stringify! {
102 /// #[proc_macro_attribute]
104 /// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream {
105 /// let mut attrs = TeaAttributes::default();
106 /// let tea_parser = syn::meta::parser(|meta| attrs.parse(meta));
107 /// parse_macro_input!(args with tea_parser);
110 /// # TokenStream::new()
113 /// #[derive(Default)]
114 /// struct TeaAttributes {
115 /// kind: Option<LitStr>,
120 /// impl TeaAttributes {
121 /// fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
122 /// if meta.path.is_ident("kind") {
123 /// self.kind = Some(meta.value()?.parse()?);
125 /// } else /* just like in last example */
126 /// # { unimplemented!() }
131 pub fn parser(logic
: impl FnMut(ParseNestedMeta
) -> Result
<()>) -> impl Parser
<Output
= ()> {
132 |input
: ParseStream
| parse_nested_meta(input
, logic
)
135 /// Context for parsing a single property in the conventional syntax for
136 /// structured attributes.
140 /// Refer to usage examples on the following two entry-points:
142 /// - [`Attribute::parse_nested_meta`] if you have an entire `Attribute` to
143 /// parse. Always use this if possible. Generally this is able to produce
144 /// better error messages because `Attribute` holds span information for all
145 /// of the delimiters therein.
147 /// - [`syn::meta::parser`] if you are implementing a `proc_macro_attribute`
148 /// macro and parsing the arguments to the attribute macro, i.e. the ones
149 /// written in the same attribute that dispatched the macro invocation. Rustc
150 /// does not pass span information for the surrounding delimiters into the
151 /// attribute macro invocation in this situation, so error messages might be
154 /// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta
155 /// [`syn::meta::parser`]: crate::meta::parser
157 pub struct ParseNestedMeta
<'a
> {
159 pub input
: ParseStream
<'a
>,
162 impl<'a
> ParseNestedMeta
<'a
> {
163 /// Used when parsing `key = "value"` syntax.
165 /// All it does is advance `meta.input` past the `=` sign in the input. You
166 /// could accomplish the same effect by writing
167 /// `meta.parse::<Token![=]>()?`, so at most it is a minor convenience to
168 /// use `meta.value()?`.
173 /// use syn::{parse_quote, Attribute, LitStr};
175 /// let attr: Attribute = parse_quote! {
176 /// #[tea(kind = "EarlGrey")]
179 /// if attr.path().is_ident("tea") { // this parses the `tea`
180 /// attr.parse_nested_meta(|meta| { // this parses the `(`
181 /// if meta.path.is_ident("kind") { // this parses the `kind`
182 /// let value = meta.value()?; // this parses the `=`
183 /// let s: LitStr = value.parse()?; // this parses `"EarlGrey"`
184 /// if s.value() == "EarlGrey" {
189 /// Err(meta.error("unsupported attribute"))
195 pub fn value(&self) -> Result
<ParseStream
<'a
>> {
196 self.input
.parse
::<Token
![=]>()?
;
200 /// Used when parsing `list(...)` syntax **if** the content inside the
201 /// nested parentheses is also expected to conform to Rust's structured
202 /// attribute convention.
207 /// use syn::{parse_quote, Attribute};
209 /// let attr: Attribute = parse_quote! {
210 /// #[tea(with(sugar, milk))]
213 /// if attr.path().is_ident("tea") {
214 /// attr.parse_nested_meta(|meta| {
215 /// if meta.path.is_ident("with") {
216 /// meta.parse_nested_meta(|meta| { // <---
217 /// if meta.path.is_ident("sugar") {
218 /// // Here we can go even deeper if needed.
220 /// } else if meta.path.is_ident("milk") {
223 /// Err(meta.error("unsupported ingredient"))
227 /// Err(meta.error("unsupported tea property"))
236 /// If you don't need `parse_nested_meta`'s help in parsing the content
237 /// written within the nested parentheses, keep in mind that you can always
238 /// just parse it yourself from the exposed ParseStream. Rust syntax permits
239 /// arbitrary tokens within those parentheses so for the crazier stuff,
240 /// `parse_nested_meta` is not what you want.
243 /// use syn::{parenthesized, parse_quote, Attribute, LitInt};
245 /// let attr: Attribute = parse_quote! {
246 /// #[repr(align(32))]
249 /// let mut align: Option<LitInt> = None;
250 /// if attr.path().is_ident("repr") {
251 /// attr.parse_nested_meta(|meta| {
252 /// if meta.path.is_ident("align") {
254 /// parenthesized!(content in meta.input);
255 /// align = Some(content.parse()?);
258 /// Err(meta.error("unsupported repr"))
264 pub fn parse_nested_meta(
266 logic
: impl FnMut(ParseNestedMeta
) -> Result
<()>,
269 parenthesized
!(content
in self.input
);
270 parse_nested_meta(&content
, logic
)
273 /// Report that the attribute's content did not conform to expectations.
275 /// The span of the resulting error will cover `meta.path` *and* everything
276 /// that has been parsed so far since it.
278 /// There are 2 ways you might call this. First, if `meta.path` is not
279 /// something you recognize:
282 /// # use syn::Attribute;
284 /// # fn example(attr: &Attribute) -> syn::Result<()> {
285 /// attr.parse_nested_meta(|meta| {
286 /// if meta.path.is_ident("kind") {
290 /// Err(meta.error("unsupported tea property"))
297 /// In this case, it behaves exactly like
298 /// `syn::Error::new_spanned(&meta.path, "message...")`.
301 /// error: unsupported tea property
302 /// --> src/main.rs:3:26
304 /// 3 | #[tea(kind = "EarlGrey", wat = "foo")]
308 /// More usefully, the second place is if you've already parsed a value but
309 /// have decided not to accept the value:
312 /// # use syn::Attribute;
314 /// # fn example(attr: &Attribute) -> syn::Result<()> {
317 /// attr.parse_nested_meta(|meta| {
318 /// if meta.path.is_ident("kind") {
319 /// let expr: Expr = meta.value()?.parse()?;
321 /// Expr::Lit(expr) => /* ... */
322 /// # unimplemented!(),
323 /// Expr::Path(expr) => /* ... */
324 /// # unimplemented!(),
325 /// Expr::Macro(expr) => /* ... */
326 /// # unimplemented!(),
327 /// _ => Err(meta.error("tea kind must be a string literal, path, or macro")),
329 /// } else /* as above */
330 /// # { unimplemented!() }
338 /// error: tea kind must be a string literal, path, or macro
339 /// --> src/main.rs:3:7
341 /// 3 | #[tea(kind = async { replicator.await })]
342 /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
345 /// Often you may want to use `syn::Error::new_spanned` even in this
346 /// situation. In the above code, that would be:
349 /// # use syn::{Error, Expr};
351 /// # fn example(expr: Expr) -> syn::Result<()> {
353 /// Expr::Lit(expr) => /* ... */
354 /// # unimplemented!(),
355 /// Expr::Path(expr) => /* ... */
356 /// # unimplemented!(),
357 /// Expr::Macro(expr) => /* ... */
358 /// # unimplemented!(),
359 /// _ => Err(Error::new_spanned(expr, "unsupported expression type for `kind`")),
365 /// error: unsupported expression type for `kind`
366 /// --> src/main.rs:3:14
368 /// 3 | #[tea(kind = async { replicator.await })]
369 /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^
371 pub fn error(&self, msg
: impl Display
) -> Error
{
372 let start_span
= self.path
.segments
[0].ident
.span();
373 let end_span
= self.input
.cursor().prev_span();
374 crate::error
::new2(start_span
, end_span
, msg
)
378 pub(crate) fn parse_nested_meta(
380 mut logic
: impl FnMut(ParseNestedMeta
) -> Result
<()>,
383 let path
= input
.call(parse_meta_path
)?
;
384 logic(ParseNestedMeta { path, input }
)?
;
385 if input
.is_empty() {
388 input
.parse
::<Token
![,]>()?
;
389 if input
.is_empty() {
395 // Like Path::parse_mod_style, but accepts keywords in the path.
396 fn parse_meta_path(input
: ParseStream
) -> Result
<Path
> {
398 leading_colon
: input
.parse()?
,
400 let mut segments
= Punctuated
::new();
401 if input
.peek(Ident
::peek_any
) {
402 let ident
= Ident
::parse_any(input
)?
;
403 segments
.push_value(PathSegment
::from(ident
));
404 } else if input
.peek(Lit
) {
405 return Err(input
.error("unexpected literal in nested attribute, expected ident"));
407 return Err(input
.error("unexpected token in nested attribute, expected ident"));
409 while input
.peek(Token
![::]) {
410 let punct
= input
.parse()?
;
411 segments
.push_punct(punct
);
412 let ident
= Ident
::parse_any(input
)?
;
413 segments
.push_value(PathSegment
::from(ident
));