Skip to main content

vtcode_macros/
lib.rs

1#![allow(
2    missing_docs,
3    reason = "Intentional compatibility, platform, or test-only suppression."
4)]
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::{format_ident, quote};
8use syn::{Data, DataEnum, DeriveInput, Fields, parse_macro_input};
9
10/// Derive macro that generates the same boilerplate as the `string_newtype!`
11/// declarative macro. Apply to a tuple struct wrapping a single `String` field.
12///
13/// Generates:
14/// - Inherent methods: `new()`, `as_str()`, `into_inner()`
15/// - `Deref<Target = str>`
16/// - `Borrow<str>`
17/// - `AsRef<str>`
18/// - `Display`
19/// - `From<String>`, `From<&str>`, `From<Self> for String`
20///
21/// # Example
22///
23/// ```rust,ignore
24/// #[derive(Debug, Clone, Serialize, Deserialize, StringNewtype)]
25/// #[serde(transparent)]
26/// pub struct SessionId(String);
27/// ```
28#[proc_macro_derive(StringNewtype)]
29pub fn derive_string_newtype(input: TokenStream) -> TokenStream {
30    let input = parse_macro_input!(input as DeriveInput);
31    impl_string_newtype(&input).unwrap_or_else(|err| err.to_compile_error().into())
32}
33
34fn impl_string_newtype(input: &DeriveInput) -> syn::Result<TokenStream> {
35    let name = &input.ident;
36
37    // Validate: must be a tuple struct with exactly one String field.
38    let field_type = match &input.data {
39        Data::Struct(data) => match &data.fields {
40            Fields::Unnamed(fields) => {
41                if fields.unnamed.len() != 1 {
42                    return Err(syn::Error::new_spanned(
43                        name,
44                        "StringNewtype requires a tuple struct with exactly one field",
45                    ));
46                }
47                let Some(field) = fields.unnamed.first() else {
48                    return Err(syn::Error::new_spanned(
49                        name,
50                        "StringNewtype requires a tuple struct with exactly one field",
51                    ));
52                };
53                &field.ty
54            }
55            _ => {
56                return Err(syn::Error::new_spanned(name, "StringNewtype can only be derived for tuple structs"));
57            }
58        },
59        _ => {
60            return Err(syn::Error::new_spanned(name, "StringNewtype can only be derived for structs"));
61        }
62    };
63
64    // Verify the inner type is String.
65    if !is_string_type(field_type) {
66        return Err(syn::Error::new_spanned(field_type, "StringNewtype requires the inner type to be String"));
67    }
68
69    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
70
71    let output = quote! {
72        impl #impl_generics #name #ty_generics #where_clause {
73            /// Create a new instance from any value that converts to `String`.
74            pub fn new(value: impl Into<String>) -> Self {
75                Self(value.into())
76            }
77
78            /// Borrow the inner string as a `&str`.
79            pub fn as_str(&self) -> &str {
80                &self.0
81            }
82
83            /// Consume the wrapper and return the inner `String`.
84            pub fn into_inner(self) -> String {
85                self.0
86            }
87        }
88
89        impl #impl_generics std::ops::Deref for #name #ty_generics #where_clause {
90            type Target = str;
91
92            fn deref(&self) -> &Self::Target {
93                &self.0
94            }
95        }
96
97        impl #impl_generics std::borrow::Borrow<str> for #name #ty_generics #where_clause {
98            fn borrow(&self) -> &str {
99                &self.0
100            }
101        }
102
103        impl #impl_generics AsRef<str> for #name #ty_generics #where_clause {
104            fn as_ref(&self) -> &str {
105                &self.0
106            }
107        }
108
109        impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
110            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111                self.0.fmt(f)
112            }
113        }
114
115        impl #impl_generics From<String> for #name #ty_generics #where_clause {
116            fn from(value: String) -> Self {
117                Self(value)
118            }
119        }
120
121        impl #impl_generics From<&str> for #name #ty_generics #where_clause {
122            fn from(value: &str) -> Self {
123                Self(value.to_string())
124            }
125        }
126
127        impl #impl_generics From<#name #ty_generics> for String #where_clause {
128            fn from(value: #name #ty_generics) -> Self {
129                value.0
130            }
131        }
132    };
133
134    Ok(output.into())
135}
136
137fn is_string_type(ty: &syn::Type) -> bool {
138    if let syn::Type::Path(type_path) = ty
139        && type_path.qself.is_none()
140        && type_path.path.segments.len() == 1
141    {
142        return type_path.path.segments.first().is_some_and(|segment| segment.ident == "String");
143    }
144    false
145}
146
147/// Derive macro equivalent to `#[derive(Debug)]` but with `#[inline(never)]` on
148/// the generated `fmt` implementation.
149///
150/// Rust's built-in `Debug` derive emits `#[inline]` on `fmt`. For large or deeply
151/// nested types — typically error enums formatted on fan-out paths — that lets
152/// `rustc` inline the whole `Debug` tree into every `{:?}` / `?err` call site,
153/// which can bloat binary size. This derive preserves the exact `Debug` output and
154/// only changes the inlining hint.
155///
156/// Use it for large/nested types whose `Debug` is formatted in hot or fan-out
157/// paths; keep `#[derive(Debug)]` for small, hot, leaf types. See
158/// `docs/development/rust-performance-principles.md`, "Derived trait impls are
159/// `#[inline]`".
160///
161/// # Example
162///
163/// ```rust,ignore
164/// use vtcode_macros::DebugNoInline;
165///
166/// #[derive(DebugNoInline)]
167/// pub struct Widgets {
168///     foo: u32,
169///     bar: usize,
170/// }
171///
172/// assert_eq!(
173///     format!("{:?}", Widgets { foo: 1, bar: 2 }),
174///     "Widgets { foo: 1, bar: 2 }"
175/// );
176/// ```
177#[proc_macro_derive(DebugNoInline)]
178pub fn derive_debug_no_inline(input: TokenStream) -> TokenStream {
179    let input = parse_macro_input!(input as DeriveInput);
180    match impl_debug_no_inline(&input) {
181        Ok(tokens) => tokens.into(),
182        Err(err) => err.to_compile_error().into(),
183    }
184}
185
186fn impl_debug_no_inline(input: &DeriveInput) -> syn::Result<TokenStream2> {
187    let name = &input.ident;
188
189    // Mirror the built-in derive: bound every type parameter on `Debug`.
190    let mut generics = input.generics.clone();
191    for param in generics.type_params_mut() {
192        param.bounds.push(syn::parse_quote!(::core::fmt::Debug));
193    }
194    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
195
196    let body = match &input.data {
197        Data::Struct(data) => fmt_body_struct(&name.to_string(), &data.fields),
198        Data::Enum(data) => fmt_body_enum(data),
199        Data::Union(_) => {
200            return Err(syn::Error::new_spanned(name, "DebugNoInline cannot be derived for unions"));
201        }
202    };
203
204    Ok(quote! {
205        #[automatically_derived]
206        impl #impl_generics ::core::fmt::Debug for #name #ty_generics #where_clause {
207            #[inline(never)]
208            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
209                #body
210            }
211        }
212    })
213}
214
215/// Debug name for a field: strip a raw-identifier prefix so `r#type` renders as
216/// `type`, matching the built-in derive.
217fn field_name(ident: &syn::Ident) -> String {
218    let raw = ident.to_string();
219    raw.strip_prefix("r#").unwrap_or(raw.as_str()).to_string()
220}
221
222fn fmt_body_struct(type_name: &str, fields: &Fields) -> TokenStream2 {
223    match fields {
224        Fields::Unit => quote! { f.write_str(#type_name) },
225        Fields::Named(named) => {
226            let calls = named.named.iter().filter_map(|field| {
227                let ident = field.ident.as_ref()?;
228                let field_name = field_name(ident);
229                Some(quote! { __debug_builder.field(#field_name, &self.#ident); })
230            });
231            let builder = debug_builder_init("debug_struct", type_name);
232            quote! {
233                #builder
234                #(#calls)*
235                __debug_builder.finish()
236            }
237        }
238        Fields::Unnamed(unnamed) => {
239            let calls = unnamed.unnamed.iter().enumerate().map(|(index, _)| {
240                let index = syn::Index::from(index);
241                quote! { __debug_builder.field(&self.#index); }
242            });
243            let builder = debug_builder_init("debug_tuple", type_name);
244            quote! {
245                #builder
246                #(#calls)*
247                __debug_builder.finish()
248            }
249        }
250    }
251}
252
253fn fmt_body_enum(data: &DataEnum) -> TokenStream2 {
254    // An uninhabited enum has no variants to match. `match self {}` is not
255    // exhaustive because `&Never` is inhabited, so dereference: `match *self {}`.
256    if data.variants.is_empty() {
257        return quote! { match *self {} };
258    }
259
260    let arms = data.variants.iter().map(|variant| {
261        let variant_ident = &variant.ident;
262        let variant_name = variant.ident.to_string();
263        match &variant.fields {
264            Fields::Unit => quote! {
265                Self::#variant_ident => f.write_str(#variant_name),
266            },
267            Fields::Named(named) => {
268                let pairs: Vec<(syn::Ident, syn::Ident)> = named
269                    .named
270                    .iter()
271                    .enumerate()
272                    .filter_map(|(index, field)| {
273                        let ident = field.ident.as_ref()?.clone();
274                        Some((ident, format_ident!("__self_{index}")))
275                    })
276                    .collect();
277                let pattern = pairs.iter().map(|(ident, binding)| quote! { #ident: #binding });
278                let calls = pairs.iter().map(|(ident, binding)| {
279                    let field_name = field_name(ident);
280                    quote! { __debug_builder.field(#field_name, #binding); }
281                });
282                let builder = debug_builder_init("debug_struct", &variant_name);
283                quote! {
284                    Self::#variant_ident { #(#pattern),* } => {
285                        #builder
286                        #(#calls)*
287                        __debug_builder.finish()
288                    }
289                }
290            }
291            Fields::Unnamed(unnamed) => {
292                let bindings: Vec<syn::Ident> = (0..unnamed.unnamed.len())
293                    .map(|index| format_ident!("__self_{index}"))
294                    .collect();
295                let calls = bindings.iter().map(|binding| quote! { __debug_builder.field(#binding); });
296                let builder = debug_builder_init("debug_tuple", &variant_name);
297                quote! {
298                    Self::#variant_ident( #(#bindings),* ) => {
299                        #builder
300                        #(#calls)*
301                        __debug_builder.finish()
302                    }
303                }
304            }
305        }
306    });
307    quote! {
308        match self {
309            #(#arms)*
310        }
311    }
312}
313
314/// Emit `let mut __debug_builder = f.<kind>("<name>");`. The builder is always
315/// `mut` because `field(...)` and `finish(...)` both take `&mut self`.
316fn debug_builder_init(kind: &str, name: &str) -> TokenStream2 {
317    let kind = format_ident!("{kind}");
318    quote! { let mut __debug_builder = f.#kind(#name); }
319}