1use proc_macro::TokenStream;
4use quote::quote;
5use syn::{Data, DeriveInput, Fields, parse_macro_input};
6
7#[proc_macro_derive(Inspect, attributes(inspect))]
8pub fn derive_inspect(input: TokenStream) -> TokenStream {
9 let input = parse_macro_input!(input as DeriveInput);
10
11 let name = &input.ident;
12 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
13
14 let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::parse_quote!(where));
16
17 for param in &input.generics.params {
18 if let syn::GenericParam::Type(type_param) = param {
19 let ident = &type_param.ident;
20 where_clause.predicates.push(syn::parse_quote!(#ident: inspect_core::Inspect));
21 }
22 }
23
24 let inspect_impl = match &input.data {
25 Data::Struct(data_struct) => impl_struct(name, &data_struct.fields),
26 Data::Enum(data_enum) => impl_enum(name, data_enum),
27 Data::Union(_) => {
28 return syn::Error::new_spanned(name, "Inspect cannot be derived for unions")
29 .to_compile_error()
30 .into();
31 }
32 };
33
34 let expanded = quote! {
35 impl #impl_generics inspect_core::Inspect for #name #ty_generics #where_clause {
36 fn inspect(&self, cx: &mut inspect_core::InspectCx<'_>) -> inspect_core::ValueRef<'_> {
37 #inspect_impl
38 }
39 }
40 };
41
42 TokenStream::from(expanded)
43}
44
45fn impl_struct(name: &syn::Ident, fields: &Fields) -> proc_macro2::TokenStream {
46 let type_name = name.to_string();
47
48 match fields {
49 Fields::Named(fields_named) => {
50 let field_inspections: Vec<_> = fields_named
51 .named
52 .iter()
53 .enumerate()
54 .filter_map(|(idx, field)| {
55 let attrs = FieldAttributes::parse(&field.attrs);
56
57 if attrs.skip {
58 return None;
59 }
60
61 let field_name = field.ident.as_ref().unwrap();
62 let field_name_str = attrs.rename.unwrap_or_else(|| field_name.to_string());
63
64 let sensitivity = match (attrs.secret, attrs.sensitive) {
65 (true, _) => quote!(inspect_core::Sensitivity::Secret),
66 (_, true) => quote!(inspect_core::Sensitivity::Sensitive),
67 _ => quote!(inspect_core::Sensitivity::Normal),
68 };
69
70 Some(quote! {
71 (
72 inspect_core::FieldInfo::named(#field_name_str, #idx)
73 .with_sensitivity(#sensitivity),
74 self.#field_name.inspect(cx)
75 )
76 })
77 })
78 .collect();
79
80 quote! {
81 cx.visit_node();
82 let fields = vec![#(#field_inspections),*];
83
84 inspect_core::ValueRef::with_children(
85 inspect_core::Kind::Struct,
86 inspect_core::TypeInfo::new(#type_name),
87 inspect_core::Children::direct(fields),
88 )
89 }
90 }
91 Fields::Unnamed(fields_unnamed) => {
92 let field_count = fields_unnamed.unnamed.len();
93 let field_inspections: Vec<_> = (0..field_count)
94 .map(|idx| {
95 let idx_token = syn::Index::from(idx);
96 quote! {
97 (
98 inspect_core::FieldInfo::tuple(#idx),
99 self.#idx_token.inspect(cx)
100 )
101 }
102 })
103 .collect();
104
105 quote! {
106 cx.visit_node();
107 let fields = vec![#(#field_inspections),*];
108
109 inspect_core::ValueRef::with_children(
110 inspect_core::Kind::TupleStruct,
111 inspect_core::TypeInfo::new(#type_name),
112 inspect_core::Children::direct(fields),
113 )
114 }
115 }
116 Fields::Unit => {
117 quote! {
118 cx.visit_node();
119 inspect_core::ValueRef::with_type(
120 inspect_core::Kind::Struct,
121 inspect_core::TypeInfo::new(#type_name),
122 )
123 }
124 }
125 }
126}
127
128fn impl_enum(name: &syn::Ident, data_enum: &syn::DataEnum) -> proc_macro2::TokenStream {
129 let type_name = name.to_string();
130
131 let variant_arms: Vec<_> = data_enum
132 .variants
133 .iter()
134 .enumerate()
135 .map(|(variant_idx, variant)| {
136 let variant_name = &variant.ident;
137 let variant_name_str = variant_name.to_string();
138
139 match &variant.fields {
140 Fields::Named(fields) => {
141 let field_names: Vec<_> = fields.named.iter()
142 .map(|f| f.ident.as_ref().unwrap())
143 .collect();
144
145 let field_inspections: Vec<_> = fields.named.iter()
146 .enumerate()
147 .map(|(idx, field)| {
148 let field_name = field.ident.as_ref().unwrap();
149 let field_name_str = field_name.to_string();
150
151 quote! {
152 (
153 inspect_core::FieldInfo::named(#field_name_str, #idx),
154 #field_name.inspect(cx)
155 )
156 }
157 })
158 .collect();
159
160 quote! {
161 #name::#variant_name { #(#field_names),* } => {
162 cx.visit_node();
163 let variant = inspect_core::VariantInfo::new(#variant_name_str, #variant_idx);
164 let fields = vec![#(#field_inspections),*];
165
166 inspect_core::ValueRef::with_children(
167 inspect_core::Kind::Enum,
168 inspect_core::TypeInfo::new(#type_name),
169 inspect_core::Children::direct(fields),
170 )
171 .with_variant(variant)
172 }
173 }
174 }
175 Fields::Unnamed(fields) => {
176 let field_count = fields.unnamed.len();
177 let field_bindings: Vec<_> = (0..field_count)
178 .map(|i| quote::format_ident!("field_{}", i))
179 .collect();
180
181 let field_inspections: Vec<_> = field_bindings.iter()
182 .enumerate()
183 .map(|(idx, binding)| {
184 quote! {
185 (
186 inspect_core::FieldInfo::tuple(#idx),
187 #binding.inspect(cx)
188 )
189 }
190 })
191 .collect();
192
193 quote! {
194 #name::#variant_name(#(#field_bindings),*) => {
195 cx.visit_node();
196 let variant = inspect_core::VariantInfo::new(#variant_name_str, #variant_idx);
197 let fields = vec![#(#field_inspections),*];
198
199 inspect_core::ValueRef::with_children(
200 inspect_core::Kind::Enum,
201 inspect_core::TypeInfo::new(#type_name),
202 inspect_core::Children::direct(fields),
203 )
204 .with_variant(variant)
205 }
206 }
207 }
208 Fields::Unit => {
209 quote! {
210 #name::#variant_name => {
211 cx.visit_node();
212 let variant = inspect_core::VariantInfo::new(#variant_name_str, #variant_idx);
213
214 inspect_core::ValueRef::with_children(
215 inspect_core::Kind::Enum,
216 inspect_core::TypeInfo::new(#type_name),
217 inspect_core::Children::direct(vec![]),
218 )
219 .with_variant(variant)
220 }
221 }
222 }
223 }
224 })
225 .collect();
226
227 quote! {
228 match self {
229 #(#variant_arms),*
230 }
231 }
232}
233
234#[derive(Default)]
235struct FieldAttributes {
236 skip: bool,
237 secret: bool,
238 sensitive: bool,
239 rename: Option<String>,
240}
241
242impl FieldAttributes {
243 fn parse(attrs: &[syn::Attribute]) -> Self {
244 let mut result = Self::default();
245
246 for attr in attrs {
247 if !attr.path().is_ident("inspect") {
248 continue;
249 }
250
251 let _ = attr.parse_nested_meta(|meta| {
252 if meta.path.is_ident("skip") {
253 result.skip = true;
254 } else if meta.path.is_ident("secret") {
255 result.secret = true;
256 } else if meta.path.is_ident("sensitive") {
257 result.sensitive = true;
258 } else if meta.path.is_ident("rename") {
259 if let Ok(value) = meta.value() {
260 if let Ok(s) = value.parse::<syn::LitStr>() {
261 result.rename = Some(s.value());
262 }
263 }
264 }
265 Ok(())
266 });
267 }
268
269 result
270 }
271}