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 derive of Arbitrary for any inner type #119

Merged
merged 1 commit into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Support integration with [`arbitrary`](https://crates.io/crates/arbitrary) crate (see `arbitrary` feature).
* Support `Arbitrary` for integer types
* Support `Arbitrary` for float types
* Support `Arbitrary` for any inner types
* Ability to specify boundaries (`greater`, `greater_or_equal`, `less`, `less_or_equal`, `len_char_min`, `len_char_max`) with expressions or named constants.
* Add `#[inline]` attribute to trivial functions

Expand Down
23 changes: 23 additions & 0 deletions Cargo.lock

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

11 changes: 11 additions & 0 deletions examples/any_arbitrary/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "any_arbitrary"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
arbitrary = { version = "1.3.2", features = ["derive"] }
arbtest = "0.2.0"
nutype = { path = "../../nutype", features = ["arbitrary"] }
31 changes: 31 additions & 0 deletions examples/any_arbitrary/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use arbitrary::Arbitrary;
use nutype::nutype;

#[derive(Arbitrary)]
struct Point {
x: i32,
y: i32,
}

// Inner type is custom type Point, which implements Arbitrary.
// There is no validation, but custom sanitization.
// Deriving Arbitrary with custom validation would not be possible in this case.
#[nutype(
derive(Arbitrary),
sanitize(with = |mut point| {
point.x = point.x.clamp(0, 100);
point.y = point.y.clamp(-200, 200);
point
})
)]
pub struct Location(Point);

fn main() {
arbtest::builder().run(|u| {
let location = u.arbitrary::<Location>()?;
let point = location.into_inner();
assert!(point.x >= 0 && point.x <= 100);
assert!(point.y >= -200 && point.y <= 200);
Ok(())
});
}
7 changes: 4 additions & 3 deletions nutype_macros/src/any/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,16 @@ impl GenerateNewtype for AnyNewtype {
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<Self::TypedTrait>,
maybe_default_value: Option<syn::Expr>,
_guard: &AnyGuard,
guard: &AnyGuard,
) -> Result<GeneratedTraits, syn::Error> {
Ok(gen_traits(
gen_traits(
type_name,
inner_type,
maybe_error_type_name,
traits,
maybe_default_value,
))
guard,
)
}

