use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{DeriveInput, parse_macro_input, parse_quote};
pub fn derive_table(input: TokenStream) -> TokenStream {
let derive_input = parse_macro_input!(input as DeriveInput);
TokenStream::from(expand_derive_table(derive_input))
}
fn expand_derive_table(mut derive_input: DeriveInput) -> TokenStream2 {
derive_input
.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &derive_input.ident;
let (impl_generics, type_generics, where_clause) = &derive_input.generics.split_for_impl();
let trait_path = quote! { suon_database::Table };
quote! {
impl #impl_generics #trait_path for #struct_name #type_generics #where_clause {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derive_table_adds_impl_and_thread_safety_bounds() {
let input: DeriveInput = match syn::parse_str("struct Inventory<T>(T);") {
Ok(input) => input,
Err(error) => panic!("Input should parse: {error}"),
};
let output = expand_derive_table(input).to_string();
assert!(
output.contains("impl < T > suon_database :: Table for Inventory < T >"),
"The derive macro should generate a Table impl for the annotated type"
);
assert!(
output.contains("Self : Send + Sync + 'static"),
"The derive macro should inject Send + Sync + 'static bounds into the where clause"
);
}
#[test]
fn derive_table_preserves_existing_where_clause() {
let input: DeriveInput = match syn::parse_str("struct Inventory<T>(T) where T: Clone;") {
Ok(input) => input,
Err(error) => panic!("Input should parse: {error}"),
};
let output = expand_derive_table(input).to_string();
assert!(
output.contains("where T : Clone , Self : Send + Sync + 'static"),
"The derive macro should preserve existing where predicates while appending \
thread-safety bounds"
);
}
#[test]
fn derive_table_supports_named_structs() {
let input: DeriveInput = match syn::parse_str("struct Inventory { slots: usize }") {
Ok(input) => input,
Err(error) => panic!("Input should parse: {error}"),
};
let output = expand_derive_table(input).to_string();
assert!(
output.contains("impl suon_database :: Table for Inventory"),
"The derive macro should support named-field structs"
);
}
#[test]
fn derive_table_supports_lifetime_and_type_generics() {
let input: DeriveInput = match syn::parse_str("struct Borrowed<'a, T>(&'a T);") {
Ok(input) => input,
Err(error) => panic!("Input should parse: {error}"),
};
let output = expand_derive_table(input).to_string();
assert!(
output.contains("impl < 'a , T > suon_database :: Table for Borrowed < 'a , T >"),
"The derive macro should carry lifetime and type generics into the generated impl"
);
assert!(
output.contains("Self : Send + Sync + 'static"),
"The generated impl should still add thread-safety bounds for generic structs"
);
}
}