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