fn gen_tests(
Expand Down
39 changes: 39 additions & 0 deletions nutype_macros/src/any/gen/traits/arbitrary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;

use crate::{
any::models::{AnyGuard, AnyInnerType},
common::models::TypeName,
};

pub fn gen_impl_trait_arbitrary(
type_name: &TypeName,
inner_type: &AnyInnerType,
guard: &AnyGuard,
) -> Result<TokenStream, syn::Error> {
// It's not possible to generate implementation of `Arbitrary` trait, because we don't know nor
// type nor validation rules.
if guard.has_validation() {
let msg = format!(
"Cannot derive trait `Arbitrary` for a custom type `{type_name}` which contains validation.\nYou have to implement `Arbitrary` trait manually to guarantee that it respects the validation rules.",
);
return Err(syn::Error::new(Span::call_site(), msg));
}

// Generate implementation of `Arbitrary` trait, assuming that inner type implements Arbitrary
// too.
Ok(quote!(
impl ::arbitrary::Arbitrary<'_> for #type_name {
fn arbitrary(u: &mut ::arbitrary::Unstructured<'_>) -> ::arbitrary::Result<Self> {
let inner_value: #inner_type = u.arbitrary()?;
Ok(#type_name::new(inner_value))
}
}

#[inline]
fn size_hint(_depth: usize) -> (usize, Option<usize>) {
let n = ::core::mem::size_of::<#inner_type>();
(n, Some(n))
}
))
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod arbitrary;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use std::collections::HashSet;

use crate::{
any::models::AnyDeriveTrait,
any::models::AnyInnerType,
any::models::{AnyGuard, AnyInnerType},
common::{
gen::traits::{
gen_impl_trait_as_ref, gen_impl_trait_borrow, gen_impl_trait_default,
Expand Down Expand Up @@ -49,6 +51,9 @@ impl From<AnyDeriveTrait> for AnyGeneratableTrait {
AnyDeriveTrait::SerdeDeserialize => {
AnyGeneratableTrait::Irregular(AnyIrregularTrait::SerdeDeserialize)
}
AnyDeriveTrait::ArbitraryArbitrary => {
AnyGeneratableTrait::Irregular(AnyIrregularTrait::ArbitraryArbitrary)
}
}
}
}
Expand Down Expand Up @@ -97,6 +102,7 @@ enum AnyIrregularTrait {
Default,
SerdeSerialize,
SerdeDeserialize,
ArbitraryArbitrary,
}

pub fn gen_traits(
Expand All @@ -105,7 +111,8 @@ pub fn gen_traits(
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<AnyDeriveTrait>,
maybe_default_value: Option<syn::Expr>,
) -> GeneratedTraits {
guard: &AnyGuard,
) -> Result<GeneratedTraits, syn::Error> {
let GeneratableTraits {
transparent_traits,
irregular_traits,
Expand All @@ -123,12 +130,13 @@ pub fn gen_traits(
maybe_error_type_name,
irregular_traits,
maybe_default_value,
);
guard,
)?;

GeneratedTraits {
Ok(GeneratedTraits {
derive_transparent_traits,
implement_traits,
}
})
}

fn gen_implemented_traits(
Expand All @@ -137,23 +145,24 @@ fn gen_implemented_traits(
maybe_error_type_name: Option<ErrorTypeName>,
impl_traits: Vec<AnyIrregularTrait>,
maybe_default_value: Option<syn::Expr>,
) -> TokenStream {
guard: &AnyGuard,
) -> Result<TokenStream, syn::Error> {
impl_traits
.iter()
.map(|t| match t {
AnyIrregularTrait::AsRef => gen_impl_trait_as_ref(type_name, inner_type),
AnyIrregularTrait::From => gen_impl_trait_from(type_name, inner_type),
AnyIrregularTrait::Into => gen_impl_trait_into(type_name, inner_type.clone()),
AnyIrregularTrait::Display => gen_impl_trait_display(type_name),
AnyIrregularTrait::Deref => gen_impl_trait_deref(type_name, inner_type),
AnyIrregularTrait::Borrow => gen_impl_trait_borrow(type_name, inner_type),
AnyIrregularTrait::FromStr => {
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::FromStr => Ok(
gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref())
}
AnyIrregularTrait::TryFrom => {
),
AnyIrregularTrait::TryFrom => Ok(
gen_impl_trait_try_from(type_name, inner_type, maybe_error_type_name.as_ref())
}
AnyIrregularTrait::Default => {
),
AnyIrregularTrait::Default => Ok(
match maybe_default_value {
Some(ref default_value) => {
let has_validation = maybe_error_type_name.is_some();
Expand All @@ -165,13 +174,14 @@ fn gen_implemented_traits(
);
}
}
}
AnyIrregularTrait::SerdeSerialize => {
),
AnyIrregularTrait::SerdeSerialize => Ok(
gen_impl_trait_serde_serialize(type_name)
}
AnyIrregularTrait::SerdeDeserialize => {
),
AnyIrregularTrait::SerdeDeserialize => Ok(
gen_impl_trait_serde_deserialize(type_name, inner_type, maybe_error_type_name.as_ref())
}
),
AnyIrregularTrait::ArbitraryArbitrary => arbitrary::gen_impl_trait_arbitrary(type_name, inner_type, guard),
})
.collect()
}
1 change: 1 addition & 0 deletions nutype_macros/src/any/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub enum AnyDeriveTrait {
// External crates
SerdeSerialize,
SerdeDeserialize,
ArbitraryArbitrary,
}

impl TypeTrait for AnyDeriveTrait {
Expand Down
6 changes: 1 addition & 5 deletions nutype_macros/src/any/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,7 @@ fn to_any_derive_trait(
DeriveTrait::SerdeSerialize => Ok(AnyDeriveTrait::SerdeSerialize),
DeriveTrait::SerdeDeserialize => Ok(AnyDeriveTrait::SerdeDeserialize),
DeriveTrait::Hash => Ok(AnyDeriveTrait::Hash),
DeriveTrait::ArbitraryArbitrary => {
// TODO: Allow deriving Arbitrary if there is no validation
let msg = "Deriving Arbitrary trait for any type is not yet possible";
Err(syn::Error::new(span, msg))
}
DeriveTrait::ArbitraryArbitrary => Ok(AnyDeriveTrait::ArbitraryArbitrary),
DeriveTrait::SchemarsJsonSchema => {
let msg =
format!("Deriving of trait `{tr:?}` is not (yet) supported for an arbitrary type");
Expand Down
Loading