use pretty_assertions::assert_str_eq;
use quote::{ToTokens as _, format_ident};
use super::*;
macro_rules! before {
($($input:tt)*) => {{
let (input, errors) = do_derive(quote!(From), quote!($($input)*)).unwrap();
assert!(errors.is_empty(), "proc macro outputted compile errors: {errors:#?}");
prettyplease::unparse(&syn::parse_file(&input.to_string()).unwrap())
}}
}
macro_rules! after {
($($expected:tt)*) => {
prettyplease::unparse(&syn::parse_file("e!($($expected)*).to_string()).unwrap())
}
}
#[test]
fn demo() {
assert_str_eq!(
before! {
#[nutype(validate(non_empty))]
struct Repo(String);
},
after! {
#[nutype(validate(non_empty))]
struct Repo(String);
#[cfg(test)]
impl<T: Into<String>> ::core::convert::From<T> for Repo {
fn from(value: T) -> Self {
let value: String = value.into();
Self::try_new(value).unwrap()
}
}
}
);
assert_str_eq!(
before! {
#[nutype]
struct PrNumber(NonZeroU32);
},
after! {
#[nutype]
struct PrNumber(NonZeroU32);
#[cfg(test)]
impl<T: Into<u32>> ::core::convert::From<T> for PrNumber {
fn from(value: T) -> Self {
let value: u32 = value.into();
Self::new(::core::num::NonZeroU32::new(value).unwrap())
}
}
}
);
assert_str_eq!(
before! {
#[nutype(validate(non_empty, predicate = is_valid_commit_hash))]
struct Commit(String);
},
after! {
#[nutype(validate(non_empty, predicate = is_valid_commit_hash))]
struct Commit(String);
#[cfg(test)]
impl<T: Into<String>> ::core::convert::From<T> for Commit {
fn from(value: T) -> Self {
let value: String = value.into();
Self::try_new(value).unwrap()
}
}
}
);
}
#[test]
fn non_zero() {
#[rustfmt::skip]
let non_zeroable_primitives = &[
"i8", "i16", "i32", "i64", "i128", "isize",
"u8", "u16", "u32", "u64", "u128", "usize",
];
for primitive in non_zeroable_primitives {
enum ConstructorKind {
New,
TryNew,
}
enum NonZeroTy {
Generic,
Concrete,
}
let check_nonzero_transformation =
|non_zero_ty: NonZeroTy, constructor_kind: ConstructorKind| {
let nutype_attr = match constructor_kind {
ConstructorKind::New => quote! { #[nutype] },
ConstructorKind::TryNew => quote! { #[nutype(validate(predicate = |_| true))] },
};
let constructor_fn = match constructor_kind {
ConstructorKind::New => quote!(new),
ConstructorKind::TryNew => quote!(try_new),
};
let primitive_ident = format_ident!("{primitive}");
let nonzero_type = match non_zero_ty {
NonZeroTy::Generic => quote!(NonZero<#primitive_ident>),
NonZeroTy::Concrete => {
let capitalized = format!(
"{}{}",
primitive.get(0..1).unwrap().to_ascii_uppercase(),
primitive.get(1..).unwrap()
);
format_ident!("NonZero{capitalized}").to_token_stream()
}
};
let inner_ctor = match non_zero_ty {
NonZeroTy::Generic => quote!(NonZero::<#primitive_ident>::new(value).unwrap()),
NonZeroTy::Concrete => quote!(#nonzero_type::new(value).unwrap()),
};
let before = before!(
#nutype_attr
struct A(num::#nonzero_type);
);
let unwrap_if_try_new = if let ConstructorKind::TryNew = constructor_kind {
quote! { .unwrap() }
} else {
quote!()
};
let after = after! {
#nutype_attr
struct A(num::#nonzero_type);
#[cfg(test)]
impl<T: Into<#primitive_ident>> ::core::convert::From<T> for A {
fn from(value: T) -> Self {
let value: #primitive_ident = value.into();
Self::#constructor_fn(::core::num::#inner_ctor)#unwrap_if_try_new
}
}
};
eprintln!("{before}");
assert_str_eq!(before, after);
};
check_nonzero_transformation(NonZeroTy::Generic, ConstructorKind::New);
check_nonzero_transformation(NonZeroTy::Generic, ConstructorKind::TryNew);
check_nonzero_transformation(NonZeroTy::Concrete, ConstructorKind::New);
check_nonzero_transformation(NonZeroTy::Concrete, ConstructorKind::TryNew);
}
}