From abd542b283d093ba3e361789121ccffad9efd347 Mon Sep 17 00:00:00 2001 From: Serhii Potapov Date: Sat, 29 Jun 2024 18:44:14 +0200 Subject: [PATCH] Implement support of derive(Arbitrary) for generic newtypes --- .github/workflows/ci.yml | 16 +++++++++---- Justfile | 1 + dummy/src/main.rs | 18 ++++++++++----- nutype_macros/src/any/gen/traits/arbitrary.rs | 23 +++++++++++++------ nutype_macros/src/any/gen/traits/mod.rs | 2 +- nutype_macros/src/common/gen/mod.rs | 20 ++++++++++++++-- nutype_macros/src/common/gen/traits.rs | 2 +- test_suite/Cargo.toml | 1 + test_suite/tests/any.rs | 22 +++++++++++++++--- 9 files changed, 80 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33e50574..da39003d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,30 +19,36 @@ jobs: with: toolchain: stable - - name: cargo test --features nutype_test + - name: cargo test uses: actions-rs/cargo@v1 with: command: test - - name: cargo test --features nutype_test,serde + - name: cargo test --features serde uses: actions-rs/cargo@v1 with: command: test args: --features serde - - name: cargo test --features nutype_test,regex + - name: cargo test --features regex uses: actions-rs/cargo@v1 with: command: test args: --features regex - - name: cargo test --features nutype_test,new_unchecked + - name: cargo test --features new_unchecked uses: actions-rs/cargo@v1 with: command: test args: --features new_unchecked - - name: cargo test --features nutype_test,schemars08 + - name: cargo test --features arbitrary + uses: actions-rs/cargo@v1 + with: + command: test + args: --features arbitrary + + - name: cargo test --features schemars08 uses: actions-rs/cargo@v1 with: command: test diff --git a/Justfile b/Justfile index be61fb63..1de0d6c9 100644 --- a/Justfile +++ b/Justfile @@ -6,6 +6,7 @@ test-all: cargo test --features regex cargo test --features new_unchecked cargo test --features schemars08 + cargo test --features arbitrary cargo test --all-features test: diff --git a/dummy/src/main.rs b/dummy/src/main.rs index 2561bfd2..b82be915 100644 --- a/dummy/src/main.rs +++ b/dummy/src/main.rs @@ -1,9 +1,15 @@ +use arbitrary::Arbitrary; use nutype::nutype; -#[nutype( - validate(predicate = |n| n.is_even()), - derive(Debug, FromStr), -)] -struct Even(T); +#[nutype(derive(Debug, Arbitrary))] +struct Wrapper(Vec); -fn main() {} +fn main() { + fn gen(bytes: &[u8]) -> Wrapper { + let mut u = arbitrary::Unstructured::new(bytes); + Wrapper::::arbitrary(&mut u).unwrap() + } + assert_eq!(gen(&[]).into_inner(), vec![]); + assert_eq!(gen(&[1]).into_inner(), vec![false]); + assert_eq!(gen(&[1, 3, 5]).into_inner(), vec![true, false]); +} diff --git a/nutype_macros/src/any/gen/traits/arbitrary.rs b/nutype_macros/src/any/gen/traits/arbitrary.rs index 1b339d12..0bf5b51c 100644 --- a/nutype_macros/src/any/gen/traits/arbitrary.rs +++ b/nutype_macros/src/any/gen/traits/arbitrary.rs @@ -1,13 +1,16 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; +use syn::Generics; use crate::{ any::models::{AnyGuard, AnyInnerType}, + common::gen::{add_bound_to_all_type_params, add_param, strip_trait_bounds_on_generics}, common::models::TypeName, }; pub fn gen_impl_trait_arbitrary( type_name: &TypeName, + generics: &Generics, inner_type: &AnyInnerType, guard: &AnyGuard, ) -> Result { @@ -22,18 +25,24 @@ pub fn gen_impl_trait_arbitrary( // Generate implementation of `Arbitrary` trait, assuming that inner type implements Arbitrary // too. + let generics_without_bounds = strip_trait_bounds_on_generics(generics); + let generics_with_lifetime = add_param(&generics_without_bounds, quote!('nu_arb)); + let generics_with_bounds = add_bound_to_all_type_params( + &generics_with_lifetime, + quote!(::arbitrary::Arbitrary<'nu_arb>), + ); Ok(quote!( - impl ::arbitrary::Arbitrary<'_> for #type_name { - fn arbitrary(u: &mut ::arbitrary::Unstructured<'_>) -> ::arbitrary::Result { + impl #generics_with_bounds ::arbitrary::Arbitrary<'nu_arb> for #type_name #generics_without_bounds { + fn arbitrary(u: &mut ::arbitrary::Unstructured<'nu_arb>) -> ::arbitrary::Result { let inner_value: #inner_type = u.arbitrary()?; Ok(#type_name::new(inner_value)) } - } - #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - let n = ::core::mem::size_of::<#inner_type>(); - (n, Some(n)) + #[inline] + fn size_hint(_depth: usize) -> (usize, Option) { + let n = ::core::mem::size_of::<#inner_type>(); + (n, Some(n)) + } } )) } diff --git a/nutype_macros/src/any/gen/traits/mod.rs b/nutype_macros/src/any/gen/traits/mod.rs index 1d367167..c0476db0 100644 --- a/nutype_macros/src/any/gen/traits/mod.rs +++ b/nutype_macros/src/any/gen/traits/mod.rs @@ -182,7 +182,7 @@ fn gen_implemented_traits( AnyIrregularTrait::SerdeDeserialize => Ok( gen_impl_trait_serde_deserialize(type_name, generics, inner_type, maybe_error_type_name.as_ref()) ), - AnyIrregularTrait::ArbitraryArbitrary => arbitrary::gen_impl_trait_arbitrary(type_name, inner_type, guard), + AnyIrregularTrait::ArbitraryArbitrary => arbitrary::gen_impl_trait_arbitrary(type_name, generics, inner_type, guard), }) .collect() } diff --git a/nutype_macros/src/common/gen/mod.rs b/nutype_macros/src/common/gen/mod.rs index 7aa397bf..54e881a8 100644 --- a/nutype_macros/src/common/gen/mod.rs +++ b/nutype_macros/src/common/gen/mod.rs @@ -156,7 +156,7 @@ pub fn gen_impl_into_inner( /// /// Output: /// -fn strip_trait_bounds_on_generics(original: &Generics) -> Generics { +pub fn strip_trait_bounds_on_generics(original: &Generics) -> Generics { let mut generics = original.clone(); for param in &mut generics.params { if let syn::GenericParam::Type(syn::TypeParam { bounds, .. }) = param { @@ -174,7 +174,7 @@ fn strip_trait_bounds_on_generics(original: &Generics) -> Generics { /// /// Output: /// -fn add_bound_to_all_type_params(generics: &Generics, bound: TokenStream) -> Generics { +pub fn add_bound_to_all_type_params(generics: &Generics, bound: TokenStream) -> Generics { let mut generics = generics.clone(); let parsed_bound: syn::TypeParamBound = syn::parse2(bound).expect("Failed to parse TypeParamBound"); @@ -186,6 +186,22 @@ fn add_bound_to_all_type_params(generics: &Generics, bound: TokenStream) -> Gene generics } +/// Add a parameter to generics. +/// +/// Input: +/// +/// 'a +/// +/// Output: +/// <'a, T, U> +/// +pub fn add_param(generics: &Generics, param: TokenStream) -> Generics { + let mut generics = generics.clone(); + let parsed_param: syn::GenericParam = syn::parse2(param).expect("Failed to parse GenericParam"); + generics.params.push(parsed_param); + generics +} + pub trait GenerateNewtype { type Sanitizer; type Validator; diff --git a/nutype_macros/src/common/gen/traits.rs b/nutype_macros/src/common/gen/traits.rs index 98adbc69..52c3936a 100644 --- a/nutype_macros/src/common/gen/traits.rs +++ b/nutype_macros/src/common/gen/traits.rs @@ -20,7 +20,7 @@ pub struct GeneratedTraits { pub implement_traits: TokenStream, } -/// Split traits into 2 groups for generatation: +/// Split traits into 2 groups for generation: /// * Transparent traits can be simply derived, e.g. `derive(Debug)`. /// * Irregular traits requires implementation to be generated. pub enum GeneratableTrait { diff --git a/test_suite/Cargo.toml b/test_suite/Cargo.toml index 7baf4962..40d94c75 100644 --- a/test_suite/Cargo.toml +++ b/test_suite/Cargo.toml @@ -24,6 +24,7 @@ num = "0.4.3" [features] serde = ["nutype/serde", "dep:serde", "dep:serde_json"] regex = ["nutype/regex", "dep:regex", "dep:lazy_static", "dep:once_cell"] +arbitrary = ["nutype/arbitrary"] schemars08 = ["schemars"] new_unchecked = [] ui = [] diff --git a/test_suite/tests/any.rs b/test_suite/tests/any.rs index 7d43b9ca..3e5e9e36 100644 --- a/test_suite/tests/any.rs +++ b/test_suite/tests/any.rs @@ -751,9 +751,25 @@ mod with_generics { } } - #[test] - fn test_generic_boundaries_arbitrary() { - // TODO + mod generics_and_arbitrary { + use super::*; + use arbitrary::Arbitrary; + + #[nutype(derive(Debug, Arbitrary))] + struct Arbaro(Vec); + + fn gen(bytes: &[u8]) -> Vec { + let mut u = arbitrary::Unstructured::new(&bytes); + let arbraro = Arbaro::::arbitrary(&mut u).unwrap(); + arbraro.into_inner() + } + + #[test] + fn test_generic_boundaries_arbitrary() { + assert_eq!(gen(&[]), Vec::::new()); + assert_eq!(gen(&[1]), vec![false]); + assert_eq!(gen(&[1, 3, 5]), vec![true, false]); + } } #[test]