diff --git a/CHANGELOG.md b/CHANGELOG.md index 19146e3..fa67e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### v0.x.x - 2024-xx-xx + +* Support newtypes with generics + + ### v0.4.2 - 2024-04-07 * Support `no_std` ( the dependency needs to be declared as `nutype = { default-features = false }` ) diff --git a/Cargo.lock b/Cargo.lock index 5dfd04c..bd7e639 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,13 @@ dependencies = [ "nutype", ] +[[package]] +name = "any_generics" +version = "0.1.0" +dependencies = [ + "nutype", +] + [[package]] name = "arbitrary" version = "1.3.2" diff --git a/Cargo.toml b/Cargo.toml index 11ea57d..8a3b4bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,5 @@ members = [ "examples/serde_complex", "examples/string_bounded_len", "examples/string_regex_email", - "examples/string_arbitrary", + "examples/string_arbitrary", "examples/any_generics", ] diff --git a/dummy/src/main.rs b/dummy/src/main.rs index 36bfb66..44259ec 100644 --- a/dummy/src/main.rs +++ b/dummy/src/main.rs @@ -1,10 +1,10 @@ use nutype::nutype; +use std::borrow::Cow; -#[nutype( - validate(predicate = |v| v), - derive(Default), - default = true -)] -pub struct TestData(bool); +#[nutype(derive(Into))] +struct Clarabelle<'a>(Cow<'a, str>); -fn main() {} +fn main() { + // let clarabelle = Clarabelle::new(Cow::Borrowed("Clarabelle")); + // assert_eq!(clarabelle.to_string(), "Clarabelle"); +} diff --git a/examples/any_generics/Cargo.toml b/examples/any_generics/Cargo.toml new file mode 100644 index 0000000..10a487c --- /dev/null +++ b/examples/any_generics/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "any_generics" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nutype = { path = "../../nutype" } diff --git a/examples/any_generics/src/main.rs b/examples/any_generics/src/main.rs new file mode 100644 index 0000000..250a7f3 --- /dev/null +++ b/examples/any_generics/src/main.rs @@ -0,0 +1,48 @@ +use nutype::nutype; +use std::borrow::Cow; + +#[nutype( + validate(predicate = |vec| !vec.is_empty()), + derive(Debug), +)] +struct NotEmpty(Vec); + +#[nutype(derive( + Debug, + Display, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Into, + From, + Deref, + Borrow, + // TODO + // AsRef, + // FromStr, + // TryFrom, + // Default, + // Serialize, + // Deserialize, + // Arbitrary, +))] +struct Clarabelle<'b>(Cow<'b, str>); + +fn main() { + { + let v = NotEmpty::new(vec![1, 2, 3]).unwrap(); + assert_eq!(v.into_inner(), vec![1, 2, 3]); + } + { + let err = NotEmpty::::new(vec![]).unwrap_err(); + assert_eq!(err, NotEmptyError::PredicateViolated); + } + + { + let muu = Clarabelle::new(Cow::Borrowed("Muu")); + assert_eq!(muu.to_string(), "Muu"); + } +} diff --git a/nutype_macros/src/any/gen/mod.rs b/nutype_macros/src/any/gen/mod.rs index 9eead88..75f9876 100644 --- a/nutype_macros/src/any/gen/mod.rs +++ b/nutype_macros/src/any/gen/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashSet; use proc_macro2::TokenStream; use quote::quote; -use syn::parse_quote; +use syn::{parse_quote, Generics}; use crate::common::{ gen::{ @@ -53,7 +53,7 @@ impl GenerateNewtype for AnyNewtype { .collect(); quote!( - fn sanitize(mut value: #inner_type) -> #inner_type { + fn __sanitize__(mut value: #inner_type) -> #inner_type { #transformations value } @@ -72,7 +72,7 @@ impl GenerateNewtype for AnyNewtype { .map(|validator| match validator { AnyValidator::Predicate(predicate) => { let inner_type_ref: syn::Type = parse_quote!( - &'a #inner_type + &'nutype_a #inner_type ); let typed_predicate: TypedCustomFunction = predicate .clone() @@ -88,7 +88,20 @@ impl GenerateNewtype for AnyNewtype { .collect(); quote!( - fn validate<'a>(val: &'a #inner_type) -> ::core::result::Result<(), #error_name> { + // NOTE 1: we're using a unique lifetime name `nutype_a` in a hope that it will not clash + // with any other lifetimes in the user's code. + // + // NOTE 2: + // When inner type is Cow<'a, str>, the generated code will look like this (with 2 + // lifetimes): + // + // fn __validate__<'nutype_a>(val: &'nutype_a Cow<'a, str>) + // + // Clippy does not like passing a reference to a Cow. So we need to ignore the `clippy::ptr_arg` warning. + // Since this code is generic which is used for different inner types (not only Cow), we cannot easily fix it to make + // clippy happy. + #[allow(clippy::ptr_arg)] + fn __validate__<'nutype_a>(val: &'nutype_a #inner_type) -> ::core::result::Result<(), #error_name> { #validations Ok(()) } @@ -104,6 +117,7 @@ impl GenerateNewtype for AnyNewtype { fn gen_traits( type_name: &TypeName, + generics: &Generics, inner_type: &Self::InnerType, maybe_error_type_name: Option, traits: HashSet, @@ -112,6 +126,7 @@ impl GenerateNewtype for AnyNewtype { ) -> Result { gen_traits( type_name, + generics, inner_type, maybe_error_type_name, traits, diff --git a/nutype_macros/src/any/gen/traits/mod.rs b/nutype_macros/src/any/gen/traits/mod.rs index 71e9e05..0c80950 100644 --- a/nutype_macros/src/any/gen/traits/mod.rs +++ b/nutype_macros/src/any/gen/traits/mod.rs @@ -107,6 +107,7 @@ enum AnyIrregularTrait { pub fn gen_traits( type_name: &TypeName, + generics: &syn::Generics, inner_type: &AnyInnerType, maybe_error_type_name: Option, traits: HashSet, @@ -126,6 +127,7 @@ pub fn gen_traits( let implement_traits = gen_implemented_traits( type_name, + generics, inner_type, maybe_error_type_name, irregular_traits, @@ -141,6 +143,7 @@ pub fn gen_traits( fn gen_implemented_traits( type_name: &TypeName, + generics: &syn::Generics, inner_type: &AnyInnerType, maybe_error_type_name: Option, impl_traits: Vec, @@ -151,16 +154,16 @@ fn gen_implemented_traits( .iter() .map(|t| match t { AnyIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, inner_type)), - AnyIrregularTrait::From => Ok(gen_impl_trait_from(type_name, inner_type)), - AnyIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, inner_type.clone())), - AnyIrregularTrait::Display => Ok(gen_impl_trait_display(type_name)), - AnyIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, inner_type)), - AnyIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, inner_type)), + AnyIrregularTrait::From => Ok(gen_impl_trait_from(type_name, generics, inner_type)), + AnyIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, generics, inner_type.clone())), + AnyIrregularTrait::Display => Ok(gen_impl_trait_display(type_name, generics)), + AnyIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)), + AnyIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, generics, inner_type)), AnyIrregularTrait::FromStr => Ok( gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref()) ), AnyIrregularTrait::TryFrom => Ok( - gen_impl_trait_try_from(type_name, inner_type, maybe_error_type_name.as_ref()) + gen_impl_trait_try_from(type_name, generics, inner_type, maybe_error_type_name.as_ref()) ), AnyIrregularTrait::Default => match maybe_default_value { Some(ref default_value) => { diff --git a/nutype_macros/src/common/gen/mod.rs b/nutype_macros/src/common/gen/mod.rs index ee17f09..09202be 100644 --- a/nutype_macros/src/common/gen/mod.rs +++ b/nutype_macros/src/common/gen/mod.rs @@ -20,7 +20,7 @@ use crate::common::{ }; use proc_macro2::{Punct, Spacing, TokenStream, TokenTree}; use quote::{format_ident, quote, ToTokens}; -use syn::Visibility; +use syn::{Generics, Visibility}; /// Inject an inner type into a closure, so compiler does not complain if the token stream matchers /// the expected closure pattern. @@ -133,9 +133,13 @@ pub fn gen_reimports( } } -pub fn gen_impl_into_inner(type_name: &TypeName, inner_type: impl ToTokens) -> TokenStream { +pub fn gen_impl_into_inner( + type_name: &TypeName, + generics: &Generics, + inner_type: impl ToTokens, +) -> TokenStream { quote! { - impl #type_name { + impl #generics #type_name #generics { #[inline] pub fn into_inner(self) -> #inner_type { self.0 @@ -178,6 +182,7 @@ pub trait GenerateNewtype { fn gen_traits( type_name: &TypeName, + generics: &Generics, inner_type: &Self::InnerType, maybe_error_type_name: Option, traits: HashSet, @@ -187,14 +192,15 @@ pub trait GenerateNewtype { fn gen_new_with_validation( type_name: &TypeName, + generics: &Generics, inner_type: &Self::InnerType, sanitizers: &[Self::Sanitizer], validators: &[Self::Validator], ) -> TokenStream { - let sanitize = Self::gen_fn_sanitize(inner_type, sanitizers); + let fn_sanitize = Self::gen_fn_sanitize(inner_type, sanitizers); let validation_error = Self::gen_validation_error_type(type_name, validators); let error_type_name = gen_error_type_name(type_name); - let validate = Self::gen_fn_validate(inner_type, type_name, validators); + let fn_validate = Self::gen_fn_validate(inner_type, type_name, validators); let (input_type, convert_raw_value_if_necessary) = if Self::NEW_CONVERT_INTO_INNER_TYPE { ( @@ -208,29 +214,30 @@ pub trait GenerateNewtype { quote!( #validation_error - impl #type_name { + impl #generics #type_name #generics { pub fn new(raw_value: #input_type) -> ::core::result::Result { - // Keep sanitize() and validate() within new() so they do not overlap with outer - // scope imported with `use super::*`. - #sanitize - #validate - #convert_raw_value_if_necessary - let sanitized_value: #inner_type = sanitize(raw_value); - validate(&sanitized_value)?; + let sanitized_value: #inner_type = Self::__sanitize__(raw_value); + Self::__validate__(&sanitized_value)?; Ok(#type_name(sanitized_value)) } + + // Definite associated private functions __sanitize__() and __validate__() with underscores so they do not overlap with outer + // scope imported with `use super::*`. + #fn_sanitize + #fn_validate } ) } fn gen_new_without_validation( type_name: &TypeName, + generics: &Generics, inner_type: &Self::InnerType, sanitizers: &[Self::Sanitizer], ) -> TokenStream { - let sanitize = Self::gen_fn_sanitize(inner_type, sanitizers); + let fn_sanitize = Self::gen_fn_sanitize(inner_type, sanitizers); let (input_type, convert_raw_value_if_necessary) = if Self::NEW_CONVERT_INTO_INNER_TYPE { ( @@ -242,34 +249,37 @@ pub trait GenerateNewtype { }; quote!( - impl #type_name { + impl #generics #type_name #generics { pub fn new(raw_value: #input_type) -> Self { - #sanitize - #convert_raw_value_if_necessary - - Self(sanitize(raw_value)) + Self(Self::__sanitize__(raw_value)) } + // Definite associated private function __sanitize__() with underscores so they do not overlap with outer + // scope imported with `use super::*`. + #fn_sanitize } ) } fn gen_implementation( type_name: &TypeName, + generics: &Generics, inner_type: &Self::InnerType, guard: &Guard, new_unchecked: NewUnchecked, ) -> TokenStream { let impl_new = match guard { Guard::WithoutValidation { sanitizers } => { - Self::gen_new_without_validation(type_name, inner_type, sanitizers) + Self::gen_new_without_validation(type_name, generics, inner_type, sanitizers) } Guard::WithValidation { sanitizers, validators, - } => Self::gen_new_with_validation(type_name, inner_type, sanitizers, validators), + } => Self::gen_new_with_validation( + type_name, generics, inner_type, sanitizers, validators, + ), }; - let impl_into_inner = gen_impl_into_inner(type_name, inner_type); + let impl_into_inner = gen_impl_into_inner(type_name, generics, inner_type); let impl_new_unchecked = gen_new_unchecked(type_name, inner_type, new_unchecked); quote! { @@ -296,11 +306,12 @@ pub trait GenerateNewtype { new_unchecked, maybe_default_value, inner_type, + generics, } = params; let module_name = gen_module_name_for_type(&type_name); let implementation = - Self::gen_implementation(&type_name, &inner_type, &guard, new_unchecked); + Self::gen_implementation(&type_name, &generics, &inner_type, &guard, new_unchecked); let maybe_error_type_name: Option = match guard { Guard::WithoutValidation { .. } => None, @@ -335,6 +346,7 @@ pub trait GenerateNewtype { implement_traits, } = Self::gen_traits( &type_name, + &generics, &inner_type, maybe_error_type_name, traits, @@ -349,7 +361,7 @@ pub trait GenerateNewtype { #(#doc_attrs)* #derive_transparent_traits - pub struct #type_name(#inner_type); + pub struct #type_name #generics(#inner_type); #implementation #implement_traits diff --git a/nutype_macros/src/common/gen/traits.rs b/nutype_macros/src/common/gen/traits.rs index 14ec3d7..2ece351 100644 --- a/nutype_macros/src/common/gen/traits.rs +++ b/nutype_macros/src/common/gen/traits.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +use syn::Generics; use crate::common::models::{ErrorTypeName, InnerType, TypeName}; @@ -51,7 +52,11 @@ where } } -pub fn gen_impl_trait_into(type_name: &TypeName, inner_type: impl Into) -> TokenStream { +pub fn gen_impl_trait_into( + type_name: &TypeName, + generics: &Generics, + inner_type: impl Into, +) -> TokenStream { let inner_type: InnerType = inner_type.into(); // NOTE: We're getting blank implementation of @@ -59,9 +64,9 @@ pub fn gen_impl_trait_into(type_name: &TypeName, inner_type: impl Into for Inner quote! { - impl ::core::convert::From<#type_name> for #inner_type { + impl #generics ::core::convert::From<#type_name #generics> for #inner_type { #[inline] - fn from(value: #type_name) -> Self { + fn from(value: #type_name #generics) -> Self { value.into_inner() } } @@ -79,9 +84,13 @@ pub fn gen_impl_trait_as_ref(type_name: &TypeName, inner_type: impl ToTokens) -> } } -pub fn gen_impl_trait_deref(type_name: &TypeName, inner_type: impl ToTokens) -> TokenStream { +pub fn gen_impl_trait_deref( + type_name: &TypeName, + generics: &Generics, + inner_type: impl ToTokens, +) -> TokenStream { quote! { - impl ::core::ops::Deref for #type_name { + impl #generics ::core::ops::Deref for #type_name #generics { type Target = #inner_type; #[inline] @@ -92,9 +101,9 @@ pub fn gen_impl_trait_deref(type_name: &TypeName, inner_type: impl ToTokens) -> } } -pub fn gen_impl_trait_display(type_name: &TypeName) -> TokenStream { +pub fn gen_impl_trait_display(type_name: &TypeName, generics: &Generics) -> TokenStream { quote! { - impl ::core::fmt::Display for #type_name { + impl #generics ::core::fmt::Display for #type_name #generics { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { // A tiny wrapper function with trait boundary that improves error reporting. @@ -111,9 +120,13 @@ pub fn gen_impl_trait_display(type_name: &TypeName) -> TokenStream { } } -pub fn gen_impl_trait_borrow(type_name: &TypeName, borrowed_type: impl ToTokens) -> TokenStream { +pub fn gen_impl_trait_borrow( + type_name: &TypeName, + generics: &Generics, + borrowed_type: impl ToTokens, +) -> TokenStream { quote! { - impl ::core::borrow::Borrow<#borrowed_type> for #type_name { + impl #generics ::core::borrow::Borrow<#borrowed_type> for #type_name #generics { #[inline] fn borrow(&self) -> &#borrowed_type { &self.0 @@ -122,9 +135,13 @@ pub fn gen_impl_trait_borrow(type_name: &TypeName, borrowed_type: impl ToTokens) } } -pub fn gen_impl_trait_from(type_name: &TypeName, inner_type: impl ToTokens) -> TokenStream { +pub fn gen_impl_trait_from( + type_name: &TypeName, + generics: &Generics, + inner_type: impl ToTokens, +) -> TokenStream { quote! { - impl ::core::convert::From<#inner_type> for #type_name { + impl #generics ::core::convert::From<#inner_type> for #type_name #generics { #[inline] fn from(raw_value: #inner_type) -> Self { Self::new(raw_value) @@ -135,6 +152,7 @@ pub fn gen_impl_trait_from(type_name: &TypeName, inner_type: impl ToTokens) -> T pub fn gen_impl_trait_try_from( type_name: &TypeName, + generics: &Generics, inner_type: impl ToTokens, maybe_error_type_name: Option<&ErrorTypeName>, ) -> TokenStream { @@ -143,11 +161,11 @@ pub fn gen_impl_trait_try_from( // The case when there are validation // quote! { - impl ::core::convert::TryFrom<#inner_type> for #type_name { + impl #generics ::core::convert::TryFrom<#inner_type> for #type_name #generics { type Error = #error_type_name; #[inline] - fn try_from(raw_value: #inner_type) -> Result<#type_name, Self::Error> { + fn try_from(raw_value: #inner_type) -> Result<#type_name #generics, Self::Error> { Self::new(raw_value) } } @@ -157,11 +175,11 @@ pub fn gen_impl_trait_try_from( // The case when there are no validation // quote! { - impl ::core::convert::TryFrom<#inner_type> for #type_name { + impl #generics ::core::convert::TryFrom<#inner_type> for #type_name #generics { type Error = ::core::convert::Infallible; #[inline] - fn try_from(raw_value: #inner_type) -> Result<#type_name, Self::Error> { + fn try_from(raw_value: #inner_type) -> Result<#type_name #generics, Self::Error> { Ok(Self::new(raw_value)) } } diff --git a/nutype_macros/src/common/models.rs b/nutype_macros/src/common/models.rs index be8b53f..e212b95 100644 --- a/nutype_macros/src/common/models.rs +++ b/nutype_macros/src/common/models.rs @@ -1,6 +1,7 @@ use kinded::Kinded; use std::ops::Add; use std::{collections::HashSet, fmt::Debug}; +use syn::Generics; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; @@ -158,6 +159,7 @@ pub struct Meta { pub inner_type: InnerType, pub vis: syn::Visibility, pub doc_attrs: Vec, + pub generics: Generics, } impl Meta { @@ -167,10 +169,12 @@ impl Meta { type_name, inner_type, vis, + generics, } = self; let typed_meta = TypedMeta { doc_attrs, type_name, + generics, attrs, vis, }; @@ -189,6 +193,7 @@ pub struct TypedMeta { pub vis: syn::Visibility, pub doc_attrs: Vec, + pub generics: Generics, } /// Validated model, that represents precisely what needs to be generated. @@ -342,6 +347,7 @@ pub struct GenerateParams { pub traits: HashSet, pub vis: syn::Visibility, pub type_name: TypeName, + pub generics: Generics, pub guard: Guard, pub new_unchecked: NewUnchecked, pub maybe_default_value: Option, @@ -381,6 +387,7 @@ pub trait Newtype { type_name, attrs, vis, + generics, } = typed_meta; let Attributes { guard, @@ -394,6 +401,7 @@ pub trait Newtype { traits, vis, type_name, + generics, guard, new_unchecked, maybe_default_value, diff --git a/nutype_macros/src/common/parse/meta.rs b/nutype_macros/src/common/parse/meta.rs index a3d4932..3a38968 100644 --- a/nutype_macros/src/common/parse/meta.rs +++ b/nutype_macros/src/common/parse/meta.rs @@ -22,7 +22,7 @@ pub fn parse_meta(token_stream: TokenStream) -> Result { data, vis, ident: type_name, - generics: _, + generics, } = input; let type_name = TypeName::new(type_name); @@ -100,6 +100,7 @@ pub fn parse_meta(token_stream: TokenStream) -> Result { Ok(Meta { doc_attrs, type_name, + generics, inner_type, vis, }) diff --git a/nutype_macros/src/float/gen/mod.rs b/nutype_macros/src/float/gen/mod.rs index 4dda353..4833267 100644 --- a/nutype_macros/src/float/gen/mod.rs +++ b/nutype_macros/src/float/gen/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashSet; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +use syn::Generics; use self::error::gen_validation_error_type; use super::{ @@ -56,7 +57,7 @@ where .collect(); quote!( - fn sanitize(mut value: #inner_type) -> #inner_type { + fn __sanitize__(mut value: #inner_type) -> #inner_type { #transformations value } @@ -119,7 +120,7 @@ where .collect(); quote!( - fn validate(val: &#inner_type) -> core::result::Result<(), #error_name> { + fn __validate__(val: &#inner_type) -> core::result::Result<(), #error_name> { let val = *val; #validations Ok(()) @@ -136,6 +137,7 @@ where fn gen_traits( type_name: &TypeName, + generics: &Generics, inner_type: &Self::InnerType, maybe_error_type_name: Option, traits: HashSet, @@ -144,6 +146,7 @@ where ) -> Result { gen_traits( type_name, + generics, inner_type, maybe_error_type_name, maybe_default_value, diff --git a/nutype_macros/src/float/gen/traits/mod.rs b/nutype_macros/src/float/gen/traits/mod.rs index 324f1b4..20bca68 100644 --- a/nutype_macros/src/float/gen/traits/mod.rs +++ b/nutype_macros/src/float/gen/traits/mod.rs @@ -3,6 +3,7 @@ use std::collections::HashSet; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +use syn::Generics; use crate::{ common::{ @@ -122,6 +123,7 @@ impl ToTokens for FloatTransparentTrait { pub fn gen_traits( type_name: &TypeName, + generics: &Generics, inner_type: &FloatInnerType, maybe_error_type_name: Option, maybe_default_value: Option, @@ -141,6 +143,7 @@ pub fn gen_traits( let implement_traits = gen_implemented_traits( type_name, + generics, inner_type, maybe_error_type_name, maybe_default_value, @@ -156,6 +159,7 @@ pub fn gen_traits( fn gen_implemented_traits( type_name: &TypeName, + generics: &Generics, inner_type: &FloatInnerType, maybe_error_type_name: Option, maybe_default_value: Option, @@ -166,17 +170,17 @@ fn gen_implemented_traits( .iter() .map(|t| match t { FloatIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, inner_type)), - FloatIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, inner_type)), + FloatIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)), FloatIrregularTrait::FromStr => { Ok(gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref())) } - FloatIrregularTrait::From => Ok(gen_impl_trait_from(type_name, inner_type)), - FloatIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, inner_type)), + FloatIrregularTrait::From => Ok(gen_impl_trait_from(type_name, generics, inner_type)), + FloatIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, generics, inner_type)), FloatIrregularTrait::TryFrom => { - Ok(gen_impl_trait_try_from(type_name, inner_type, maybe_error_type_name.as_ref())) + Ok(gen_impl_trait_try_from(type_name, generics, inner_type, maybe_error_type_name.as_ref())) } - FloatIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, inner_type)), - FloatIrregularTrait::Display => Ok(gen_impl_trait_display(type_name)), + FloatIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, generics, inner_type)), + FloatIrregularTrait::Display => Ok(gen_impl_trait_display(type_name, generics)), FloatIrregularTrait::Default => match maybe_default_value { Some(ref default_value) => { let has_validation = maybe_error_type_name.is_some(); diff --git a/nutype_macros/src/integer/gen/mod.rs b/nutype_macros/src/integer/gen/mod.rs index ca68ecd..49746d5 100644 --- a/nutype_macros/src/integer/gen/mod.rs +++ b/nutype_macros/src/integer/gen/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashSet; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +use syn::Generics; use self::{error::gen_validation_error_type, traits::gen_traits}; use super::{ @@ -55,7 +56,7 @@ where .collect(); quote!( - fn sanitize(mut value: #inner_type) -> #inner_type { + fn __sanitize__(mut value: #inner_type) -> #inner_type { #transformations value } @@ -111,7 +112,7 @@ where .collect(); quote!( - fn validate(val: &#inner_type) -> ::core::result::Result<(), #error_name> { + fn __validate__(val: &#inner_type) -> ::core::result::Result<(), #error_name> { let val = *val; #validations Ok(()) @@ -128,6 +129,7 @@ where fn gen_traits( type_name: &TypeName, + generics: &Generics, inner_type: &Self::InnerType, maybe_error_type_name: Option, traits: HashSet, @@ -136,6 +138,7 @@ where ) -> Result { gen_traits( type_name, + generics, inner_type, maybe_error_type_name, traits, diff --git a/nutype_macros/src/integer/gen/traits/mod.rs b/nutype_macros/src/integer/gen/traits/mod.rs index 8c74ceb..c7fe088 100644 --- a/nutype_macros/src/integer/gen/traits/mod.rs +++ b/nutype_macros/src/integer/gen/traits/mod.rs @@ -4,6 +4,7 @@ use std::collections::HashSet; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +use syn::Generics; use crate::{ common::{ @@ -23,6 +24,7 @@ type IntegerGeneratableTrait = GeneratableTrait( type_name: &TypeName, + generics: &Generics, inner_type: &IntegerInnerType, maybe_error_type_name: Option, traits: HashSet, @@ -42,6 +44,7 @@ pub fn gen_traits( let implement_traits = gen_implemented_traits( type_name, + generics, inner_type, maybe_error_type_name, irregular_traits, @@ -176,6 +179,7 @@ impl ToTokens for IntegerTransparentTrait { fn gen_implemented_traits( type_name: &TypeName, + generics: &Generics, inner_type: &IntegerInnerType, maybe_error_type_name: Option, impl_traits: Vec, @@ -186,17 +190,17 @@ fn gen_implemented_traits( .iter() .map(|t| match t { IntegerIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, inner_type)), - IntegerIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, inner_type)), + IntegerIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, inner_type)), IntegerIrregularTrait::FromStr => { Ok(gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref())) } - IntegerIrregularTrait::From => Ok(gen_impl_trait_from(type_name, inner_type)), - IntegerIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, inner_type)), + IntegerIrregularTrait::From => Ok(gen_impl_trait_from(type_name, generics, inner_type)), + IntegerIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, generics, inner_type)), IntegerIrregularTrait::TryFrom => { - Ok(gen_impl_trait_try_from(type_name, inner_type, maybe_error_type_name.as_ref())) + Ok(gen_impl_trait_try_from(type_name, generics, inner_type, maybe_error_type_name.as_ref())) } - IntegerIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, inner_type)), - IntegerIrregularTrait::Display => Ok(gen_impl_trait_display(type_name)), + IntegerIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, generics, inner_type)), + IntegerIrregularTrait::Display => Ok(gen_impl_trait_display(type_name, generics)), IntegerIrregularTrait::Default => { match maybe_default_value { Some(ref default_value) => { diff --git a/nutype_macros/src/string/gen/mod.rs b/nutype_macros/src/string/gen/mod.rs index c5305c8..7975ef0 100644 --- a/nutype_macros/src/string/gen/mod.rs +++ b/nutype_macros/src/string/gen/mod.rs @@ -6,6 +6,7 @@ use std::collections::HashSet; use proc_macro2::TokenStream; use quote::quote; +use syn::Generics; use crate::{ common::{ @@ -71,7 +72,7 @@ impl GenerateNewtype for StringNewtype { .collect(); quote!( - fn sanitize(value: String) -> String { + fn __sanitize__(value: String) -> String { #transformations value } @@ -158,7 +159,7 @@ impl GenerateNewtype for StringNewtype { }; quote!( - fn validate(val: &str) -> ::core::result::Result<(), #error_name> { + fn __validate__(val: &str) -> ::core::result::Result<(), #error_name> { #chars_count_if_required #validations Ok(()) @@ -175,6 +176,7 @@ impl GenerateNewtype for StringNewtype { fn gen_traits( type_name: &TypeName, + generics: &Generics, _inner_type: &Self::InnerType, maybe_error_type_name: Option, traits: HashSet, @@ -183,6 +185,7 @@ impl GenerateNewtype for StringNewtype { ) -> Result { gen_traits( type_name, + generics, maybe_error_type_name, traits, maybe_default_value, diff --git a/nutype_macros/src/string/gen/traits/mod.rs b/nutype_macros/src/string/gen/traits/mod.rs index 42ad88b..4896945 100644 --- a/nutype_macros/src/string/gen/traits/mod.rs +++ b/nutype_macros/src/string/gen/traits/mod.rs @@ -4,6 +4,7 @@ use std::collections::HashSet; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +use syn::Generics; use crate::{ common::{ @@ -137,6 +138,7 @@ impl ToTokens for StringTransparentTrait { pub fn gen_traits( type_name: &TypeName, + generics: &Generics, maybe_error_type_name: Option, traits: HashSet, maybe_default_value: Option, @@ -155,6 +157,7 @@ pub fn gen_traits( let implement_traits = gen_implemented_traits( type_name, + generics, maybe_error_type_name, maybe_default_value, irregular_traits, @@ -169,6 +172,7 @@ pub fn gen_traits( fn gen_implemented_traits( type_name: &TypeName, + generics: &Generics, maybe_error_type_name: Option, maybe_default_value: Option, impl_traits: Vec, @@ -180,17 +184,17 @@ fn gen_implemented_traits( .iter() .map(|t| match t { StringIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, quote!(str))), - StringIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, quote!(String))), + StringIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, generics, quote!(String))), StringIrregularTrait::FromStr => { Ok(gen_impl_from_str(type_name, maybe_error_type_name.as_ref())) } StringIrregularTrait::From => Ok(gen_impl_from_str_and_string(type_name)), - StringIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, inner_type)), + StringIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, &Generics::default(), inner_type)), StringIrregularTrait::TryFrom => { Ok(gen_impl_try_from(type_name, maybe_error_type_name.as_ref())) } StringIrregularTrait::Borrow => Ok(gen_impl_borrow_str_and_string(type_name)), - StringIrregularTrait::Display => Ok(gen_impl_trait_display(type_name)), + StringIrregularTrait::Display => Ok(gen_impl_trait_display(type_name, &Generics::default())), StringIrregularTrait::Default => match maybe_default_value { Some(ref default_value) => { let has_validation = maybe_error_type_name.is_some(); @@ -249,8 +253,9 @@ fn gen_impl_from_str( } fn gen_impl_from_str_and_string(type_name: &TypeName) -> TokenStream { - let impl_from_string = gen_impl_trait_from(type_name, quote!(String)); - let impl_from_str = gen_impl_trait_from(type_name, quote!(&str)); + let generics = Generics::default(); + let impl_from_string = gen_impl_trait_from(type_name, &generics, quote!(String)); + let impl_from_str = gen_impl_trait_from(type_name, &generics, quote!(&str)); quote! { #impl_from_string @@ -262,9 +267,11 @@ fn gen_impl_try_from( type_name: &TypeName, maybe_error_type_name: Option<&ErrorTypeName>, ) -> TokenStream { + let generics = Generics::default(); let impl_try_from_string = - gen_impl_trait_try_from(type_name, quote!(String), maybe_error_type_name); - let impl_try_from_str = gen_impl_trait_try_from(type_name, quote!(&str), maybe_error_type_name); + gen_impl_trait_try_from(type_name, &generics, quote!(String), maybe_error_type_name); + let impl_try_from_str = + gen_impl_trait_try_from(type_name, &generics, quote!(&str), maybe_error_type_name); quote! { #impl_try_from_string @@ -273,8 +280,9 @@ fn gen_impl_try_from( } fn gen_impl_borrow_str_and_string(type_name: &TypeName) -> TokenStream { - let impl_borrow_string = gen_impl_trait_borrow(type_name, quote!(String)); - let impl_borrow_str = gen_impl_trait_borrow(type_name, quote!(str)); + let generics = Generics::default(); + let impl_borrow_string = gen_impl_trait_borrow(type_name, &generics, quote!(String)); + let impl_borrow_str = gen_impl_trait_borrow(type_name, &generics, quote!(str)); quote! { #impl_borrow_string diff --git a/test_suite/tests/any.rs b/test_suite/tests/any.rs index c588ed9..a30555d 100644 --- a/test_suite/tests/any.rs +++ b/test_suite/tests/any.rs @@ -1,4 +1,5 @@ use nutype::nutype; +use std::borrow::Cow; use test_suite::test_helpers::traits::*; // Inner custom type, which is unknown to nutype @@ -382,3 +383,121 @@ mod new_unchecked { assert_eq!(line_point.into_inner(), Point::new(3, 4)); } } + +#[cfg(test)] +mod with_generics { + use super::*; + + #[test] + fn test_generic_with_validate() { + #[nutype( + validate(predicate = |v| !v.is_empty()), + derive(Debug) + )] + struct NonEmptyVec(Vec); + + { + let vec = NonEmptyVec::new(vec![1, 2, 3]).unwrap(); + assert_eq!(vec.into_inner(), vec![1, 2, 3]); + } + + { + let vec = NonEmptyVec::new(vec![5]).unwrap(); + assert_eq!(vec.into_inner(), vec![5]); + } + + { + let vec: Vec = vec![]; + let err = NonEmptyVec::new(vec).unwrap_err(); + assert_eq!(err, NonEmptyVecError::PredicateViolated); + } + } + + #[test] + fn test_generic_with_sanitize() { + #[nutype( + sanitize(with = |mut v| { v.truncate(2); v }), + derive(Debug) + )] + struct UpToTwo(Vec); + + { + let vec = UpToTwo::new(vec![1, 2, 3]); + assert_eq!(vec.into_inner(), vec![1, 2]); + } + + { + let vec = UpToTwo::new(vec![5]); + assert_eq!(vec.into_inner(), vec![5]); + } + } + + #[test] + fn test_generic_with_sanitize_and_validate() { + #[nutype( + sanitize(with = |mut v| { v.truncate(2); v }), + validate(predicate = |v| !v.is_empty()), + derive(Debug) + )] + struct OneOrTwo(Vec); + + { + let vec = OneOrTwo::new(vec![1, 2, 3]).unwrap(); + assert_eq!(vec.into_inner(), vec![1, 2]); + } + + { + let vec = OneOrTwo::new(vec![5]).unwrap(); + assert_eq!(vec.into_inner(), vec![5]); + } + + { + let vec: Vec = vec![]; + let err = OneOrTwo::new(vec).unwrap_err(); + assert_eq!(err, OneOrTwoError::PredicateViolated); + } + } + + // TODO + // #[test] + // fn test_generic_with_boundaries_and_sanitize() { + // #[nutype( + // sanitize(with = |v| { v.sort(); v }), + // derive(Debug) + // )] + // struct SortedVec(Vec); + + // { + // let vec = NonEmptyVec::new(vec![1, 2, 3]).unwrap(); + // assert_eq!(vec.into_inner(), vec![1, 2, 3]); + // } + + // { + // let vec = NonEmptyVec::new(vec![5]).unwrap(); + // assert_eq!(vec.into_inner(), vec![5]); + // } + + // { + // let vec: Vec = vec![]; + // let err = NonEmptyVec::new(vec).unwrap_err(); + // assert_eq!(err, NonEmptyVecError::PredicateViolated); + // } + // } + + #[test] + fn test_generic_with_lifetime_cow() { + #[nutype( + validate(predicate = |s| s.len() >= 3), + derive(Debug, Display, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Into, Deref, Borrow, TryFrom) + )] + struct Clarabelle<'a>(Cow<'a, str>); + + { + let clarabelle = Clarabelle::new(Cow::Borrowed("Clarabelle")).unwrap(); + assert_eq!(clarabelle.to_string(), "Clarabelle"); + + let muu = Clarabelle::new(Cow::Owned("Muu".to_string())).unwrap(); + assert_eq!(muu.to_string(), "Muu"); + } + } +}