Skip to content

Commit 1bc35f9

Browse files
authored
Merge pull request #119 from greyblake/arbitrary-any
Support derive of Arbitrary for any inner type
2 parents 5e4b930 + 469ab9d commit 1bc35f9

File tree

9 files changed

+143
-30
lines changed

9 files changed

+143
-30
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Support integration with [`arbitrary`](https://crates.io/crates/arbitrary) crate (see `arbitrary` feature).
44
* Support `Arbitrary` for integer types
55
* Support `Arbitrary` for float types
6+
* Support `Arbitrary` for any inner types
67
* Ability to specify boundaries (`greater`, `greater_or_equal`, `less`, `less_or_equal`, `len_char_min`, `len_char_max`) with expressions or named constants.
78
* Add `#[inline]` attribute to trivial functions
89

Cargo.lock

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/any_arbitrary/Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "any_arbitrary"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
arbitrary = { version = "1.3.2", features = ["derive"] }
10+
arbtest = "0.2.0"
11+
nutype = { path = "../../nutype", features = ["arbitrary"] }

examples/any_arbitrary/src/main.rs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use arbitrary::Arbitrary;
2+
use nutype::nutype;
3+
4+
#[derive(Arbitrary)]
5+
struct Point {
6+
x: i32,
7+
y: i32,
8+
}
9+
10+
// Inner type is custom type Point, which implements Arbitrary.
11+
// There is no validation, but custom sanitization.
12+
// Deriving Arbitrary with custom validation would not be possible in this case.
13+
#[nutype(
14+
derive(Arbitrary),
15+
sanitize(with = |mut point| {
16+
point.x = point.x.clamp(0, 100);
17+
point.y = point.y.clamp(-200, 200);
18+
point
19+
})
20+
)]
21+
pub struct Location(Point);
22+
23+
fn main() {
24+
arbtest::builder().run(|u| {
25+
let location = u.arbitrary::<Location>()?;
26+
let point = location.into_inner();
27+
assert!(point.x >= 0 && point.x <= 100);
28+
assert!(point.y >= -200 && point.y <= 200);
29+
Ok(())
30+
});
31+
}

nutype_macros/src/any/gen/mod.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,16 @@ impl GenerateNewtype for AnyNewtype {
108108
maybe_error_type_name: Option<ErrorTypeName>,
109109
traits: HashSet<Self::TypedTrait>,
110110
maybe_default_value: Option<syn::Expr>,
111-
_guard: &AnyGuard,
111+
guard: &AnyGuard,
112112
) -> Result<GeneratedTraits, syn::Error> {
113-
Ok(gen_traits(
113+
gen_traits(
114114
type_name,
115115
inner_type,
116116
maybe_error_type_name,
117117
traits,
118118
maybe_default_value,
119-
))
119+
guard,
120+
)
120121
}
121122

