Skip to content

Commit

Permalink
Merge pull request #158 from greyblake/fix-derive-as-ref-with-generic…
Browse files Browse the repository at this point in the history
…-boundaries

Fix derive as ref with generic boundaries
  • Loading branch information
greyblake authored Jul 6, 2024
2 parents 5467722 + 2a4c255 commit 9550f04
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 30 deletions.
27 changes: 24 additions & 3 deletions dummy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
use nutype::nutype;

#[nutype(validate(len_char_min = 5), default = "Anonymous", derive(Default))]
pub struct Name(String);
#[nutype(
sanitize(with = |mut v| { v.sort(); v }),
validate(predicate = |vec| !vec.is_empty()),
derive(Debug, AsRef, PartialEq, Deref),
)]
struct SortedNotEmptyVec<T: Ord>(Vec<T>);

fn main() {}
fn main() {
// Empty list is not allowed
assert_eq!(
SortedNotEmptyVec::<&str>::try_new(vec![]),
Err(SortedNotEmptyVecError::PredicateViolated)
);

let wise_friends =
SortedNotEmptyVec::try_new(vec!["Seneca", "Zeno", "Socrates", "Epictetus", "Plato"])
.unwrap();

assert_eq!(
wise_friends.as_ref(),
&["Epictetus", "Plato", "Seneca", "Socrates", "Zeno"]
);

assert_eq!(wise_friends.len(), 5);
}
62 changes: 35 additions & 27 deletions nutype_macros/src/common/gen/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct GeneratedTraits {

/// Split traits into 2 groups for generation:
/// * Transparent traits can be simply derived, e.g. `derive(Debug)`.
/// * Irregular traits requires implementation to be generated.
/// * Irregular traits requires custom implementation to be generated.
pub enum GeneratableTrait<TransparentTrait, IrregularTrait> {
Transparent(TransparentTrait),
Irregular(IrregularTrait),
Expand Down Expand Up @@ -81,13 +81,15 @@ pub fn gen_impl_trait_as_ref(
generics: &Generics,
inner_type: impl ToTokens,
) -> TokenStream {
let generics_without_bounds = strip_trait_bounds_on_generics(generics);

quote! {
impl #generics ::core::convert::AsRef<#inner_type> for #type_name #generics {
#[inline]
fn as_ref(&self) -> &#inner_type {
&self.0
}
}
impl #generics ::core::convert::AsRef<#inner_type> for #type_name #generics_without_bounds { // impl<T: Ord> AsRef<Vec<T>> for Collection<T> {
#[inline] // #[inline]
fn as_ref(&self) -> &#inner_type { // fn as_ref(&self) -> &Vec<T> {
&self.0 // &self.0
} // }
} // }
}
}

Expand All @@ -96,15 +98,17 @@ pub fn gen_impl_trait_deref(
generics: &Generics,
inner_type: impl ToTokens,
) -> TokenStream {
quote! {
impl #generics ::core::ops::Deref for #type_name #generics {
type Target = #inner_type;
let generics_without_bounds = strip_trait_bounds_on_generics(generics);

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
quote! {
impl #generics ::core::ops::Deref for #type_name #generics_without_bounds { // impl<T: Ord> Deref for Collection<T> {
type Target = #inner_type; // type Target = Vec<T>;
//
#[inline] // #[inline]
fn deref(&self) -> &Self::Target { // fn deref(&self) -> &Self::Target {
&self.0 // &self.0
} // }
} // }
}
}

Expand Down Expand Up @@ -135,13 +139,15 @@ pub fn gen_impl_trait_borrow(
generics: &Generics,
borrowed_type: impl ToTokens,
) -> TokenStream {
let generics_without_bounds = strip_trait_bounds_on_generics(generics);

quote! {
impl #generics ::core::borrow::Borrow<#borrowed_type> for #type_name #generics {
#[inline]
fn borrow(&self) -> &#borrowed_type {
&self.0
}
}
impl #generics ::core::borrow::Borrow<#borrowed_type> for #type_name #generics_without_bounds { // impl<T: Ord> Borrow<Vec<T>> for Collection<T> {
#[inline] // #[inline]
fn borrow(&self) -> &#borrowed_type { // fn borrow(&self) -> &Vec<T> {
&self.0 // &self.0
} // }
} // }
}
}

