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