rustc_macros/
print_attribute.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote, quote_spanned};
3use syn::spanned::Spanned;
4use syn::{Data, Fields, Ident};
5use synstructure::Structure;
6
7fn print_fields(name: &Ident, fields: &Fields) -> (TokenStream, TokenStream, TokenStream) {
8    let string_name = name.to_string();
9    let mut disps = vec![quote! {let mut __printed_anything = false;}];
10
11    match fields {
12        Fields::Named(fields_named) => {
13            let mut field_names = Vec::new();
14
15            for field in &fields_named.named {
16                let name = field.ident.as_ref().unwrap();
17                let string_name = name.to_string();
18                disps.push(quote! {
19                    if #name.should_render() {
20                        if __printed_anything {
21                            __p.word_space(",");
22                        }
23                        __p.word(#string_name);
24                        __p.word(":");
25                        __p.nbsp();
26                        __printed_anything = true;
27                    }
28                    #name.print_attribute(__p);
29                });
30                field_names.push(name);
31            }
32
33            (
34                quote! { {#(#field_names),*} },
35                quote! {
36                    __p.word(#string_name);
37                    if true #(&& !#field_names.should_render())* {
38                        return;
39                    }
40
41                    __p.nbsp();
42                    __p.word("{");
43                    #(#disps)*
44                    __p.word("}");
45                },
46                quote! { true },
47            )
48        }
49        Fields::Unnamed(fields_unnamed) => {
50            let mut field_names = Vec::new();
51
52            for idx in 0..fields_unnamed.unnamed.len() {
53                let name = format_ident!("f{idx}");
54                disps.push(quote! {
55                    if #name.should_render() {
56                        if __printed_anything {
57                            __p.word_space(",");
58                        }
59                        __printed_anything = true;
60                    }
61                    #name.print_attribute(__p);
62                });
63                field_names.push(name);
64            }
65
66            (
67                quote! { (#(#field_names),*) },
68                quote! {
69                    __p.word(#string_name);
70
71                    if true #(&& !#field_names.should_render())* {
72                        return;
73                    }
74
75                    __p.popen();
76                    #(#disps)*
77                    __p.pclose();
78                },
79                quote! { true },
80            )
81        }
82        Fields::Unit => (quote! {}, quote! { __p.word(#string_name) }, quote! { true }),
83    }
84}
85
86pub(crate) fn print_attribute(input: Structure<'_>) -> TokenStream {
87    let span_error = |span, message: &str| {
88        quote_spanned! { span => const _: () = ::core::compile_error!(#message); }
89    };
90
91    // Must be applied to an enum type.
92    let (code, printed) = match &input.ast().data {
93        Data::Enum(e) => {
94            let (arms, printed) = e
95                .variants
96                .iter()
97                .map(|x| {
98                    let ident = &x.ident;
99                    let (pat, code, printed) = print_fields(ident, &x.fields);
100
101                    (
102                        quote! {
103                            Self::#ident #pat => {#code}
104                        },
105                        quote! {
106                            Self::#ident #pat => {#printed}
107                        },
108                    )
109                })
110                .unzip::<_, _, Vec<_>, Vec<_>>();
111
112            (
113                quote! {
114                    match self {
115                        #(#arms)*
116                    }
117                },
118                quote! {
119                    match self {
120                        #(#printed)*
121                    }
122                },
123            )
124        }
125        Data::Struct(s) => {
126            let (pat, code, printed) = print_fields(&input.ast().ident, &s.fields);
127            (
128                quote! {
129                    let Self #pat = self;
130                    #code
131                },
132                quote! {
133                    let Self #pat = self;
134                    #printed
135                },
136            )
137        }
138        Data::Union(u) => {
139            return span_error(u.union_token.span(), "can't derive PrintAttribute on unions");
140        }
141    };
142
143    #[allow(keyword_idents_2024)]
144    input.gen_impl(quote! {
145        #[allow(unused)]
146        gen impl PrintAttribute for @Self {
147            fn should_render(&self) -> bool { #printed }
148            fn print_attribute(&self, __p: &mut rustc_ast_pretty::pp::Printer) { #code }
149        }
150    })
151}