]> git.proxmox.com Git - rustc.git/blame - vendor/syn/src/meta.rs
Merge 1.70 into proxmox/bookworm
[rustc.git] / vendor / syn / src / meta.rs
CommitLineData
353b0b11
FG
1//! Facility for interpreting structured content inside of an `Attribute`.
2
3use crate::ext::IdentExt;
4use crate::lit::Lit;
5use crate::parse::{Error, ParseStream, Parser, Result};
6use crate::path::{Path, PathSegment};
7use crate::punctuated::Punctuated;
8use proc_macro2::Ident;
9use std::fmt::Display;
10
11/// Make a parser that is usable with `parse_macro_input!` in a
12/// `#[proc_macro_attribute]` macro.
13///
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.
20///
21/// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta
22///
23/// # Example
24///
25/// This example implements an attribute macro whose invocations look like this:
26///
27/// ```
28/// # const IGNORE: &str = stringify! {
29/// #[tea(kind = "EarlGrey", hot)]
30/// struct Picard {...}
31/// # };
32/// ```
33///
34/// The "parameters" supported by the attribute are:
35///
36/// - `kind = "..."`
37/// - `hot`
38/// - `with(sugar, milk, ...)`, a comma-separated list of ingredients
39///
40/// ```
41/// # extern crate proc_macro;
42/// #
43/// use proc_macro::TokenStream;
44/// use syn::{parse_macro_input, LitStr, Path};
45///
46/// # const IGNORE: &str = stringify! {
47/// #[proc_macro_attribute]
48/// # };
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()?);
56/// Ok(())
57/// } else if meta.path.is_ident("hot") {
58/// hot = true;
59/// Ok(())
60/// } else if meta.path.is_ident("with") {
61/// meta.parse_nested_meta(|meta| {
62/// with.push(meta.path);
63/// Ok(())
64/// })
65/// } else {
66/// Err(meta.error("unsupported tea property"))
67/// }
68/// });
69///
70/// parse_macro_input!(args with tea_parser);
71/// eprintln!("kind={kind:?} hot={hot} with={with:?}");
72///
73/// /* ... */
74/// # TokenStream::new()
75/// }
76/// ```
77///
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.
80///
81/// ```console
82/// error: expected `,`
83/// --> src/main.rs:3:37
84/// |
85/// 3 | #[tea(kind = "EarlGrey", with(sugar = "lol", milk))]
86/// | ^
87/// ```
88///
89/// # Example
90///
91/// Same as above but we factor out most of the logic into a separate function.
92///
93/// ```
94/// # extern crate proc_macro;
95/// #
96/// use proc_macro::TokenStream;
97/// use syn::meta::ParseNestedMeta;
98/// use syn::parse::{Parser, Result};
99/// use syn::{parse_macro_input, LitStr, Path};
100///
101/// # const IGNORE: &str = stringify! {
102/// #[proc_macro_attribute]
103/// # };
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);
108///
109/// /* ... */
110/// # TokenStream::new()
111/// }
112///
113/// #[derive(Default)]
114/// struct TeaAttributes {
115/// kind: Option<LitStr>,
116/// hot: bool,
117/// with: Vec<Path>,
118/// }
119///
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()?);
124/// Ok(())
125/// } else /* just like in last example */
126/// # { unimplemented!() }
127///
128/// }
129/// }
130/// ```
131pub fn parser(logic: impl FnMut(ParseNestedMeta) -> Result<()>) -> impl Parser<Output = ()> {
132 |input: ParseStream| parse_nested_meta(input, logic)
133}
134
135/// Context for parsing a single property in the conventional syntax for
136/// structured attributes.
137///
138/// # Examples
139///
140/// Refer to usage examples on the following two entry-points:
141///
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.
146///
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
152/// less precise.
153///
154/// [`Attribute::parse_nested_meta`]: crate::Attribute::parse_nested_meta
155/// [`syn::meta::parser`]: crate::meta::parser
156#[non_exhaustive]
157pub struct ParseNestedMeta<'a> {
158 pub path: Path,
159 pub input: ParseStream<'a>,
160}
161
162impl<'a> ParseNestedMeta<'a> {
163 /// Used when parsing `key = "value"` syntax.
164 ///
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()?`.
169 ///
170 /// # Example
171 ///
172 /// ```
173 /// use syn::{parse_quote, Attribute, LitStr};
174 ///
175 /// let attr: Attribute = parse_quote! {
176 /// #[tea(kind = "EarlGrey")]
177 /// };
178 /// // conceptually:
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" {
185 /// // ...
186 /// }
187 /// Ok(())
188 /// } else {
189 /// Err(meta.error("unsupported attribute"))
190 /// }
191 /// })?;
192 /// }
193 /// # anyhow::Ok(())
194 /// ```
195 pub fn value(&self) -> Result<ParseStream<'a>> {
196 self.input.parse::<Token![=]>()?;
197 Ok(self.input)
198 }
199
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.
203 ///
204 /// # Example
205 ///
206 /// ```
207 /// use syn::{parse_quote, Attribute};
208 ///
209 /// let attr: Attribute = parse_quote! {
210 /// #[tea(with(sugar, milk))]
211 /// };
212 ///
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.
219 /// Ok(())
220 /// } else if meta.path.is_ident("milk") {
221 /// Ok(())
222 /// } else {
223 /// Err(meta.error("unsupported ingredient"))
224 /// }
225 /// })
226 /// } else {
227 /// Err(meta.error("unsupported tea property"))
228 /// }
229 /// })?;
230 /// }
231 /// # anyhow::Ok(())
232 /// ```
233 ///
234 /// # Counterexample
235 ///
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.
241 ///
242 /// ```
243 /// use syn::{parenthesized, parse_quote, Attribute, LitInt};
244 ///
245 /// let attr: Attribute = parse_quote! {
246 /// #[repr(align(32))]
247 /// };
248 ///
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") {
253 /// let content;
254 /// parenthesized!(content in meta.input);
255 /// align = Some(content.parse()?);
256 /// Ok(())
257 /// } else {
258 /// Err(meta.error("unsupported repr"))
259 /// }
260 /// })?;
261 /// }
262 /// # anyhow::Ok(())
263 /// ```
264 pub fn parse_nested_meta(
265 &self,
266 logic: impl FnMut(ParseNestedMeta) -> Result<()>,
267 ) -> Result<()> {
268 let content;
269 parenthesized!(content in self.input);
270 parse_nested_meta(&content, logic)
271 }
272
273 /// Report that the attribute's content did not conform to expectations.
274 ///
275 /// The span of the resulting error will cover `meta.path` *and* everything
276 /// that has been parsed so far since it.
277 ///
278 /// There are 2 ways you might call this. First, if `meta.path` is not
279 /// something you recognize:
280 ///
281 /// ```
282 /// # use syn::Attribute;
283 /// #
284 /// # fn example(attr: &Attribute) -> syn::Result<()> {
285 /// attr.parse_nested_meta(|meta| {
286 /// if meta.path.is_ident("kind") {
287 /// // ...
288 /// Ok(())
289 /// } else {
290 /// Err(meta.error("unsupported tea property"))
291 /// }
292 /// })?;
293 /// # Ok(())
294 /// # }
295 /// ```
296 ///
297 /// In this case, it behaves exactly like
298 /// `syn::Error::new_spanned(&meta.path, "message...")`.
299 ///
300 /// ```console
301 /// error: unsupported tea property
302 /// --> src/main.rs:3:26
303 /// |
304 /// 3 | #[tea(kind = "EarlGrey", wat = "foo")]
305 /// | ^^^
306 /// ```
307 ///
308 /// More usefully, the second place is if you've already parsed a value but
309 /// have decided not to accept the value:
310 ///
311 /// ```
312 /// # use syn::Attribute;
313 /// #
314 /// # fn example(attr: &Attribute) -> syn::Result<()> {
315 /// use syn::Expr;
316 ///
317 /// attr.parse_nested_meta(|meta| {
318 /// if meta.path.is_ident("kind") {
319 /// let expr: Expr = meta.value()?.parse()?;
320 /// match expr {
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")),
328 /// }
329 /// } else /* as above */
330 /// # { unimplemented!() }
331 ///
332 /// })?;
333 /// # Ok(())
334 /// # }
335 /// ```
336 ///
337 /// ```console
338 /// error: tea kind must be a string literal, path, or macro
339 /// --> src/main.rs:3:7
340 /// |
341 /// 3 | #[tea(kind = async { replicator.await })]
342 /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
343 /// ```
344 ///
345 /// Often you may want to use `syn::Error::new_spanned` even in this
346 /// situation. In the above code, that would be:
347 ///
348 /// ```
349 /// # use syn::{Error, Expr};
350 /// #
351 /// # fn example(expr: Expr) -> syn::Result<()> {
352 /// match expr {
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`")),
360 /// }
361 /// # }
362 /// ```
363 ///
364 /// ```console
365 /// error: unsupported expression type for `kind`
366 /// --> src/main.rs:3:14
367 /// |
368 /// 3 | #[tea(kind = async { replicator.await })]
369 /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^
370 /// ```
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)
375 }
376}
377
378pub(crate) fn parse_nested_meta(
379 input: ParseStream,
380 mut logic: impl FnMut(ParseNestedMeta) -> Result<()>,
381) -> Result<()> {
382 loop {
383 let path = input.call(parse_meta_path)?;
384 logic(ParseNestedMeta { path, input })?;
385 if input.is_empty() {
386 return Ok(());
387 }
388 input.parse::<Token![,]>()?;
389 if input.is_empty() {
390 return Ok(());
391 }
392 }
393}
394
395// Like Path::parse_mod_style, but accepts keywords in the path.
396fn parse_meta_path(input: ParseStream) -> Result<Path> {
397 Ok(Path {
398 leading_colon: input.parse()?,
399 segments: {
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"));
406 } else {
407 return Err(input.error("unexpected token in nested attribute, expected ident"));
408 }
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));
414 }
415 segments
416 },
417 })
418}