| 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: Path = input.call(function: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 |  | 
|---|