Skip to main content

vtcode_macros/
lib.rs

1#![allow(missing_docs)]
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{Data, DeriveInput, Fields, parse_macro_input};
5
6/// Derive macro that generates the same boilerplate as the `string_newtype!`
7/// declarative macro. Apply to a tuple struct wrapping a single `String` field.
8///
9/// Generates:
10/// - Inherent methods: `new()`, `as_str()`, `into_inner()`
11/// - `Deref<Target = str>`
12/// - `Borrow<str>`
13/// - `AsRef<str>`
14/// - `Display`
15/// - `From<String>`, `From<&str>`, `From<Self> for String`
16///
17/// # Example
18///
19/// ```rust,ignore
20/// #[derive(Debug, Clone, Serialize, Deserialize, StringNewtype)]
21/// #[serde(transparent)]
22/// pub struct SessionId(String);
23/// ```
24#[proc_macro_derive(StringNewtype)]
25pub fn derive_string_newtype(input: TokenStream) -> TokenStream {
26    let input = parse_macro_input!(input as DeriveInput);
27    impl_string_newtype(&input).unwrap_or_else(|err| err.to_compile_error().into())
28}
29
30fn impl_string_newtype(input: &DeriveInput) -> syn::Result<TokenStream> {
31    let name = &input.ident;
32
33    // Validate: must be a tuple struct with exactly one String field.
34    let field_type = match &input.data {
35        Data::Struct(data) => match &data.fields {
36            Fields::Unnamed(fields) => {
37                if fields.unnamed.len() != 1 {
38                    return Err(syn::Error::new_spanned(
39                        name,
40                        "StringNewtype requires a tuple struct with exactly one field",
41                    ));
42                }
43                let Some(field) = fields.unnamed.first() else {
44                    return Err(syn::Error::new_spanned(
45                        name,
46                        "StringNewtype requires a tuple struct with exactly one field",
47                    ));
48                };
49                &field.ty
50            }
51            _ => {
52                return Err(syn::Error::new_spanned(name, "StringNewtype can only be derived for tuple structs"));
53            }
54        },
55        _ => {
56            return Err(syn::Error::new_spanned(name, "StringNewtype can only be derived for structs"));
57        }
58    };
59
60    // Verify the inner type is String.
61    if !is_string_type(field_type) {
62        return Err(syn::Error::new_spanned(field_type, "StringNewtype requires the inner type to be String"));
63    }
64
65    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
66
67    let output = quote! {
68        impl #impl_generics #name #ty_generics #where_clause {
69            /// Create a new instance from any value that converts to `String`.
70            pub fn new(value: impl Into<String>) -> Self {
71                Self(value.into())
72            }
73
74            /// Borrow the inner string as a `&str`.
75            pub fn as_str(&self) -> &str {
76                &self.0
77            }
78
79            /// Consume the wrapper and return the inner `String`.
80            pub fn into_inner(self) -> String {
81                self.0
82            }
83        }
84
85        impl #impl_generics std::ops::Deref for #name #ty_generics #where_clause {
86            type Target = str;
87
88            fn deref(&self) -> &Self::Target {
89                &self.0
90            }
91        }
92
93        impl #impl_generics std::borrow::Borrow<str> for #name #ty_generics #where_clause {
94            fn borrow(&self) -> &str {
95                &self.0
96            }
97        }
98
99        impl #impl_generics AsRef<str> for #name #ty_generics #where_clause {
100            fn as_ref(&self) -> &str {
101                &self.0
102            }
103        }
104
105        impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
106            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107                self.0.fmt(f)
108            }
109        }
110
111        impl #impl_generics From<String> for #name #ty_generics #where_clause {
112            fn from(value: String) -> Self {
113                Self(value)
114            }
115        }
116
117        impl #impl_generics From<&str> for #name #ty_generics #where_clause {
118            fn from(value: &str) -> Self {
119                Self(value.to_string())
120            }
121        }
122
123        impl #impl_generics From<#name #ty_generics> for String #where_clause {
124            fn from(value: #name #ty_generics) -> Self {
125                value.0
126            }
127        }
128    };
129
130    Ok(output.into())
131}
132
133fn is_string_type(ty: &syn::Type) -> bool {
134    if let syn::Type::Path(type_path) = ty
135        && type_path.qself.is_none()
136        && type_path.path.segments.len() == 1
137    {
138        return type_path.path.segments[0].ident == "String";
139    }
140    false
141}