-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #138 from michaelsproul/specify-bounds
Allow trait bounds to be manually specified
- Loading branch information
Showing
6 changed files
with
324 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use crate::ARBITRARY_ATTRIBUTE_NAME; | ||
use syn::{ | ||
parse::Error, punctuated::Punctuated, DeriveInput, Lit, Meta, MetaNameValue, NestedMeta, Token, | ||
TypeParam, | ||
}; | ||
|
||
pub struct ContainerAttributes { | ||
/// Specify type bounds to be applied to the derived `Arbitrary` implementation instead of the | ||
/// default inferred bounds. | ||
/// | ||
/// ```ignore | ||
/// #[arbitrary(bound = "T: Default, U: Debug")] | ||
/// ``` | ||
/// | ||
/// Multiple attributes will be combined as long as they don't conflict, e.g. | ||
/// | ||
/// ```ignore | ||
/// #[arbitrary(bound = "T: Default")] | ||
/// #[arbitrary(bound = "U: Default")] | ||
/// ``` | ||
pub bounds: Option<Vec<Punctuated<TypeParam, Token![,]>>>, | ||
} | ||
|
||
impl ContainerAttributes { | ||
pub fn from_derive_input(derive_input: &DeriveInput) -> Result<Self, Error> { | ||
let mut bounds = None; | ||
|
||
for attr in &derive_input.attrs { | ||
if !attr.path.is_ident(ARBITRARY_ATTRIBUTE_NAME) { | ||
continue; | ||
} | ||
|
||
let meta_list = match attr.parse_meta()? { | ||
Meta::List(l) => l, | ||
_ => { | ||
return Err(Error::new_spanned( | ||
attr, | ||
format!( | ||
"invalid `{}` attribute. expected list", | ||
ARBITRARY_ATTRIBUTE_NAME | ||
), | ||
)) | ||
} | ||
}; | ||
|
||
for nested_meta in meta_list.nested.iter() { | ||
match nested_meta { | ||
NestedMeta::Meta(Meta::NameValue(MetaNameValue { | ||
path, | ||
lit: Lit::Str(bound_str_lit), | ||
.. | ||
})) if path.is_ident("bound") => { | ||
bounds | ||
.get_or_insert_with(Vec::new) | ||
.push(bound_str_lit.parse_with(Punctuated::parse_terminated)?); | ||
} | ||
_ => { | ||
return Err(Error::new_spanned( | ||
attr, | ||
format!( | ||
"invalid `{}` attribute. expected `bound = \"..\"`", | ||
ARBITRARY_ATTRIBUTE_NAME, | ||
), | ||
)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
Ok(Self { bounds }) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
#![cfg(feature = "derive")] | ||
|
||
use arbitrary::{Arbitrary, Unstructured}; | ||
|
||
fn arbitrary_from<'a, T: Arbitrary<'a>>(input: &'a [u8]) -> T { | ||
let mut buf = Unstructured::new(input); | ||
T::arbitrary(&mut buf).expect("can create arbitrary instance OK") | ||
} | ||
|
||
/// This wrapper trait *implies* `Arbitrary`, but the compiler isn't smart enough to work that out | ||
/// so when using this wrapper we *must* opt-out of the auto-generated `T: Arbitrary` bounds. | ||
pub trait WrapperTrait: for<'a> Arbitrary<'a> {} | ||
|
||
impl WrapperTrait for u32 {} | ||
|
||
#[derive(Arbitrary)] | ||
#[arbitrary(bound = "T: WrapperTrait")] | ||
struct GenericSingleBound<T: WrapperTrait> { | ||
t: T, | ||
} | ||
|
||
#[test] | ||
fn single_bound() { | ||
let v: GenericSingleBound<u32> = arbitrary_from(&[0, 0, 0, 0]); | ||
assert_eq!(v.t, 0); | ||
} | ||
|
||
#[derive(Arbitrary)] | ||
#[arbitrary(bound = "T: WrapperTrait, U: WrapperTrait")] | ||
struct GenericMultipleBoundsSingleAttribute<T: WrapperTrait, U: WrapperTrait> { | ||
t: T, | ||
u: U, | ||
} | ||
|
||
#[test] | ||
fn multiple_bounds_single_attribute() { | ||
let v: GenericMultipleBoundsSingleAttribute<u32, u32> = | ||
arbitrary_from(&[1, 0, 0, 0, 2, 0, 0, 0]); | ||
assert_eq!(v.t, 1); | ||
assert_eq!(v.u, 2); | ||
} | ||
|
||
#[derive(Arbitrary)] | ||
#[arbitrary(bound = "T: WrapperTrait")] | ||
#[arbitrary(bound = "U: Default")] | ||
struct GenericMultipleArbitraryAttributes<T: WrapperTrait, U: Default> { | ||
t: T, | ||
#[arbitrary(default)] | ||
u: U, | ||
} | ||
|
||
#[test] | ||
fn multiple_arbitrary_attributes() { | ||
let v: GenericMultipleArbitraryAttributes<u32, u32> = arbitrary_from(&[1, 0, 0, 0]); | ||
assert_eq!(v.t, 1); | ||
assert_eq!(v.u, 0); | ||
} | ||
|
||
#[derive(Arbitrary)] | ||
#[arbitrary(bound = "T: WrapperTrait", bound = "U: Default")] | ||
struct GenericMultipleBoundAttributes<T: WrapperTrait, U: Default> { | ||
t: T, | ||
#[arbitrary(default)] | ||
u: U, | ||
} | ||
|
||
#[test] | ||
fn multiple_bound_attributes() { | ||
let v: GenericMultipleBoundAttributes<u32, u32> = arbitrary_from(&[1, 0, 0, 0]); | ||
assert_eq!(v.t, 1); | ||
assert_eq!(v.u, 0); | ||
} | ||
|
||
#[derive(Arbitrary)] | ||
#[arbitrary(bound = "T: WrapperTrait", bound = "U: Default")] | ||
#[arbitrary(bound = "V: WrapperTrait, W: Default")] | ||
struct GenericMultipleArbitraryAndBoundAttributes< | ||
T: WrapperTrait, | ||
U: Default, | ||
V: WrapperTrait, | ||
W: Default, | ||
> { | ||
t: T, | ||
#[arbitrary(default)] | ||
u: U, | ||
v: V, | ||
#[arbitrary(default)] | ||
w: W, | ||
} | ||
|
||
#[test] | ||
fn multiple_arbitrary_and_bound_attributes() { | ||
let v: GenericMultipleArbitraryAndBoundAttributes<u32, u32, u32, u32> = | ||
arbitrary_from(&[1, 0, 0, 0, 2, 0, 0, 0]); | ||
assert_eq!(v.t, 1); | ||
assert_eq!(v.u, 0); | ||
assert_eq!(v.v, 2); | ||
assert_eq!(v.w, 0); | ||
} | ||
|
||
#[derive(Arbitrary)] | ||
#[arbitrary(bound = "T: Default")] | ||
struct GenericDefault<T: Default> { | ||
#[arbitrary(default)] | ||
x: T, | ||
} | ||
|
||
#[test] | ||
fn default_bound() { | ||
// We can write a generic func without any `Arbitrary` bound. | ||
fn generic_default<T: Default>() -> GenericDefault<T> { | ||
arbitrary_from(&[]) | ||
} | ||
|
||
assert_eq!(generic_default::<u64>().x, 0); | ||
assert_eq!(generic_default::<String>().x, String::new()); | ||
assert_eq!(generic_default::<Vec<u8>>().x, Vec::new()); | ||
} | ||
|
||
#[derive(Arbitrary)] | ||
#[arbitrary()] | ||
struct EmptyArbitraryAttribute { | ||
t: u32, | ||
} | ||
|
||
#[test] | ||
fn empty_arbitrary_attribute() { | ||
let v: EmptyArbitraryAttribute = arbitrary_from(&[1, 0, 0, 0]); | ||
assert_eq!(v.t, 1); | ||
} | ||
|
||
#[derive(Arbitrary)] | ||
#[arbitrary(bound = "")] | ||
struct EmptyBoundAttribute { | ||
t: u32, | ||
} | ||
|
||
#[test] | ||
fn empty_bound_attribute() { | ||
let v: EmptyBoundAttribute = arbitrary_from(&[1, 0, 0, 0]); | ||
assert_eq!(v.t, 1); | ||
} |