Expand All @@ -150,13 +156,15 @@ pub fn gen_impl_trait_from(
generics: &Generics,
inner_type: impl ToTokens,
) -> TokenStream {
let generics_without_bounds = strip_trait_bounds_on_generics(generics);

quote! {
impl #generics ::core::convert::From<#inner_type> for #type_name #generics {
#[inline]
fn from(raw_value: #inner_type) -> Self {
Self::new(raw_value)
}
}
impl #generics ::core::convert::From<#inner_type> for #type_name #generics_without_bounds { // impl<T: Ord> From<Vec<T>> for Collection<T> {
#[inline] // #[inline]
fn from(raw_value: #inner_type) -> Self { // fn from(raw_value: Vec<T>) -> Self {
Self::new(raw_value) // Self::new(raw_value)
} // }
} // }
}
}

Expand Down
61 changes: 61 additions & 0 deletions test_suite/tests/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,8 +700,22 @@ mod with_generics {
}
}

#[test]
fn test_generic_from_with_bounds_and_sanitization() {
#[nutype(
sanitize(with = |mut v| { v.sort(); v }),
derive(Debug, From),
)]
struct Sorted<T: Ord>(Vec<T>);

let sorted: Sorted<i32> = Sorted::from(vec![3, 1, 2]);
assert_eq!(sorted.into_inner(), vec![1, 2, 3]);
}

#[test]
fn test_generic_from_str_without_validation() {
// Note: the code generate for FromStr relies on "associated type bounds" feature, which is
// stabilized only in 1.79.
#[nutype(derive(Debug, FromStr))]
struct Parseable<T>(T);

Expand Down Expand Up @@ -835,6 +849,53 @@ mod with_generics {
assert_eq!(squares.as_ref(), &inner_map);
}

#[test]
fn test_derive_as_ref_with_generic_boundaries_and_validation_and_sanitization() {
#[nutype(
sanitize(with = |mut v| { v.sort(); v }),
validate(predicate = |vec| !vec.is_empty()),
derive(Debug, AsRef, PartialEq),
)]
struct Friends<T: Ord>(Vec<T>);

assert_eq!(
Friends::<&str>::try_new(vec![]),
Err(FriendsError::PredicateViolated)
);

let wise_friends = Friends::try_new(vec!["Seneca", "Zeno", "Aristotle"]).unwrap();
assert_eq!(wise_friends.as_ref(), &["Aristotle", "Seneca", "Zeno"]);
}

#[test]
fn test_derive_deref_with_generic_boundaries_and_validation_and_sanitization() {
#[nutype(
sanitize(with = |mut v| { v.sort(); v }),
validate(predicate = |vec| !vec.is_empty()),
derive(Debug, Deref),
)]
struct Penguins<T: Ord>(Vec<T>);

let penguins = Penguins::try_new(vec!["Tux", "Chilly Willy"]).unwrap();
assert_eq!(penguins.len(), 2);
}

#[test]
fn test_derive_borrow_with_generic_boundaries_and_validation_and_sanitization() {
use std::borrow::Borrow;

#[nutype(
sanitize(with = |mut v| { v.sort(); v }),
validate(predicate = |vec| !vec.is_empty()),
derive(Debug, Borrow),
)]
struct Heroes<T: Ord>(Vec<T>);

let heroes = Heroes::try_new(vec!["Spiderman", "Batman"]).unwrap();
let borrowed_heroes: &Vec<&str> = heroes.borrow();
assert_eq!(borrowed_heroes, &vec!["Batman", "Spiderman"]);
}

#[test]
fn test_derive_default_with_generics() {
#[nutype(
Expand Down

0 comments on commit 9550f04

Please sign in to comment.