#![doc = include_str!("../README.md")]
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{Data, DataStruct, DeriveInput, Field, Fields, Lit, Member, Meta, Result};
#[proc_macro_derive(Defew, attributes(new, defew))]
pub fn defew(input: TokenStream) -> TokenStream {
let input = &syn::parse_macro_input!(input as DeriveInput);
defew_internal(input)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
macro_rules! match_token {
(MetaList, $p:pat) => {
syn::Meta::List(syn::MetaList {
tokens: $p,
delimiter: syn::MacroDelimiter::Paren(_),
..
})
};
(NameValue, $p:pat) => {
syn::Meta::NameValue(syn::MetaNameValue {
value: syn::Expr::Lit(syn::ExprLit { lit: $p, .. }),
..
})
};
}
macro_rules! err {
($e:expr, $msg:expr) => {
return Err(syn::Error::new_spanned($e, $msg))
};
($msg:expr) => {
return Err(syn::Error::new(proc_macro2::Span::call_site(), $msg))
};
}
fn defew_internal(input: &DeriveInput) -> Result<proc_macro2::TokenStream> {
let Data::Struct(DataStruct { fields, .. }) = &input.data else {
err!("Defew only supports structs");
};
if matches!(fields, Fields::Unit) {
err!("Defew does not support unit structs");
}
let (trait_for, visibility) = match find_meta(&input.attrs, "defew")? {
Some(match_token!(MetaList, tr)) if !tr.is_empty() => (quote! { #tr for }, quote!()), Some(Meta::Path(_)) => (quote!(), quote!()), Some(match_token!(NameValue, Lit::Str(s))) => {
let restriction: proc_macro2::TokenStream = s.parse()?;
(quote!(), quote!(pub(#restriction))) }
None => (quote!(), quote!(pub)), Some(meta) => err!(meta, "Defew does not support this syntax"),
};
let names: Vec<_> = fields
.members()
.map(|member| match member {
Member::Named(ident) => ident,
Member::Unnamed(idx) => format_ident!("_{}", idx),
})
.collect();
let default = quote! { ::core::default::Default::default() };
let mut params = Vec::new(); let mut variables = Vec::new();
for (Field { ty, attrs, .. }, name) in fields.iter().zip(&names) {
match find_meta(attrs, "new")? {
Some(Meta::Path(_)) => params.push(quote! ( #name: #ty )),
Some(match_token!(MetaList, v)) => variables.push(quote! { let #name: #ty = #v; }),
Some(match_token!(NameValue, v)) => variables.push(quote! { const #name: #ty = #v; }),
None => variables.push(quote! { let #name: #ty = #default; }),
Some(meta) => err!(meta, "Defew does not support this syntax"),
}
}
let struct_name = &input.ident;
let (impl_generics, ty_generics, where_clause) = &input.generics.split_for_impl();
let field_values = fields.members().zip(names).map(|(f, v)| quote! { #f: #v });
let expanded = quote! {
#[automatically_derived]
impl #impl_generics #trait_for #struct_name #ty_generics #where_clause {
#[doc = "Creates a new instance of the struct with default values"]
#[allow(non_upper_case_globals)]
#visibility fn new(#(#params),*) -> Self {
#(#variables)*
Self { #(#field_values),* }
}
}
};
Ok(expanded)
}
fn find_meta<'a>(attrs: &'a [syn::Attribute], name: &'static str) -> Result<Option<&'a syn::Meta>> {
const NAMES: [&str; 2] = ["new", "defew"];
let is_ours = |attr: &&syn::Attribute| NAMES.iter().any(|n| attr.path().is_ident(n));
match attrs.iter().filter(is_ours).take(2).collect::<Vec<_>>()[..] {
[] => Ok(None),
[a] if !a.path().is_ident(name) => err!(a, format!("Defew only supports #[{name}] here")),
[attr] => Ok(Some(&attr.meta)),
[.., attr] => err!(attr, format!("Defew accepts one attribute")),
}
}
#[cfg(test)]
mod tests {
use crate::{defew_internal, find_meta};
use quote::quote;
use syn::parse_quote;
#[test]
fn test_find_meta() {
use syn::parse_quote as pq;
use syn::Meta::{List, NameValue, Path};
macro_rules! am {
($left:expr, $right:pat) => {
assert!(matches!($left, $right));
};
($left:expr, $right:pat,?) => {
assert!(matches!($left, Ok(Some($right))));
};
}
am!(find_meta(&[pq!(#[new])], "new"), Path(_),?);
am!(find_meta(&[pq!(#[new(42)])], "new"), List(_),?);
am!(find_meta(&[pq!(#[new = 42])], "new"), NameValue(_),?);
am!(find_meta(&[pq!(#[serde])], "defew"), Ok(None));
am!(find_meta(&[pq!(#[defew])], "new"), Err(_));
am!(find_meta(&[pq!(#[new]), pq!(#[new])], "new"), Err(_));
am!(find_meta(&[pq!(#[new[1]])], "new"), List(_),?);
}
#[test]
fn test_defew_internal_basic() {
let input = parse_quote! {
struct Data {
a: i32,
#[new("ABC".into())]
b: String,
#[new(Some(42))]
c: Option<u64>,
}
};
let output = quote! {
#[automatically_derived]
impl Data {
#[doc = "Creates a new instance of the struct with default values"]
#[allow(non_upper_case_globals)]
pub fn new() -> Self {
let a: i32 = ::core::default::Default::default();
let b: String = "ABC".into();
let c: Option<u64> = Some(42);
Self { a: a, b: b, c: c }
}
}
}
.to_string();
assert_eq!(defew_internal(&input).unwrap().to_string(), output);
}
#[test]
fn test_defew_internal_basic_unnamed() {
let input = parse_quote! {
struct Data(#[new(42)] u64, i32);
};
let output = quote! {
#[automatically_derived]
impl Data {
#[doc = "Creates a new instance of the struct with default values"]
#[allow(non_upper_case_globals)]
pub fn new() -> Self {
let _0: u64 = 42;
let _1: i32 = ::core::default::Default::default();
Self { 0: _0, 1: _1 }
}
}
}
.to_string();
assert_eq!(defew_internal(&input).unwrap().to_string(), output);
}
#[test]
fn test_defew_internal_with_visibility_and_const() {
let input = parse_quote! {
#[defew = "crate"]
struct Data {
#[new = 42]
a: i32,
}
};
let output = quote! {
#[automatically_derived]
impl Data {
#[doc = "Creates a new instance of the struct with default values"]
#[allow(non_upper_case_globals)]
pub(crate) fn new() -> Self {
const a: i32 = 42;
Self { a: a }
}
}
}
.to_string();
assert_eq!(defew_internal(&input).unwrap().to_string(), output);
}
#[test]
fn test_defew_internal_with_trait_generics() {
let input = parse_quote! {
#[defew(DataTrait<T>)]
struct Data<T: From<u8>> {
#[new]
a: T,
#[new(98.into())]
b: T,
}
};
let output = quote! {
#[automatically_derived]
impl<T: From<u8> > DataTrait<T> for Data<T> {
#[doc = "Creates a new instance of the struct with default values"]
#[allow(non_upper_case_globals)]
fn new(a: T) -> Self {
let b: T = 98.into();
Self { a: a, b: b }
}
}
}
.to_string();
assert_eq!(defew_internal(&input).unwrap().to_string(), output);
}
#[test]
fn test_defew_internal_reference_other_field() {
let input = parse_quote! {
struct Data {
#[new]
a: i32,
#[new = 42]
b: i32,
#[new(a * b + 4)]
c: i32,
}
};
let output = quote! {
#[automatically_derived]
impl Data {
#[doc = "Creates a new instance of the struct with default values"]
#[allow(non_upper_case_globals)]
pub fn new(a: i32) -> Self {
const b: i32 = 42;
let c: i32 = a * b + 4;
Self { a: a, b: b, c: c }
}
}
}
.to_string();
assert_eq!(defew_internal(&input).unwrap().to_string(), output);
}
#[test]
fn test_defew_internal_reference_other_field_unnamed() {
let input = parse_quote! {
struct Data(#[new] i32, #[new(_0 * 2)] i32);
};
let output = quote! {
#[automatically_derived]
impl Data {
#[doc = "Creates a new instance of the struct with default values"]
#[allow(non_upper_case_globals)]
pub fn new(_0: i32) -> Self {
let _1: i32 = _0 * 2;
Self { 0: _0, 1: _1 }
}
}
}
.to_string();
assert_eq!(defew_internal(&input).unwrap().to_string(), output);
}
#[test]
fn test_defew_internal_with_unit_struct() {
let input = parse_quote! {
struct Data;
};
let output = "Defew does not support unit structs";
assert_eq!(defew_internal(&input).unwrap_err().to_string(), output);
}
#[test]
fn test_defew_internal_with_enum() {
let input = parse_quote! {
enum Data {
Foo,
Bar,
}
};
let output = "Defew only supports structs";
assert_eq!(defew_internal(&input).unwrap_err().to_string(), output);
}
#[test]
fn test_defew_internal_with_multiple_attributes() {
let input = parse_quote! {
struct Data {
#[new(42)]
#[new(11)]
a: i32,
}
};
let output = "Defew accepts one attribute";
assert_eq!(defew_internal(&input).unwrap_err().to_string(), output);
}
#[test]
fn test_defew_internal_with_invalid_visibility() {
let input = parse_quote! {
#[defew = 1]
struct Data {
a: i32,
}
};
let output = "Defew does not support this syntax";
assert_eq!(defew_internal(&input).unwrap_err().to_string(), output);
}
#[test]
fn test_defew_internal_with_invalid_syntax() {
let input = parse_quote! {
struct Data {
#[new[1]]
a: i32,
}
};
let output = "Defew does not support this syntax";
assert_eq!(defew_internal(&input).unwrap_err().to_string(), output);
}
#[test]
fn test_defew_internal_with_invalid_attribute() {
let input = parse_quote! {
struct Data {
#[defew]
a: i32,
}
};
let output = "Defew only supports #[new] here";
assert_eq!(defew_internal(&input).unwrap_err().to_string(), output);
}
#[test]
fn test_defew_internal_with_no_visibility() {
let input = parse_quote! {
#[defew]
struct Data {
a: i32,
}
};
let output = quote! {
#[automatically_derived]
impl Data {
#[doc = "Creates a new instance of the struct with default values"]
#[allow(non_upper_case_globals)]
fn new() -> Self {
let a: i32 = ::core::default::Default::default();
Self { a: a }
}
}
}
.to_string();
assert_eq!(defew_internal(&input).unwrap().to_string(), output);
}
}