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