Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for generic types #135

Merged
merged 12 commits into from
Jun 1, 2024
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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 }` )
Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
14 changes: 7 additions & 7 deletions dummy/src/main.rs
Original file line number Diff line number Diff line change
@@ -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");
}
9 changes: 9 additions & 0 deletions examples/any_generics/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
48 changes: 48 additions & 0 deletions examples/any_generics/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use nutype::nutype;
use std::borrow::Cow;

#[nutype(
validate(predicate = |vec| !vec.is_empty()),
derive(Debug),
)]
struct NotEmpty<T>(Vec<T>);

#[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::<i32>::new(vec![]).unwrap_err();
assert_eq!(err, NotEmptyError::PredicateViolated);
}

{
let muu = Clarabelle::new(Cow::Borrowed("Muu"));
assert_eq!(muu.to_string(), "Muu");
}
}
23 changes: 19 additions & 4 deletions nutype_macros/src/any/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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
}
Expand All @@ -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()
Expand All @@ -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(())
}
Expand All @@ -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<ErrorTypeName>,
traits: HashSet<Self::TypedTrait>,
Expand All @@ -112,6 +126,7 @@ impl GenerateNewtype for AnyNewtype {
) -> Result<GeneratedTraits, syn::Error> {
gen_traits(
type_name,
generics,
inner_type,
maybe_error_type_name,
traits,
Expand Down
15 changes: 9 additions & 6 deletions nutype_macros/src/any/gen/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ enum AnyIrregularTrait {

pub fn gen_traits(
type_name: &TypeName,
generics: &syn::Generics,
inner_type: &AnyInnerType,
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<AnyDeriveTrait>,
Expand All @@ -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,
Expand All @@ -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<ErrorTypeName>,
impl_traits: Vec<AnyIrregularTrait>,
Expand All @@ -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) => {
Expand Down
60 changes: 36 additions & 24 deletions nutype_macros/src/common/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -178,6 +182,7 @@ pub trait GenerateNewtype {

fn gen_traits(
type_name: &TypeName,
generics: &Generics,
inner_type: &Self::InnerType,
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<Self::TypedTrait>,
Expand All @@ -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 {
(
Expand All @@ -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<Self, #error_type_name> {
// 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 {
(
Expand All @@ -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<Self::Sanitizer, Self::Validator>,
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! {
Expand All @@ -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<ErrorTypeName> = match guard {
Guard::WithoutValidation { .. } => None,
Expand Down Expand Up @@ -335,6 +346,7 @@ pub trait GenerateNewtype {
implement_traits,
} = Self::gen_traits(
&type_name,
&generics,
&inner_type,
maybe_error_type_name,
traits,
Expand All @@ -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
Expand Down
Loading
Loading