Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/rust/syn/meta.rs
38271 views
1
// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3
//! Facility for interpreting structured content inside of an `Attribute`.
4
5
use crate::error::{Error, Result};
6
use crate::ext::IdentExt as _;
7
use crate::lit::Lit;
8
use crate::parse::{ParseStream, Parser};
9
use crate::path::{Path, PathSegment};
10
use crate::punctuated::Punctuated;
11
use proc_macro2::Ident;
12
use std::fmt::Display;
13
14
/// Make a parser that is usable with `parse_macro_input!` in a
15
/// `#[proc_macro_attribute]` macro.
16
///
17
/// *Warning:* When parsing attribute args **other than** the
18
/// `proc_macro::TokenStream` input of a `proc_macro_attribute`, you do **not**
19
/// need this function. In several cases your callers will get worse error
20
/// messages if you use this function, because the surrounding delimiter's span
21
/// is concealed from attribute macros by rustc. Use
22
/// [`Attribute::parse_nested_meta`] instead.
23
///
24
/// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta
25
///
26
/// # Example
27
///
28
/// This example implements an attribute macro whose invocations look like this:
29
///
30
/// ```
31
/// # const IGNORE: &str = stringify! {
32
/// #[tea(kind = "EarlGrey", hot)]
33
/// struct Picard {...}
34
/// # };
35
/// ```
36
///
37
/// The "parameters" supported by the attribute are:
38
///
39
/// - `kind = "..."`
40
/// - `hot`
41
/// - `with(sugar, milk, ...)`, a comma-separated list of ingredients
42
///
43
/// ```
44
/// # extern crate proc_macro;
45
/// #
46
/// use proc_macro::TokenStream;
47
/// use syn::{parse_macro_input, LitStr, Path};
48
///
49
/// # const IGNORE: &str = stringify! {
50
/// #[proc_macro_attribute]
51
/// # };
52
/// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream {
53
/// let mut kind: Option<LitStr> = None;
54
/// let mut hot: bool = false;
55
/// let mut with: Vec<Path> = Vec::new();
56
/// let tea_parser = syn::meta::parser(|meta| {
57
/// if meta.path.is_ident("kind") {
58
/// kind = Some(meta.value()?.parse()?);
59
/// Ok(())
60
/// } else if meta.path.is_ident("hot") {
61
/// hot = true;
62
/// Ok(())
63
/// } else if meta.path.is_ident("with") {
64
/// meta.parse_nested_meta(|meta| {
65
/// with.push(meta.path);
66
/// Ok(())
67
/// })
68
/// } else {
69
/// Err(meta.error("unsupported tea property"))
70
/// }
71
/// });
72
///
73
/// parse_macro_input!(args with tea_parser);
74
/// eprintln!("kind={kind:?} hot={hot} with={with:?}");
75
///
76
/// /* ... */
77
/// # TokenStream::new()
78
/// }
79
/// ```
80
///
81
/// The `syn::meta` library will take care of dealing with the commas including
82
/// trailing commas, and producing sensible error messages on unexpected input.
83
///
84
/// ```console
85
/// error: expected `,`
86
/// --> src/main.rs:3:37
87
/// |
88
/// 3 | #[tea(kind = "EarlGrey", with(sugar = "lol", milk))]
89
/// | ^
90
/// ```
91
///
92
/// # Example
93
///
94
/// Same as above but we factor out most of the logic into a separate function.
95
///
96
/// ```
97
/// # extern crate proc_macro;
98
/// #
99
/// use proc_macro::TokenStream;
100
/// use syn::meta::ParseNestedMeta;
101
/// use syn::parse::{Parser, Result};
102
/// use syn::{parse_macro_input, LitStr, Path};
103
///
104
/// # const IGNORE: &str = stringify! {
105
/// #[proc_macro_attribute]
106
/// # };
107
/// pub fn tea(args: TokenStream, input: TokenStream) -> TokenStream {
108
/// let mut attrs = TeaAttributes::default();
109
/// let tea_parser = syn::meta::parser(|meta| attrs.parse(meta));
110
/// parse_macro_input!(args with tea_parser);
111
///
112
/// /* ... */
113
/// # TokenStream::new()
114
/// }
115
///
116
/// #[derive(Default)]
117
/// struct TeaAttributes {
118
/// kind: Option<LitStr>,
119
/// hot: bool,
120
/// with: Vec<Path>,
121
/// }
122
///
123
/// impl TeaAttributes {
124
/// fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
125
/// if meta.path.is_ident("kind") {
126
/// self.kind = Some(meta.value()?.parse()?);
127
/// Ok(())
128
/// } else /* just like in last example */
129
/// # { unimplemented!() }
130
///
131
/// }
132
/// }
133
/// ```
134
pub fn parser(logic: impl FnMut(ParseNestedMeta) -> Result<()>) -> impl Parser<Output = ()> {
135
|input: ParseStream| {
136
if input.is_empty() {
137
Ok(())
138
} else {
139
parse_nested_meta(input, logic)
140
}
141
}
142
}
143
144
/// Context for parsing a single property in the conventional syntax for
145
/// structured attributes.
146
///
147
/// # Examples
148
///
149
/// Refer to usage examples on the following two entry-points:
150
///
151
/// - [`Attribute::parse_nested_meta`] if you have an entire `Attribute` to
152
/// parse. Always use this if possible. Generally this is able to produce
153
/// better error messages because `Attribute` holds span information for all
154
/// of the delimiters therein.
155
///
156
/// - [`syn::meta::parser`] if you are implementing a `proc_macro_attribute`
157
/// macro and parsing the arguments to the attribute macro, i.e. the ones
158
/// written in the same attribute that dispatched the macro invocation. Rustc
159
/// does not pass span information for the surrounding delimiters into the
160
/// attribute macro invocation in this situation, so error messages might be
161
/// less precise.
162
///
163
/// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta
164
/// [`syn::meta::parser`]: crate::meta::parser
165
#[non_exhaustive]
166
pub struct ParseNestedMeta<'a> {
167
pub path: Path,
168
pub input: ParseStream<'a>,
169
}
170
171
impl<'a> ParseNestedMeta<'a> {
172
/// Used when parsing `key = "value"` syntax.
173
///
174
/// All it does is advance `meta.input` past the `=` sign in the input. You
175
/// could accomplish the same effect by writing
176
/// `meta.parse::<Token![=]>()?`, so at most it is a minor convenience to
177
/// use `meta.value()?`.
178
///
179
/// # Example
180
///
181
/// ```
182
/// use syn::{parse_quote, Attribute, LitStr};
183
///
184
/// let attr: Attribute = parse_quote! {
185
/// #[tea(kind = "EarlGrey")]
186
/// };
187
/// // conceptually:
188
/// if attr.path().is_ident("tea") { // this parses the `tea`
189
/// attr.parse_nested_meta(|meta| { // this parses the `(`
190
/// if meta.path.is_ident("kind") { // this parses the `kind`
191
/// let value = meta.value()?; // this parses the `=`
192
/// let s: LitStr = value.parse()?; // this parses `"EarlGrey"`
193
/// if s.value() == "EarlGrey" {
194
/// // ...
195
/// }
196
/// Ok(())
197
/// } else {
198
/// Err(meta.error("unsupported attribute"))
199
/// }
200
/// })?;
201
/// }
202
/// # anyhow::Ok(())
203
/// ```
204
pub fn value(&self) -> Result<ParseStream<'a>> {
205
self.input.parse::<Token![=]>()?;
206
Ok(self.input)
207
}
208
209
/// Used when parsing `list(...)` syntax **if** the content inside the
210
/// nested parentheses is also expected to conform to Rust's structured
211
/// attribute convention.
212
///
213
/// # Example
214
///
215
/// ```
216
/// use syn::{parse_quote, Attribute};
217
///
218
/// let attr: Attribute = parse_quote! {
219
/// #[tea(with(sugar, milk))]
220
/// };
221
///
222
/// if attr.path().is_ident("tea") {
223
/// attr.parse_nested_meta(|meta| {
224
/// if meta.path.is_ident("with") {
225
/// meta.parse_nested_meta(|meta| { // <---
226
/// if meta.path.is_ident("sugar") {
227
/// // Here we can go even deeper if needed.
228
/// Ok(())
229
/// } else if meta.path.is_ident("milk") {
230
/// Ok(())
231
/// } else {
232
/// Err(meta.error("unsupported ingredient"))
233
/// }
234
/// })
235
/// } else {
236
/// Err(meta.error("unsupported tea property"))
237
/// }
238
/// })?;
239
/// }
240
/// # anyhow::Ok(())
241
/// ```
242
///
243
/// # Counterexample
244
///
245
/// If you don't need `parse_nested_meta`'s help in parsing the content
246
/// written within the nested parentheses, keep in mind that you can always
247
/// just parse it yourself from the exposed ParseStream. Rust syntax permits
248
/// arbitrary tokens within those parentheses so for the crazier stuff,
249
/// `parse_nested_meta` is not what you want.
250
///
251
/// ```
252
/// use syn::{parenthesized, parse_quote, Attribute, LitInt};
253
///
254
/// let attr: Attribute = parse_quote! {
255
/// #[repr(align(32))]
256
/// };
257
///
258
/// let mut align: Option<LitInt> = None;
259
/// if attr.path().is_ident("repr") {
260
/// attr.parse_nested_meta(|meta| {
261
/// if meta.path.is_ident("align") {
262
/// let content;
263
/// parenthesized!(content in meta.input);
264
/// align = Some(content.parse()?);
265
/// Ok(())
266
/// } else {
267
/// Err(meta.error("unsupported repr"))
268
/// }
269
/// })?;
270
/// }
271
/// # anyhow::Ok(())
272
/// ```
273
pub fn parse_nested_meta(
274
&self,
275
logic: impl FnMut(ParseNestedMeta) -> Result<()>,
276
) -> Result<()> {
277
let content;
278
parenthesized!(content in self.input);
279
parse_nested_meta(&content, logic)
280
}
281
282
/// Report that the attribute's content did not conform to expectations.
283
///
284
/// The span of the resulting error will cover `meta.path` *and* everything
285
/// that has been parsed so far since it.
286
///
287
/// There are 2 ways you might call this. First, if `meta.path` is not
288
/// something you recognize:
289
///
290
/// ```
291
/// # use syn::Attribute;
292
/// #
293
/// # fn example(attr: &Attribute) -> syn::Result<()> {
294
/// attr.parse_nested_meta(|meta| {
295
/// if meta.path.is_ident("kind") {
296
/// // ...
297
/// Ok(())
298
/// } else {
299
/// Err(meta.error("unsupported tea property"))
300
/// }
301
/// })?;
302
/// # Ok(())
303
/// # }
304
/// ```
305
///
306
/// In this case, it behaves exactly like
307
/// `syn::Error::new_spanned(&meta.path, "message...")`.
308
///
309
/// ```console
310
/// error: unsupported tea property
311
/// --> src/main.rs:3:26
312
/// |
313
/// 3 | #[tea(kind = "EarlGrey", wat = "foo")]
314
/// | ^^^
315
/// ```
316
///
317
/// More usefully, the second place is if you've already parsed a value but
318
/// have decided not to accept the value:
319
///
320
/// ```
321
/// # use syn::Attribute;
322
/// #
323
/// # fn example(attr: &Attribute) -> syn::Result<()> {
324
/// use syn::Expr;
325
///
326
/// attr.parse_nested_meta(|meta| {
327
/// if meta.path.is_ident("kind") {
328
/// let expr: Expr = meta.value()?.parse()?;
329
/// match expr {
330
/// Expr::Lit(expr) => /* ... */
331
/// # unimplemented!(),
332
/// Expr::Path(expr) => /* ... */
333
/// # unimplemented!(),
334
/// Expr::Macro(expr) => /* ... */
335
/// # unimplemented!(),
336
/// _ => Err(meta.error("tea kind must be a string literal, path, or macro")),
337
/// }
338
/// } else /* as above */
339
/// # { unimplemented!() }
340
///
341
/// })?;
342
/// # Ok(())
343
/// # }
344
/// ```
345
///
346
/// ```console
347
/// error: tea kind must be a string literal, path, or macro
348
/// --> src/main.rs:3:7
349
/// |
350
/// 3 | #[tea(kind = async { replicator.await })]
351
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
352
/// ```
353
///
354
/// Often you may want to use `syn::Error::new_spanned` even in this
355
/// situation. In the above code, that would be:
356
///
357
/// ```
358
/// # use syn::{Error, Expr};
359
/// #
360
/// # fn example(expr: Expr) -> syn::Result<()> {
361
/// match expr {
362
/// Expr::Lit(expr) => /* ... */
363
/// # unimplemented!(),
364
/// Expr::Path(expr) => /* ... */
365
/// # unimplemented!(),
366
/// Expr::Macro(expr) => /* ... */
367
/// # unimplemented!(),
368
/// _ => Err(Error::new_spanned(expr, "unsupported expression type for `kind`")),
369
/// }
370
/// # }
371
/// ```
372
///
373
/// ```console
374
/// error: unsupported expression type for `kind`
375
/// --> src/main.rs:3:14
376
/// |
377
/// 3 | #[tea(kind = async { replicator.await })]
378
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^
379
/// ```
380
pub fn error(&self, msg: impl Display) -> Error {
381
let start_span = self.path.segments[0].ident.span();
382
let end_span = self.input.cursor().prev_span();
383
crate::error::new2(start_span, end_span, msg)
384
}
385
}
386
387
pub(crate) fn parse_nested_meta(
388
input: ParseStream,
389
mut logic: impl FnMut(ParseNestedMeta) -> Result<()>,
390
) -> Result<()> {
391
loop {
392
let path = input.call(parse_meta_path)?;
393
logic(ParseNestedMeta { path, input })?;
394
if input.is_empty() {
395
return Ok(());
396
}
397
input.parse::<Token![,]>()?;
398
if input.is_empty() {
399
return Ok(());
400
}
401
}
402
}
403
404
// Like Path::parse_mod_style, but accepts keywords in the path.
405
fn parse_meta_path(input: ParseStream) -> Result<Path> {
406
Ok(Path {
407
leading_colon: input.parse()?,
408
segments: {
409
let mut segments = Punctuated::new();
410
if input.peek(Ident::peek_any) {
411
let ident = Ident::parse_any(input)?;
412
segments.push_value(PathSegment::from(ident));
413
} else if input.is_empty() {
414
return Err(input.error("expected nested attribute"));
415
} else if input.peek(Lit) {
416
return Err(input.error("unexpected literal in nested attribute, expected ident"));
417
} else {
418
return Err(input.error("unexpected token in nested attribute, expected ident"));
419
}
420
while input.peek(Token![::]) {
421
let punct = input.parse()?;
422
segments.push_punct(punct);
423
let ident = Ident::parse_any(input)?;
424
segments.push_value(PathSegment::from(ident));
425
}
426
segments
427
},
428
})
429
}
430
431