1 use super::*;
2 use crate::punctuated::Punctuated;
3 use proc_macro2::TokenStream;
4 use std::iter;
5
6 #[cfg(feature = "parsing")]
7 use crate::parse::{Parse, ParseBuffer, ParseStream, Parser, Result};
8 #[cfg(feature = "parsing")]
9 use crate::punctuated::Pair;
10
11 ast_struct! {
12 /// An attribute like `#[repr(transparent)]`.
13 ///
14 /// *This type is available only if Syn is built with the `"derive"` or `"full"`
15 /// feature.*
16 ///
17 /// <br>
18 ///
19 /// # Syntax
20 ///
21 /// Rust has six types of attributes.
22 ///
23 /// - Outer attributes like `#[repr(transparent)]`. These appear outside or
24 /// in front of the item they describe.
25 /// - Inner attributes like `#![feature(proc_macro)]`. These appear inside
26 /// of the item they describe, usually a module.
27 /// - Outer doc comments like `/// # Example`.
28 /// - Inner doc comments like `//! Please file an issue`.
29 /// - Outer block comments `/** # Example */`.
30 /// - Inner block comments `/*! Please file an issue */`.
31 ///
32 /// The `style` field of type `AttrStyle` distinguishes whether an attribute
33 /// is outer or inner. Doc comments and block comments are promoted to
34 /// attributes, as this is how they are processed by the compiler and by
35 /// `macro_rules!` macros.
36 ///
37 /// The `path` field gives the possibly colon-delimited path against which
38 /// the attribute is resolved. It is equal to `"doc"` for desugared doc
39 /// comments. The `tokens` field contains the rest of the attribute body as
40 /// tokens.
41 ///
42 /// ```text
43 /// #[derive(Copy)] #[crate::precondition x < 5]
44 /// ^^^^^^~~~~~~ ^^^^^^^^^^^^^^^^^^^ ~~~~~
45 /// path tokens path tokens
46 /// ```
47 ///
48 /// <br>
49 ///
50 /// # Parsing from tokens to Attribute
51 ///
52 /// This type does not implement the [`Parse`] trait and thus cannot be
53 /// parsed directly by [`ParseStream::parse`]. Instead use
54 /// [`ParseStream::call`] with one of the two parser functions
55 /// [`Attribute::parse_outer`] or [`Attribute::parse_inner`] depending on
56 /// which you intend to parse.
57 ///
58 /// [`Parse`]: parse::Parse
59 /// [`ParseStream::parse`]: parse::ParseBuffer::parse
60 /// [`ParseStream::call`]: parse::ParseBuffer::call
61 ///
62 /// ```
63 /// use syn::{Attribute, Ident, Result, Token};
64 /// use syn::parse::{Parse, ParseStream};
65 ///
66 /// // Parses a unit struct with attributes.
67 /// //
68 /// // #[path = "s.tmpl"]
69 /// // struct S;
70 /// struct UnitStruct {
71 /// attrs: Vec<Attribute>,
72 /// struct_token: Token![struct],
73 /// name: Ident,
74 /// semi_token: Token![;],
75 /// }
76 ///
77 /// impl Parse for UnitStruct {
78 /// fn parse(input: ParseStream) -> Result<Self> {
79 /// Ok(UnitStruct {
80 /// attrs: input.call(Attribute::parse_outer)?,
81 /// struct_token: input.parse()?,
82 /// name: input.parse()?,
83 /// semi_token: input.parse()?,
84 /// })
85 /// }
86 /// }
87 /// ```
88 ///
89 /// <p><br></p>
90 ///
91 /// # Parsing from Attribute to structured arguments
92 ///
93 /// The grammar of attributes in Rust is very flexible, which makes the
94 /// syntax tree not that useful on its own. In particular, arguments of the
95 /// attribute are held in an arbitrary `tokens: TokenStream`. Macros are
96 /// expected to check the `path` of the attribute, decide whether they
97 /// recognize it, and then parse the remaining tokens according to whatever
98 /// grammar they wish to require for that kind of attribute.
99 ///
100 /// If the attribute you are parsing is expected to conform to the
101 /// conventional structured form of attribute, use [`parse_meta()`] to
102 /// obtain that structured representation. If the attribute follows some
103 /// other grammar of its own, use [`parse_args()`] to parse that into the
104 /// expected data structure.
105 ///
106 /// [`parse_meta()`]: Attribute::parse_meta
107 /// [`parse_args()`]: Attribute::parse_args
108 ///
109 /// <p><br></p>
110 ///
111 /// # Doc comments
112 ///
113 /// The compiler transforms doc comments, such as `/// comment` and `/*!
114 /// comment */`, into attributes before macros are expanded. Each comment is
115 /// expanded into an attribute of the form `#[doc = r"comment"]`.
116 ///
117 /// As an example, the following `mod` items are expanded identically:
118 ///
119 /// ```
120 /// # use syn::{ItemMod, parse_quote};
121 /// let doc: ItemMod = parse_quote! {
122 /// /// Single line doc comments
123 /// /// We write so many!
124 /// /**
125 /// * Multi-line comments...
126 /// * May span many lines
127 /// */
128 /// mod example {
129 /// //! Of course, they can be inner too
130 /// /*! And fit in a single line */
131 /// }
132 /// };
133 /// let attr: ItemMod = parse_quote! {
134 /// #[doc = r" Single line doc comments"]
135 /// #[doc = r" We write so many!"]
136 /// #[doc = r"
137 /// * Multi-line comments...
138 /// * May span many lines
139 /// "]
140 /// mod example {
141 /// #![doc = r" Of course, they can be inner too"]
142 /// #![doc = r" And fit in a single line "]
143 /// }
144 /// };
145 /// assert_eq!(doc, attr);
146 /// ```
147 #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
148 pub struct Attribute {
149 pub pound_token: Token![#],
150 pub style: AttrStyle,
151 pub bracket_token: token::Bracket,
152 pub path: Path,
153 pub tokens: TokenStream,
154 }
155 }
156
157 impl Attribute {
158 /// Parses the content of the attribute, consisting of the path and tokens,
159 /// as a [`Meta`] if possible.
160 ///
161 /// *This function is available only if Syn is built with the `"parsing"`
162 /// feature.*
163 #[cfg(feature = "parsing")]
164 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
parse_meta(&self) -> Result<Meta>165 pub fn parse_meta(&self) -> Result<Meta> {
166 fn clone_ident_segment(segment: &PathSegment) -> PathSegment {
167 PathSegment {
168 ident: segment.ident.clone(),
169 arguments: PathArguments::None,
170 }
171 }
172
173 let path = Path {
174 leading_colon: self
175 .path
176 .leading_colon
177 .as_ref()
178 .map(|colon| Token![::](colon.spans)),
179 segments: self
180 .path
181 .segments
182 .pairs()
183 .map(|pair| match pair {
184 Pair::Punctuated(seg, punct) => {
185 Pair::Punctuated(clone_ident_segment(seg), Token![::](punct.spans))
186 }
187 Pair::End(seg) => Pair::End(clone_ident_segment(seg)),
188 })
189 .collect(),
190 };
191
192 let parser = |input: ParseStream| parsing::parse_meta_after_path(path, input);
193 parse::Parser::parse2(parser, self.tokens.clone())
194 }
195
196 /// Parse the arguments to the attribute as a syntax tree.
197 ///
198 /// This is similar to `syn::parse2::<T>(attr.tokens)` except that:
199 ///
200 /// - the surrounding delimiters are *not* included in the input to the
201 /// parser; and
202 /// - the error message has a more useful span when `tokens` is empty.
203 ///
204 /// ```text
205 /// #[my_attr(value < 5)]
206 /// ^^^^^^^^^ what gets parsed
207 /// ```
208 ///
209 /// *This function is available only if Syn is built with the `"parsing"`
210 /// feature.*
211 #[cfg(feature = "parsing")]
212 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
parse_args<T: Parse>(&self) -> Result<T>213 pub fn parse_args<T: Parse>(&self) -> Result<T> {
214 self.parse_args_with(T::parse)
215 }
216
217 /// Parse the arguments to the attribute using the given parser.
218 ///
219 /// *This function is available only if Syn is built with the `"parsing"`
220 /// feature.*
221 #[cfg(feature = "parsing")]
222 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
parse_args_with<F: Parser>(&self, parser: F) -> Result<F::Output>223 pub fn parse_args_with<F: Parser>(&self, parser: F) -> Result<F::Output> {
224 let parser = |input: ParseStream| {
225 let args = enter_args(self, input)?;
226 parse::parse_stream(parser, &args)
227 };
228 parser.parse2(self.tokens.clone())
229 }
230
231 /// Parses zero or more outer attributes from the stream.
232 ///
233 /// *This function is available only if Syn is built with the `"parsing"`
234 /// feature.*
235 #[cfg(feature = "parsing")]
236 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
parse_outer(input: ParseStream) -> Result<Vec<Self>>237 pub fn parse_outer(input: ParseStream) -> Result<Vec<Self>> {
238 let mut attrs = Vec::new();
239 while input.peek(Token![#]) {
240 attrs.push(input.call(parsing::single_parse_outer)?);
241 }
242 Ok(attrs)
243 }
244
245 /// Parses zero or more inner attributes from the stream.
246 ///
247 /// *This function is available only if Syn is built with the `"parsing"`
248 /// feature.*
249 #[cfg(feature = "parsing")]
250 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
parse_inner(input: ParseStream) -> Result<Vec<Self>>251 pub fn parse_inner(input: ParseStream) -> Result<Vec<Self>> {
252 let mut attrs = Vec::new();
253 while input.peek(Token![#]) && input.peek2(Token![!]) {
254 attrs.push(input.call(parsing::single_parse_inner)?);
255 }
256 Ok(attrs)
257 }
258 }
259
260 #[cfg(feature = "parsing")]
expected_parentheses(attr: &Attribute) -> String261 fn expected_parentheses(attr: &Attribute) -> String {
262 let style = match attr.style {
263 AttrStyle::Outer => "#",
264 AttrStyle::Inner(_) => "#!",
265 };
266
267 let mut path = String::new();
268 for segment in &attr.path.segments {
269 if !path.is_empty() || attr.path.leading_colon.is_some() {
270 path += "::";
271 }
272 path += &segment.ident.to_string();
273 }
274
275 format!("{}[{}(...)]", style, path)
276 }
277
278 #[cfg(feature = "parsing")]
enter_args<'a>(attr: &Attribute, input: ParseStream<'a>) -> Result<ParseBuffer<'a>>279 fn enter_args<'a>(attr: &Attribute, input: ParseStream<'a>) -> Result<ParseBuffer<'a>> {
280 if input.is_empty() {
281 let expected = expected_parentheses(attr);
282 let msg = format!("expected attribute arguments in parentheses: {}", expected);
283 return Err(crate::error::new2(
284 attr.pound_token.span,
285 attr.bracket_token.span,
286 msg,
287 ));
288 } else if input.peek(Token![=]) {
289 let expected = expected_parentheses(attr);
290 let msg = format!("expected parentheses: {}", expected);
291 return Err(input.error(msg));
292 };
293
294 let content;
295 if input.peek(token::Paren) {
296 parenthesized!(content in input);
297 } else if input.peek(token::Bracket) {
298 bracketed!(content in input);
299 } else if input.peek(token::Brace) {
300 braced!(content in input);
301 } else {
302 return Err(input.error("unexpected token in attribute arguments"));
303 }
304
305 if input.is_empty() {
306 Ok(content)
307 } else {
308 Err(input.error("unexpected token in attribute arguments"))
309 }
310 }
311
312 ast_enum! {
313 /// Distinguishes between attributes that decorate an item and attributes
314 /// that are contained within an item.
315 ///
316 /// *This type is available only if Syn is built with the `"derive"` or `"full"`
317 /// feature.*
318 ///
319 /// # Outer attributes
320 ///
321 /// - `#[repr(transparent)]`
322 /// - `/// # Example`
323 /// - `/** Please file an issue */`
324 ///
325 /// # Inner attributes
326 ///
327 /// - `#![feature(proc_macro)]`
328 /// - `//! # Example`
329 /// - `/*! Please file an issue */`
330 #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
331 pub enum AttrStyle {
332 Outer,
333 Inner(Token![!]),
334 }
335 }
336
337 ast_enum_of_structs! {
338 /// Content of a compile-time structured attribute.
339 ///
340 /// *This type is available only if Syn is built with the `"derive"` or `"full"`
341 /// feature.*
342 ///
343 /// ## Path
344 ///
345 /// A meta path is like the `test` in `#[test]`.
346 ///
347 /// ## List
348 ///
349 /// A meta list is like the `derive(Copy)` in `#[derive(Copy)]`.
350 ///
351 /// ## NameValue
352 ///
353 /// A name-value meta is like the `path = "..."` in `#[path =
354 /// "sys/windows.rs"]`.
355 ///
356 /// # Syntax tree enum
357 ///
358 /// This type is a [syntax tree enum].
359 ///
360 /// [syntax tree enum]: Expr#syntax-tree-enums
361 #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
362 pub enum Meta {
363 Path(Path),
364
365 /// A structured list within an attribute, like `derive(Copy, Clone)`.
366 List(MetaList),
367
368 /// A name-value pair within an attribute, like `feature = "nightly"`.
369 NameValue(MetaNameValue),
370 }
371 }
372
373 ast_struct! {
374 /// A structured list within an attribute, like `derive(Copy, Clone)`.
375 ///
376 /// *This type is available only if Syn is built with the `"derive"` or
377 /// `"full"` feature.*
378 #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
379 pub struct MetaList {
380 pub path: Path,
381 pub paren_token: token::Paren,
382 pub nested: Punctuated<NestedMeta, Token![,]>,
383 }
384 }
385
386 ast_struct! {
387 /// A name-value pair within an attribute, like `feature = "nightly"`.
388 ///
389 /// *This type is available only if Syn is built with the `"derive"` or
390 /// `"full"` feature.*
391 #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
392 pub struct MetaNameValue {
393 pub path: Path,
394 pub eq_token: Token![=],
395 pub lit: Lit,
396 }
397 }
398
399 impl Meta {
400 /// Returns the identifier that begins this structured meta item.
401 ///
402 /// For example this would return the `test` in `#[test]`, the `derive` in
403 /// `#[derive(Copy)]`, and the `path` in `#[path = "sys/windows.rs"]`.
path(&self) -> &Path404 pub fn path(&self) -> &Path {
405 match self {
406 Meta::Path(path) => path,
407 Meta::List(meta) => &meta.path,
408 Meta::NameValue(meta) => &meta.path,
409 }
410 }
411 }
412
413 ast_enum_of_structs! {
414 /// Element of a compile-time attribute list.
415 ///
416 /// *This type is available only if Syn is built with the `"derive"` or `"full"`
417 /// feature.*
418 #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
419 pub enum NestedMeta {
420 /// A structured meta item, like the `Copy` in `#[derive(Copy)]` which
421 /// would be a nested `Meta::Path`.
422 Meta(Meta),
423
424 /// A Rust literal, like the `"new_name"` in `#[rename("new_name")]`.
425 Lit(Lit),
426 }
427 }
428
429 /// Conventional argument type associated with an invocation of an attribute
430 /// macro.
431 ///
432 /// For example if we are developing an attribute macro that is intended to be
433 /// invoked on function items as follows:
434 ///
435 /// ```
436 /// # const IGNORE: &str = stringify! {
437 /// #[my_attribute(path = "/v1/refresh")]
438 /// # };
439 /// pub fn refresh() {
440 /// /* ... */
441 /// }
442 /// ```
443 ///
444 /// The implementation of this macro would want to parse its attribute arguments
445 /// as type `AttributeArgs`.
446 ///
447 /// ```
448 /// # extern crate proc_macro;
449 /// #
450 /// use proc_macro::TokenStream;
451 /// use syn::{parse_macro_input, AttributeArgs, ItemFn};
452 ///
453 /// # const IGNORE: &str = stringify! {
454 /// #[proc_macro_attribute]
455 /// # };
456 /// pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
457 /// let args = parse_macro_input!(args as AttributeArgs);
458 /// let input = parse_macro_input!(input as ItemFn);
459 ///
460 /// /* ... */
461 /// # "".parse().unwrap()
462 /// }
463 /// ```
464 #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))]
465 pub type AttributeArgs = Vec<NestedMeta>;
466
467 pub trait FilterAttrs<'a> {
468 type Ret: Iterator<Item = &'a Attribute>;
469
outer(self) -> Self::Ret470 fn outer(self) -> Self::Ret;
inner(self) -> Self::Ret471 fn inner(self) -> Self::Ret;
472 }
473
474 impl<'a, T> FilterAttrs<'a> for T
475 where
476 T: IntoIterator<Item = &'a Attribute>,
477 {
478 type Ret = iter::Filter<T::IntoIter, fn(&&Attribute) -> bool>;
479
outer(self) -> Self::Ret480 fn outer(self) -> Self::Ret {
481 fn is_outer(attr: &&Attribute) -> bool {
482 match attr.style {
483 AttrStyle::Outer => true,
484 AttrStyle::Inner(_) => false,
485 }
486 }
487 self.into_iter().filter(is_outer)
488 }
489
inner(self) -> Self::Ret490 fn inner(self) -> Self::Ret {
491 fn is_inner(attr: &&Attribute) -> bool {
492 match attr.style {
493 AttrStyle::Inner(_) => true,
494 AttrStyle::Outer => false,
495 }
496 }
497 self.into_iter().filter(is_inner)
498 }
499 }
500
501 #[cfg(feature = "parsing")]
502 pub mod parsing {
503 use super::*;
504 use crate::ext::IdentExt;
505 use crate::parse::{Parse, ParseStream, Result};
506 #[cfg(feature = "full")]
507 use crate::private;
508
single_parse_inner(input: ParseStream) -> Result<Attribute>509 pub fn single_parse_inner(input: ParseStream) -> Result<Attribute> {
510 let content;
511 Ok(Attribute {
512 pound_token: input.parse()?,
513 style: AttrStyle::Inner(input.parse()?),
514 bracket_token: bracketed!(content in input),
515 path: content.call(Path::parse_mod_style)?,
516 tokens: content.parse()?,
517 })
518 }
519
single_parse_outer(input: ParseStream) -> Result<Attribute>520 pub fn single_parse_outer(input: ParseStream) -> Result<Attribute> {
521 let content;
522 Ok(Attribute {
523 pound_token: input.parse()?,
524 style: AttrStyle::Outer,
525 bracket_token: bracketed!(content in input),
526 path: content.call(Path::parse_mod_style)?,
527 tokens: content.parse()?,
528 })
529 }
530
531 #[cfg(feature = "full")]
532 impl private {
attrs(outer: Vec<Attribute>, inner: Vec<Attribute>) -> Vec<Attribute>533 pub(crate) fn attrs(outer: Vec<Attribute>, inner: Vec<Attribute>) -> Vec<Attribute> {
534 let mut attrs = outer;
535 attrs.extend(inner);
536 attrs
537 }
538 }
539
540 // Like Path::parse_mod_style but accepts keywords in the path.
parse_meta_path(input: ParseStream) -> Result<Path>541 fn parse_meta_path(input: ParseStream) -> Result<Path> {
542 Ok(Path {
543 leading_colon: input.parse()?,
544 segments: {
545 let mut segments = Punctuated::new();
546 while input.peek(Ident::peek_any) {
547 let ident = Ident::parse_any(input)?;
548 segments.push_value(PathSegment::from(ident));
549 if !input.peek(Token![::]) {
550 break;
551 }
552 let punct = input.parse()?;
553 segments.push_punct(punct);
554 }
555 if segments.is_empty() {
556 return Err(input.error("expected path"));
557 } else if segments.trailing_punct() {
558 return Err(input.error("expected path segment"));
559 }
560 segments
561 },
562 })
563 }
564
565 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
566 impl Parse for Meta {
parse(input: ParseStream) -> Result<Self>567 fn parse(input: ParseStream) -> Result<Self> {
568 let path = input.call(parse_meta_path)?;
569 parse_meta_after_path(path, input)
570 }
571 }
572
573 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
574 impl Parse for MetaList {
parse(input: ParseStream) -> Result<Self>575 fn parse(input: ParseStream) -> Result<Self> {
576 let path = input.call(parse_meta_path)?;
577 parse_meta_list_after_path(path, input)
578 }
579 }
580
581 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
582 impl Parse for MetaNameValue {
parse(input: ParseStream) -> Result<Self>583 fn parse(input: ParseStream) -> Result<Self> {
584 let path = input.call(parse_meta_path)?;
585 parse_meta_name_value_after_path(path, input)
586 }
587 }
588
589 #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
590 impl Parse for NestedMeta {
parse(input: ParseStream) -> Result<Self>591 fn parse(input: ParseStream) -> Result<Self> {
592 if input.peek(Lit) && !(input.peek(LitBool) && input.peek2(Token![=])) {
593 input.parse().map(NestedMeta::Lit)
594 } else if input.peek(Ident::peek_any)
595 || input.peek(Token![::]) && input.peek3(Ident::peek_any)
596 {
597 input.parse().map(NestedMeta::Meta)
598 } else {
599 Err(input.error("expected identifier or literal"))
600 }
601 }
602 }
603
parse_meta_after_path(path: Path, input: ParseStream) -> Result<Meta>604 pub fn parse_meta_after_path(path: Path, input: ParseStream) -> Result<Meta> {
605 if input.peek(token::Paren) {
606 parse_meta_list_after_path(path, input).map(Meta::List)
607 } else if input.peek(Token![=]) {
608 parse_meta_name_value_after_path(path, input).map(Meta::NameValue)
609 } else {
610 Ok(Meta::Path(path))
611 }
612 }
613
parse_meta_list_after_path(path: Path, input: ParseStream) -> Result<MetaList>614 fn parse_meta_list_after_path(path: Path, input: ParseStream) -> Result<MetaList> {
615 let content;
616 Ok(MetaList {
617 path,
618 paren_token: parenthesized!(content in input),
619 nested: content.parse_terminated(NestedMeta::parse)?,
620 })
621 }
622
parse_meta_name_value_after_path(path: Path, input: ParseStream) -> Result<MetaNameValue>623 fn parse_meta_name_value_after_path(path: Path, input: ParseStream) -> Result<MetaNameValue> {
624 Ok(MetaNameValue {
625 path,
626 eq_token: input.parse()?,
627 lit: input.parse()?,
628 })
629 }
630 }
631
632 #[cfg(feature = "printing")]
633 mod printing {
634 use super::*;
635 use proc_macro2::TokenStream;
636 use quote::ToTokens;
637
638 #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
639 impl ToTokens for Attribute {
to_tokens(&self, tokens: &mut TokenStream)640 fn to_tokens(&self, tokens: &mut TokenStream) {
641 self.pound_token.to_tokens(tokens);
642 if let AttrStyle::Inner(b) = &self.style {
643 b.to_tokens(tokens);
644 }
645 self.bracket_token.surround(tokens, |tokens| {
646 self.path.to_tokens(tokens);
647 self.tokens.to_tokens(tokens);
648 });
649 }
650 }
651
652 #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
653 impl ToTokens for MetaList {
to_tokens(&self, tokens: &mut TokenStream)654 fn to_tokens(&self, tokens: &mut TokenStream) {
655 self.path.to_tokens(tokens);
656 self.paren_token.surround(tokens, |tokens| {
657 self.nested.to_tokens(tokens);
658 })
659 }
660 }
661
662 #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
663 impl ToTokens for MetaNameValue {
to_tokens(&self, tokens: &mut TokenStream)664 fn to_tokens(&self, tokens: &mut TokenStream) {
665 self.path.to_tokens(tokens);
666 self.eq_token.to_tokens(tokens);
667 self.lit.to_tokens(tokens);
668 }
669 }
670 }
671