Skip to content

Commit

Permalink
Merge pull request #135 from greyblake/generic-newtype
Browse files Browse the repository at this point in the history
Support for generic types
  • Loading branch information
greyblake authored Jun 1, 2024
2 parents b334d20 + 2cf0376 commit 0f79e72
Show file tree
Hide file tree
Showing 19 changed files with 355 additions and 85 deletions.
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

0 comments on commit 0f79e72

Please sign in to comment.