1 | use proc_macro2::TokenStream; |
2 | use quote::{format_ident, quote, quote_spanned}; |
3 | use syn::spanned::Spanned; |
4 | use syn::{Data, Fields, Ident}; |
5 | use synstructure::Structure; |
6 | |
7 | fn 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 | |
85 | pub(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 | } |
151 | |