122123
fn gen_tests(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use proc_macro2::{Span, TokenStream};
2+
use quote::quote;
3+
4+
use crate::{
5+
any::models::{AnyGuard, AnyInnerType},
6+
common::models::TypeName,
7+
};
8+
9+
pub fn gen_impl_trait_arbitrary(
10+
type_name: &TypeName,
11+
inner_type: &AnyInnerType,
12+
guard: &AnyGuard,
13+
) -> Result<TokenStream, syn::Error> {
14+
// It's not possible to generate implementation of `Arbitrary` trait, because we don't know nor
15+
// type nor validation rules.
16+
if guard.has_validation() {
17+
let msg = format!(
18+
"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.",
19+
);
20+
return Err(syn::Error::new(Span::call_site(), msg));
21+
}
22+
23+
// Generate implementation of `Arbitrary` trait, assuming that inner type implements Arbitrary
24+
// too.
25+
Ok(quote!(
26+
impl ::arbitrary::Arbitrary<'_> for #type_name {
27+
fn arbitrary(u: &mut ::arbitrary::Unstructured<'_>) -> ::arbitrary::Result<Self> {
28+
let inner_value: #inner_type = u.arbitrary()?;
29+
Ok(#type_name::new(inner_value))
30+
}
31+
}
32+
33+
#[inline]
34+
fn size_hint(_depth: usize) -> (usize, Option<usize>) {
35+
let n = ::core::mem::size_of::<#inner_type>();
36+
(n, Some(n))
37+
}
38+
))
39+
}

nutype_macros/src/any/gen/traits.rs nutype_macros/src/any/gen/traits/mod.rs

+32-22
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
pub mod arbitrary;
2+
13
use proc_macro2::TokenStream;
24
use quote::{quote, ToTokens};
35
use std::collections::HashSet;
46

57
use crate::{
68
any::models::AnyDeriveTrait,
7-
any::models::AnyInnerType,
9+
any::models::{AnyGuard, AnyInnerType},
810
common::{
911
gen::traits::{
1012
gen_impl_trait_as_ref, gen_impl_trait_borrow, gen_impl_trait_default,
@@ -49,6 +51,9 @@ impl From<AnyDeriveTrait> for AnyGeneratableTrait {
4951
AnyDeriveTrait::SerdeDeserialize => {
5052
AnyGeneratableTrait::Irregular(AnyIrregularTrait::SerdeDeserialize)
5153
}
54+
AnyDeriveTrait::ArbitraryArbitrary => {
55+
AnyGeneratableTrait::Irregular(AnyIrregularTrait::ArbitraryArbitrary)
56+
}
5257
}
5358
}
5459
}
@@ -97,6 +102,7 @@ enum AnyIrregularTrait {
97102
Default,
98103
SerdeSerialize,
99104
SerdeDeserialize,
105+
ArbitraryArbitrary,
100106
}
101107

102108
pub fn gen_traits(
@@ -105,7 +111,8 @@ pub fn gen_traits(
105111
maybe_error_type_name: Option<ErrorTypeName>,
106112
traits: HashSet<AnyDeriveTrait>,
107113
maybe_default_value: Option<syn::Expr>,
108-
) -> GeneratedTraits {
114+
guard: &AnyGuard,
115+
) -> Result<GeneratedTraits, syn::Error> {
109116
let GeneratableTraits {
110117
transparent_traits,
111118
irregular_traits,
@@ -123,12 +130,13 @@ pub fn gen_traits(
123130
maybe_error_type_name,
124131
irregular_traits,
125132
maybe_default_value,
126-
);
133+
guard,
134+
)?;
127135

128-
GeneratedTraits {
136+
Ok(GeneratedTraits {
129137
derive_transparent_traits,
130138
implement_traits,
131-
}
139+
})
132140
}
133141

134142
fn gen_implemented_traits(
@@ -137,23 +145,24 @@ fn gen_implemented_traits(
137145
maybe_error_type_name: Option<ErrorTypeName>,
138146
impl_traits: Vec<AnyIrregularTrait>,
139147
maybe_default_value: Option<syn::Expr>,
140-
) -> TokenStream {
148+
guard: &AnyGuard,
149+
) -> Result<TokenStream, syn::Error> {
141150
impl_traits
142151
.iter()
143152
.map(|t| match t {
144-
AnyIrregularTrait::AsRef => gen_impl_trait_as_ref(type_name, inner_type),
145-
AnyIrregularTrait::From => gen_impl_trait_from(type_name, inner_type),
146-
AnyIrregularTrait::Into => gen_impl_trait_into(type_name, inner_type.clone()),
147-
AnyIrregularTrait::Display => gen_impl_trait_display(type_name),
148-
AnyIrregularTrait::Deref => gen_impl_trait_deref(type_name, inner_type),
149-
AnyIrregularTrait::Borrow => gen_impl_trait_borrow(type_name, inner_type),
150-
AnyIrregularTrait::FromStr => {
153+
AnyIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, inner_type)),
154+
AnyIrregularTrait::From => Ok(gen_impl_trait_from(type_name, inner_type)),
155+
AnyIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, inner_type.clone())),
156+
AnyIrregularTrait::Display => Ok(gen_impl_trait_display(type_name)),
157+
AnyIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, inner_type)),
158+
AnyIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, inner_type)),
159+
AnyIrregularTrait::FromStr => Ok(
151160
gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref())
152-
}
153-
AnyIrregularTrait::TryFrom => {
161+
),
162+
AnyIrregularTrait::TryFrom => Ok(
154163
gen_impl_trait_try_from(type_name, inner_type, maybe_error_type_name.as_ref())
155-
}
156-
AnyIrregularTrait::Default => {
164+
),
165+
AnyIrregularTrait::Default => Ok(
157166
match maybe_default_value {
158167
Some(ref default_value) => {
159168
let has_validation = maybe_error_type_name.is_some();
@@ -165,13 +174,14 @@ fn gen_implemented_traits(
165174
);
166175
}
167176
}
168-
}
169-
AnyIrregularTrait::SerdeSerialize => {
177+
),
178+
AnyIrregularTrait::SerdeSerialize => Ok(
170179
gen_impl_trait_serde_serialize(type_name)
171-
}
172-
AnyIrregularTrait::SerdeDeserialize => {
180+
),
181+
AnyIrregularTrait::SerdeDeserialize => Ok(
173182
gen_impl_trait_serde_deserialize(type_name, inner_type, maybe_error_type_name.as_ref())
174-
}
183+
),
184+
AnyIrregularTrait::ArbitraryArbitrary => arbitrary::gen_impl_trait_arbitrary(type_name, inner_type, guard),
175185
})
176186
.collect()
177187
}

nutype_macros/src/any/models.rs

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub enum AnyDeriveTrait {
4848
// External crates
4949
SerdeSerialize,
5050
SerdeDeserialize,
51+
ArbitraryArbitrary,
5152
}
5253

5354
impl TypeTrait for AnyDeriveTrait {

nutype_macros/src/any/validate.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,7 @@ fn to_any_derive_trait(
9494
DeriveTrait::SerdeSerialize => Ok(AnyDeriveTrait::SerdeSerialize),
9595
DeriveTrait::SerdeDeserialize => Ok(AnyDeriveTrait::SerdeDeserialize),
9696
DeriveTrait::Hash => Ok(AnyDeriveTrait::Hash),
97-
DeriveTrait::ArbitraryArbitrary => {
98-
// TODO: Allow deriving Arbitrary if there is no validation
99-
let msg = "Deriving Arbitrary trait for any type is not yet possible";
100-
Err(syn::Error::new(span, msg))
101-
}
97+
DeriveTrait::ArbitraryArbitrary => Ok(AnyDeriveTrait::ArbitraryArbitrary),
10298
DeriveTrait::SchemarsJsonSchema => {
10399
let msg =
104100
format!("Deriving of trait `{tr:?}` is not (yet) supported for an arbitrary type");

0 commit comments

Comments
 (0)