chore: remove AsRef impl from the StringLiteral macro since I don't like that behavior, reorganize it to prevent leaking proc-macro2 / syn / quote

This commit is contained in:
J / Jacob Babich
2026-07-12 13:54:57 -04:00
parent 3e063901be
commit 3f94015b4f
7 changed files with 94 additions and 89 deletions

7
Cargo.lock generated
View File

@@ -2230,6 +2230,13 @@ dependencies = [
[[package]] [[package]]
name = "string-literal-macros" name = "string-literal-macros"
version = "0.1.0" version = "0.1.0"
dependencies = [
"string-literal-macros-impl",
]
[[package]]
name = "string-literal-macros-impl"
version = "0.1.0"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",

View File

@@ -12,6 +12,7 @@ members = [
"python-utils-macros-impl", "python-utils-macros-impl",
"string-literal", "string-literal",
"string-literal-macros", "string-literal-macros",
"string-literal-macros-impl",
] ]
resolver = "3" resolver = "3"

View File

@@ -0,0 +1,11 @@
[package]
name = "string-literal-macros-impl"
version = "0.1.0"
edition = "2024"
license.workspace = true
[dependencies]
darling = "0.23.0"
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }

View File

@@ -0,0 +1,71 @@
use darling::FromDeriveInput;
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use syn::{Ident, LitStr, Path, parse_quote, parse2};
#[derive(FromDeriveInput)]
#[darling(supports(struct_unit))]
#[darling(attributes(string_literal))]
struct StringLiteral {
ident: Ident,
value: String,
}
impl ToTokens for StringLiteral {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self { ident, value } = self;
let display_trait: Path = parse_quote!(::std::fmt::Display);
let from_trait: Path = parse_quote!(::core::convert::From);
let from_str_trait: Path = parse_quote!(::std::str::FromStr);
let result: Path = parse_quote!(::core::result::Result);
let wrong_literal_error_generic: Path = parse_quote!(::string_literal::WrongLiteralError);
let wrong_literal_error: Path = parse_quote!(#wrong_literal_error_generic<#ident>);
let write_macro: Path = parse_quote!(::std::write);
let value_literal = LitStr::new(value.as_str(), Span::call_site());
let output = quote! {
impl #from_trait<&#ident> for &'static str {
fn from(unit: &#ident) -> Self {
#value_literal
}
}
impl #from_str_trait for #ident {
type Err = #wrong_literal_error;
fn from_str(s: &str) -> #result<Self, Self::Err> {
if s == #value_literal {
Ok(Self)
} else {
Err(#wrong_literal_error_generic { actual: s.to_owned(), expected: #ident })
}
}
}
impl #display_trait for #ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
#write_macro!(f, "state_changed")
}
}
};
dbg!(&output.to_string());
tokens.extend(output);
}
}
pub fn string_literal_impl(input: TokenStream) -> TokenStream {
let derive_input = match parse2(input) {
Ok(derive_input) => derive_input,
Err(error) => return error.into_compile_error(),
};
let string_literal = match StringLiteral::from_derive_input(&derive_input) {
Ok(string_literal) => string_literal,
Err(error) => return error.write_errors(),
};
quote! { #string_literal }
}

View File

@@ -8,7 +8,4 @@ license.workspace = true
proc-macro = true proc-macro = true
[dependencies] [dependencies]
darling = "0.23.0" string-literal-macros-impl = { path = "../string-literal-macros-impl" }
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }

View File

@@ -1,88 +1,6 @@
use darling::FromDeriveInput;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
use quote::{ToTokens, quote};
use syn::{Ident, LitStr, Path, parse_quote, parse2};
#[derive(FromDeriveInput)]
#[darling(supports(struct_unit))]
#[darling(attributes(string_literal))]
struct StringLiteral {
ident: Ident,
value: String,
}
impl ToTokens for StringLiteral {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let Self { ident, value } = self;
dbg!("reached this point");
let as_ref_trait: Path = parse_quote!(::core::convert::AsRef);
let display_trait: Path = parse_quote!(::std::fmt::Display);
let from_trait: Path = parse_quote!(::core::convert::From);
let from_str_trait: Path = parse_quote!(::std::str::FromStr);
let result: Path = parse_quote!(::core::result::Result);
let wrong_literal_error_generic: Path = parse_quote!(::string_literal::WrongLiteralError);
let wrong_literal_error: Path = parse_quote!(#wrong_literal_error_generic<#ident>);
let write_macro: Path = parse_quote!(::std::write);
let value_literal = LitStr::new(value.as_str(), Span2::call_site());
dbg!("reached this point");
let output = quote! {
impl #from_trait<#ident> for &'static str {
fn from(unit: #ident) -> Self {
#value_literal
}
}
impl #from_str_trait for #ident {
type Err = #wrong_literal_error;
fn from_str(s: &str) -> #result<Self, Self::Err> {
if s == #value_literal {
Ok(Self)
} else {
Err(#wrong_literal_error_generic { actual: s.to_owned(), expected: #ident })
}
}
}
impl #display_trait for #ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
#write_macro!(f, "state_changed")
}
}
impl #as_ref_trait<str> for #ident {
fn as_ref(&self) -> &str {
#value_literal
}
}
};
dbg!(&output.to_string());
tokens.extend(output);
}
}
fn string_literal_impl(input: TokenStream2) -> TokenStream2 {
let derive_input = match parse2(input) {
Ok(derive_input) => derive_input,
Err(error) => return error.into_compile_error(),
};
let string_literal = match StringLiteral::from_derive_input(&derive_input) {
Ok(string_literal) => string_literal,
Err(error) => return error.write_errors(),
};
quote! { #string_literal }
}
#[proc_macro_derive(StringLiteral, attributes(string_literal))] #[proc_macro_derive(StringLiteral, attributes(string_literal))]
pub fn string_literal(input: TokenStream) -> TokenStream { pub fn string_literal(input: TokenStream) -> TokenStream {
string_literal_impl(input.into()).into() string_literal_macros_impl::string_literal_impl(input.into()).into()
} }

View File

@@ -11,11 +11,11 @@ pub struct WrongLiteralError<S> {
impl<S> Display for WrongLiteralError<S> impl<S> Display for WrongLiteralError<S>
where where
S: AsRef<str>, &'static str: for<'a> From<&'a S>,
{ {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { actual, expected } = self; let Self { actual, expected } = self;
let expected_str = expected.as_ref(); let expected_str = <&'static str>::from(&expected);
write!(f, "expected {expected_str:?} but got {actual:?}") write!(f, "expected {expected_str:?} but got {actual:?}")
} }