diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 81c0274..18140eb 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -64,7 +64,7 @@ fn expand_derive_arbitrary(input: syn::DeriveInput) -> Result { } #[automatically_derived] - impl #impl_generics arbitrary::Arbitrary<#lifetime_without_bounds> for #name #ty_generics #where_clause { + impl #impl_generics ::arbitrary::Arbitrary<#lifetime_without_bounds> for #name #ty_generics #where_clause { #arbitrary_method #size_hint_method } @@ -141,7 +141,7 @@ fn add_trait_bounds(mut generics: Generics, lifetime: LifetimeParam) -> Generics if let GenericParam::Type(type_param) = param { type_param .bounds - .push(parse_quote!(arbitrary::Arbitrary<#lifetime>)); + .push(parse_quote!(::arbitrary::Arbitrary<#lifetime>)); } } generics @@ -156,7 +156,7 @@ fn with_recursive_count_guard( if guard_against_recursion { #recursive_count.with(|count| { if count.get() > 0 { - return Err(arbitrary::Error::NotEnoughData); + return Err(::arbitrary::Error::NotEnoughData); } count.set(count.get() + 1); Ok(()) @@ -194,11 +194,11 @@ fn gen_arbitrary_method( with_recursive_count_guard(recursive_count, quote! { Ok(#ident #arbitrary_take_rest) }); Ok(quote! { - fn arbitrary(u: &mut arbitrary::Unstructured<#lifetime>) -> arbitrary::Result { + fn arbitrary(u: &mut ::arbitrary::Unstructured<#lifetime>) -> ::arbitrary::Result { #body } - fn arbitrary_take_rest(mut u: arbitrary::Unstructured<#lifetime>) -> arbitrary::Result { + fn arbitrary_take_rest(mut u: ::arbitrary::Unstructured<#lifetime>) -> ::arbitrary::Result { #take_rest_body } }) @@ -225,7 +225,7 @@ fn gen_arbitrary_method( // Use a multiply + shift to generate a ranged random number // with slight bias. For details, see: // https://lemire.me/blog/2016/06/30/fast-random-shuffling - Ok(match (u64::from(::arbitrary(#unstructured)?) * #count) >> 32 { + Ok(match (u64::from(::arbitrary(#unstructured)?) * #count) >> 32 { #(#variants,)* _ => unreachable!() }) @@ -279,11 +279,11 @@ fn gen_arbitrary_method( let arbitrary_take_rest = arbitrary_enum_method(recursive_count, quote! { &mut u }, &variants_take_rest); quote! { - fn arbitrary(u: &mut arbitrary::Unstructured<#lifetime>) -> arbitrary::Result { + fn arbitrary(u: &mut ::arbitrary::Unstructured<#lifetime>) -> ::arbitrary::Result { #arbitrary } - fn arbitrary_take_rest(mut u: arbitrary::Unstructured<#lifetime>) -> arbitrary::Result { + fn arbitrary_take_rest(mut u: ::arbitrary::Unstructured<#lifetime>) -> ::arbitrary::Result { #arbitrary_take_rest } } @@ -344,9 +344,9 @@ fn construct_take_rest(fields: &Fields) -> Result { FieldConstructor::Default => quote!(::core::default::Default::default()), FieldConstructor::Arbitrary => { if idx + 1 == fields.len() { - quote! { arbitrary::Arbitrary::arbitrary_take_rest(u)? } + quote! { ::arbitrary::Arbitrary::arbitrary_take_rest(u)? } } else { - quote! { arbitrary::Arbitrary::arbitrary(&mut u)? } + quote! { ::arbitrary::Arbitrary::arbitrary(&mut u)? } } } FieldConstructor::With(function_or_closure) => quote!((#function_or_closure)(&mut u)?), @@ -364,17 +364,17 @@ fn gen_size_hint_method(input: &DeriveInput) -> Result { determine_field_constructor(f).map(|field_constructor| { match field_constructor { FieldConstructor::Default | FieldConstructor::Value(_) => { - quote!(Ok((0, Some(0)))) + quote!(::arbitrary::SizeHint::exactly(0)) } FieldConstructor::Arbitrary => { - quote! { <#ty as arbitrary::Arbitrary>::try_size_hint(depth) } + quote! { context.get::<#ty>() } } // Note that in this case it's hard to determine what size_hint must be, so size_of::() is // just an educated guess, although it's gonna be inaccurate for dynamically // allocated types (Vec, HashMap, etc.). FieldConstructor::With(_) => { - quote! { Ok((::core::mem::size_of::<#ty>(), None)) } + quote! { ::arbitrary::SizeHint::at_least(::core::mem::size_of::<#ty>()) } } } }) @@ -382,9 +382,9 @@ fn gen_size_hint_method(input: &DeriveInput) -> Result { .collect::>>() .map(|hints| { quote! { - Ok(arbitrary::size_hint::and_all(&[ - #( #hints? ),* - ])) + ::arbitrary::SizeHint::and_all(&[ + #( #hints ),* + ]) } }) }; @@ -392,13 +392,8 @@ fn gen_size_hint_method(input: &DeriveInput) -> Result { size_hint_fields(fields).map(|hint| { quote! { #[inline] - fn size_hint(depth: usize) -> (usize, ::core::option::Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> ::core::result::Result<(usize, ::core::option::Option), arbitrary::MaxRecursionReached> { - arbitrary::size_hint::try_recursion_guard(depth, |depth| #hint) + fn size_hint(context: &::arbitrary::size_hint::Context) -> ::arbitrary::size_hint::SizeHint { + #hint } } }) @@ -418,17 +413,12 @@ fn gen_size_hint_method(input: &DeriveInput) -> Result { .collect::>>() .map(|variants| { quote! { - fn size_hint(depth: usize) -> (usize, ::core::option::Option) { - Self::try_size_hint(depth).unwrap_or_default() - } #[inline] - fn try_size_hint(depth: usize) -> ::core::result::Result<(usize, ::core::option::Option), arbitrary::MaxRecursionReached> { - Ok(arbitrary::size_hint::and( - ::try_size_hint(depth)?, - arbitrary::size_hint::try_recursion_guard(depth, |depth| { - Ok(arbitrary::size_hint::or_all(&[ #( #variants? ),* ])) - })?, - )) + fn size_hint(context: &::arbitrary::size_hint::Context) -> ::arbitrary::size_hint::SizeHint { + ::arbitrary::SizeHint::and( + ::size_hint(context), + ::arbitrary::SizeHint::or_all(&[ #( #variants ),* ]) + ) } } }), @@ -438,7 +428,7 @@ fn gen_size_hint_method(input: &DeriveInput) -> Result { fn gen_constructor_for_field(field: &Field) -> Result { let ctor = match determine_field_constructor(field)? { FieldConstructor::Default => quote!(::core::default::Default::default()), - FieldConstructor::Arbitrary => quote!(arbitrary::Arbitrary::arbitrary(u)?), + FieldConstructor::Arbitrary => quote!(::arbitrary::Arbitrary::arbitrary(u)?), FieldConstructor::With(function_or_closure) => quote!((#function_or_closure)(u)?), FieldConstructor::Value(value) => quote!(#value), }; diff --git a/src/foreign/alloc/borrow.rs b/src/foreign/alloc/borrow.rs index b5b1e22..f03898b 100644 --- a/src/foreign/alloc/borrow.rs +++ b/src/foreign/alloc/borrow.rs @@ -13,14 +13,7 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), crate::MaxRecursionReached> { - size_hint::try_recursion_guard(depth, |depth| { - <::Owned as Arbitrary>::try_size_hint(depth) - }) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + <::Owned as Arbitrary>::size_hint(context) } } diff --git a/src/foreign/alloc/boxed.rs b/src/foreign/alloc/boxed.rs index c3014a3..4024bdb 100644 --- a/src/foreign/alloc/boxed.rs +++ b/src/foreign/alloc/boxed.rs @@ -12,13 +12,8 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), crate::MaxRecursionReached> { - size_hint::try_recursion_guard(depth, ::try_size_hint) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() } } @@ -35,8 +30,8 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } @@ -46,7 +41,8 @@ impl<'a> Arbitrary<'a> for Box { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - ::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + // known non-recursive + ::size_hint(context) } } diff --git a/src/foreign/alloc/collections/binary_heap.rs b/src/foreign/alloc/collections/binary_heap.rs index 25a0384..1595553 100644 --- a/src/foreign/alloc/collections/binary_heap.rs +++ b/src/foreign/alloc/collections/binary_heap.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::collections::binary_heap::BinaryHeap, }; @@ -16,7 +16,7 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } diff --git a/src/foreign/alloc/collections/btree_map.rs b/src/foreign/alloc/collections/btree_map.rs index 21b93a4..4b5f5ce 100644 --- a/src/foreign/alloc/collections/btree_map.rs +++ b/src/foreign/alloc/collections/btree_map.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::collections::btree_map::BTreeMap, }; @@ -17,7 +17,7 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } diff --git a/src/foreign/alloc/collections/btree_set.rs b/src/foreign/alloc/collections/btree_set.rs index 8c6e92f..b85d5ca 100644 --- a/src/foreign/alloc/collections/btree_set.rs +++ b/src/foreign/alloc/collections/btree_set.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::collections::btree_set::BTreeSet, }; @@ -16,7 +16,7 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } diff --git a/src/foreign/alloc/collections/linked_list.rs b/src/foreign/alloc/collections/linked_list.rs index 6bf2e98..fe4b6dd 100644 --- a/src/foreign/alloc/collections/linked_list.rs +++ b/src/foreign/alloc/collections/linked_list.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::collections::linked_list::LinkedList, }; @@ -16,7 +16,7 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } diff --git a/src/foreign/alloc/collections/vec_deque.rs b/src/foreign/alloc/collections/vec_deque.rs index 40e0413..521a3e6 100644 --- a/src/foreign/alloc/collections/vec_deque.rs +++ b/src/foreign/alloc/collections/vec_deque.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::collections::vec_deque::VecDeque, }; @@ -16,7 +16,7 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } diff --git a/src/foreign/alloc/ffi/c_str.rs b/src/foreign/alloc/ffi/c_str.rs index a1b2383..6e11ca7 100644 --- a/src/foreign/alloc/ffi/c_str.rs +++ b/src/foreign/alloc/ffi/c_str.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::ffi::CString, }; @@ -13,7 +13,8 @@ impl<'a> Arbitrary<'a> for CString { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - as Arbitrary>::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + // known non-recursive + as Arbitrary>::size_hint(context) } } diff --git a/src/foreign/alloc/rc.rs b/src/foreign/alloc/rc.rs index 6d58167..9f506e2 100644 --- a/src/foreign/alloc/rc.rs +++ b/src/foreign/alloc/rc.rs @@ -12,13 +12,8 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), crate::MaxRecursionReached> { - size_hint::try_recursion_guard(depth, ::try_size_hint) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() } } @@ -35,8 +30,8 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } @@ -46,7 +41,7 @@ impl<'a> Arbitrary<'a> for Rc { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - <&str as Arbitrary>::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + <&str as Arbitrary>::size_hint(context) } } diff --git a/src/foreign/alloc/string.rs b/src/foreign/alloc/string.rs index a579784..f226fcc 100644 --- a/src/foreign/alloc/string.rs +++ b/src/foreign/alloc/string.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::string::String, }; @@ -13,7 +13,7 @@ impl<'a> Arbitrary<'a> for String { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - <&str as Arbitrary>::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + <&str as Arbitrary>::size_hint(context) } } diff --git a/src/foreign/alloc/sync.rs b/src/foreign/alloc/sync.rs index c8ca1db..57de800 100644 --- a/src/foreign/alloc/sync.rs +++ b/src/foreign/alloc/sync.rs @@ -12,13 +12,8 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), crate::MaxRecursionReached> { - size_hint::try_recursion_guard(depth, ::try_size_hint) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() } } @@ -35,8 +30,8 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } @@ -46,7 +41,8 @@ impl<'a> Arbitrary<'a> for Arc { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - <&str as Arbitrary>::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + // known non-recursive + <&str as Arbitrary>::size_hint(context) } } diff --git a/src/foreign/alloc/vec.rs b/src/foreign/alloc/vec.rs index 63313ba..18794a7 100644 --- a/src/foreign/alloc/vec.rs +++ b/src/foreign/alloc/vec.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::vec::Vec, }; @@ -16,7 +16,7 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } diff --git a/src/foreign/core/array.rs b/src/foreign/core/array.rs index 39075be..4cafc9b 100644 --- a/src/foreign/core/array.rs +++ b/src/foreign/core/array.rs @@ -1,7 +1,6 @@ use { crate::{size_hint, Arbitrary, Result, Unstructured}, core::{ - array, mem::{self, MaybeUninit}, ptr, }, @@ -64,13 +63,7 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), crate::MaxRecursionReached> { - let hint = ::try_size_hint(depth)?; - Ok(size_hint::and_all(&array::from_fn::<_, N, _>(|_| hint))) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::().repeat(N) } } diff --git a/src/foreign/core/bool.rs b/src/foreign/core/bool.rs index 42e82fb..160d473 100644 --- a/src/foreign/core/bool.rs +++ b/src/foreign/core/bool.rs @@ -1,4 +1,4 @@ -use crate::{Arbitrary, Result, Unstructured}; +use crate::{size_hint, Arbitrary, Result, Unstructured}; impl<'a> Arbitrary<'a> for bool { fn arbitrary(u: &mut Unstructured<'a>) -> Result { @@ -6,7 +6,7 @@ impl<'a> Arbitrary<'a> for bool { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - >::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + >::size_hint(context) } } diff --git a/src/foreign/core/cell.rs b/src/foreign/core/cell.rs index ad0db38..414abd8 100644 --- a/src/foreign/core/cell.rs +++ b/src/foreign/core/cell.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, MaxRecursionReached, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, core::cell::{Cell, RefCell, UnsafeCell}, }; @@ -12,13 +12,8 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - >::try_size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + >::size_hint(context) } } @@ -31,13 +26,8 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - >::try_size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + >::size_hint(context) } } @@ -50,12 +40,7 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - >::try_size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + >::size_hint(context) } } diff --git a/src/foreign/core/char.rs b/src/foreign/core/char.rs index 1adc62d..6c98613 100644 --- a/src/foreign/core/char.rs +++ b/src/foreign/core/char.rs @@ -1,4 +1,4 @@ -use crate::{Arbitrary, Result, Unstructured}; +use crate::{size_hint, Arbitrary, Result, Unstructured}; impl<'a> Arbitrary<'a> for char { fn arbitrary(u: &mut Unstructured<'a>) -> Result { @@ -18,7 +18,7 @@ impl<'a> Arbitrary<'a> for char { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - >::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + >::size_hint(context) } } diff --git a/src/foreign/core/cmp.rs b/src/foreign/core/cmp.rs index 17bb029..2e0eb87 100644 --- a/src/foreign/core/cmp.rs +++ b/src/foreign/core/cmp.rs @@ -12,12 +12,7 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), crate::MaxRecursionReached> { - size_hint::try_recursion_guard(depth, ::try_size_hint) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() } } diff --git a/src/foreign/core/iter.rs b/src/foreign/core/iter.rs index 8a56f2a..9413117 100644 --- a/src/foreign/core/iter.rs +++ b/src/foreign/core/iter.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, SizeHint, Unstructured}, core::iter::{empty, Empty}, }; @@ -12,7 +12,7 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, Some(0)) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + SizeHint::exactly(0) } } diff --git a/src/foreign/core/marker.rs b/src/foreign/core/marker.rs index aa0afbe..86bc294 100644 --- a/src/foreign/core/marker.rs +++ b/src/foreign/core/marker.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, SizeHint, Unstructured}, core::marker::{PhantomData, PhantomPinned}, }; @@ -12,8 +12,8 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, Some(0)) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + SizeHint::exactly(0) } } @@ -23,7 +23,7 @@ impl<'a> Arbitrary<'a> for PhantomPinned { } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, Some(0)) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + SizeHint::exactly(0) } } diff --git a/src/foreign/core/num.rs b/src/foreign/core/num.rs index f0d5b33..6599503 100644 --- a/src/foreign/core/num.rs +++ b/src/foreign/core/num.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Error, MaxRecursionReached, Result, Unstructured}, + crate::{size_hint, Arbitrary, Error, Result, SizeHint, Unstructured}, core::{ mem, num::{ @@ -20,9 +20,8 @@ macro_rules! impl_arbitrary_for_integers { } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - let n = mem::size_of::<$ty>(); - (n, Some(n)) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + SizeHint::exactly(mem::size_of::<$ty>()) } } @@ -52,8 +51,8 @@ impl<'a> Arbitrary<'a> for usize { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - ::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + ::size_hint(context) } } @@ -63,8 +62,8 @@ impl<'a> Arbitrary<'a> for isize { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - ::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + ::size_hint(context) } } @@ -77,8 +76,8 @@ macro_rules! impl_arbitrary_for_floats { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - <$unsigned as Arbitrary<'a>>::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + <$unsigned as Arbitrary<'a>>::size_hint(context) } } )* @@ -101,8 +100,8 @@ macro_rules! implement_nonzero_int { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - <$int as Arbitrary<'a>>::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + <$int as Arbitrary<'a>>::size_hint(context) } } }; @@ -130,12 +129,7 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - >::try_size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + >::size_hint(context) } } diff --git a/src/foreign/core/ops.rs b/src/foreign/core/ops.rs index 8c24735..b6a7a2a 100644 --- a/src/foreign/core/ops.rs +++ b/src/foreign/core/ops.rs @@ -1,5 +1,5 @@ use { - crate::{size_hint, Arbitrary, MaxRecursionReached, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, SizeHint, Unstructured}, core::{ mem, ops::{Bound, Range, RangeBounds, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}, @@ -24,14 +24,9 @@ macro_rules! impl_range { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { #[allow(clippy::redundant_closure_call)] - $size_hint_closure(depth) + $size_hint_closure(context) } } }; @@ -41,41 +36,35 @@ impl_range!( |r: &Range| (r.start.clone(), r.end.clone()), (A, A), bounded_range(|(a, b)| a..b), - |depth| Ok(crate::size_hint::and( - ::try_size_hint(depth)?, - ::try_size_hint(depth)?, - )) + |context: &size_hint::Context| context.get::() + context.get::() ); impl_range!( RangeFrom, |r: &RangeFrom| r.start.clone(), A, unbounded_range(|a| a..), - |depth| ::try_size_hint(depth) + |context: &size_hint::Context| context.get::() ); impl_range!( RangeInclusive, |r: &RangeInclusive| (r.start().clone(), r.end().clone()), (A, A), bounded_range(|(a, b)| a..=b), - |depth| Ok(crate::size_hint::and( - ::try_size_hint(depth)?, - ::try_size_hint(depth)?, - )) + |context: &size_hint::Context| context.get::() + context.get::() ); impl_range!( RangeTo, |r: &RangeTo| r.end.clone(), A, unbounded_range(|b| ..b), - |depth| ::try_size_hint(depth) + |context: &size_hint::Context| context.get::() ); impl_range!( RangeToInclusive, |r: &RangeToInclusive| r.end.clone(), A, unbounded_range(|b| ..=b), - |depth| ::try_size_hint(depth) + |context: &size_hint::Context| context.get::() ); pub(crate) fn bounded_range(bounds: (I, I), cb: CB) -> R @@ -113,15 +102,7 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - Ok(size_hint::or( - size_hint::and((1, Some(1)), A::try_size_hint(depth)?), - (1, Some(1)), - )) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + (SizeHint::exactly(1) + context.get::()) | SizeHint::exactly(1) } } diff --git a/src/foreign/core/option.rs b/src/foreign/core/option.rs index 76ab978..039fd23 100644 --- a/src/foreign/core/option.rs +++ b/src/foreign/core/option.rs @@ -1,4 +1,4 @@ -use crate::{size_hint, Arbitrary, MaxRecursionReached, Result, Unstructured}; +use crate::{size_hint, Arbitrary, Result, SizeHint, Unstructured}; impl<'a, A> Arbitrary<'a> for Option where @@ -13,15 +13,7 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - Ok(size_hint::and( - ::try_size_hint(depth)?, - size_hint::or((0, Some(0)), ::try_size_hint(depth)?), - )) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() + (SizeHint::exactly(0) | context.get::()) } } diff --git a/src/foreign/core/result.rs b/src/foreign/core/result.rs index 65ad50a..61993b2 100644 --- a/src/foreign/core/result.rs +++ b/src/foreign/core/result.rs @@ -1,4 +1,4 @@ -use crate::{size_hint, Arbitrary, Error, MaxRecursionReached, Unstructured}; +use crate::{size_hint, Arbitrary, Error, Unstructured}; impl<'a, T, E> Arbitrary<'a> for Result where @@ -14,18 +14,7 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - Ok(size_hint::and( - ::size_hint(depth), - size_hint::or( - ::try_size_hint(depth)?, - ::try_size_hint(depth)?, - ), - )) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() + (context.get::() | context.get::()) } } diff --git a/src/foreign/core/slice.rs b/src/foreign/core/slice.rs index 296d474..4d202ba 100644 --- a/src/foreign/core/slice.rs +++ b/src/foreign/core/slice.rs @@ -1,4 +1,4 @@ -use crate::{Arbitrary, Result, Unstructured}; +use crate::{size_hint, Arbitrary, Result, SizeHint, Unstructured}; impl<'a> Arbitrary<'a> for &'a [u8] { fn arbitrary(u: &mut Unstructured<'a>) -> Result { @@ -11,7 +11,7 @@ impl<'a> Arbitrary<'a> for &'a [u8] { } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + SizeHint::at_least(0) } } diff --git a/src/foreign/core/str.rs b/src/foreign/core/str.rs index cfca8ad..9f8c24b 100644 --- a/src/foreign/core/str.rs +++ b/src/foreign/core/str.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, SizeHint, Unstructured}, core::str, }; @@ -33,7 +33,7 @@ impl<'a> Arbitrary<'a> for &'a str { } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + SizeHint::at_least(0) } } diff --git a/src/foreign/core/sync/atomic.rs b/src/foreign/core/sync/atomic.rs index 004c396..e6b3c69 100644 --- a/src/foreign/core/sync/atomic.rs +++ b/src/foreign/core/sync/atomic.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, core::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize}, }; @@ -9,8 +9,8 @@ impl<'a> Arbitrary<'a> for AtomicBool { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - >::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + >::size_hint(context) } } @@ -20,8 +20,8 @@ impl<'a> Arbitrary<'a> for AtomicIsize { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - >::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + >::size_hint(context) } } @@ -31,7 +31,7 @@ impl<'a> Arbitrary<'a> for AtomicUsize { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - >::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + >::size_hint(context) } } diff --git a/src/foreign/core/time.rs b/src/foreign/core/time.rs index 9ab3434..e9e916b 100644 --- a/src/foreign/core/time.rs +++ b/src/foreign/core/time.rs @@ -12,10 +12,7 @@ impl<'a> Arbitrary<'a> for Duration { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - size_hint::and( - ::size_hint(depth), - ::size_hint(depth), - ) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() + context.get::() } } diff --git a/src/foreign/core/tuple.rs b/src/foreign/core/tuple.rs index dfdd469..9534b49 100644 --- a/src/foreign/core/tuple.rs +++ b/src/foreign/core/tuple.rs @@ -1,4 +1,4 @@ -use crate::{size_hint, Arbitrary, MaxRecursionReached, Result, Unstructured}; +use crate::{size_hint, Arbitrary, Result, Unstructured}; macro_rules! arbitrary_tuple { () => {}; @@ -22,15 +22,9 @@ macro_rules! arbitrary_tuple { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - Ok(size_hint::and_all(&[ - <$last as Arbitrary>::try_size_hint(depth)?, - $( <$xs as Arbitrary>::try_size_hint(depth)?),* - ])) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + <$last as Arbitrary>::size_hint(context) + $( + <$xs as Arbitrary>::size_hint(context))* } } }; diff --git a/src/foreign/core/unit.rs b/src/foreign/core/unit.rs index 20523a6..dee3fae 100644 --- a/src/foreign/core/unit.rs +++ b/src/foreign/core/unit.rs @@ -1,4 +1,4 @@ -use crate::{Arbitrary, Result, Unstructured}; +use crate::{size_hint, Arbitrary, Result, SizeHint, Unstructured}; impl<'a> Arbitrary<'a> for () { fn arbitrary(_: &mut Unstructured<'a>) -> Result { @@ -6,7 +6,7 @@ impl<'a> Arbitrary<'a> for () { } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, Some(0)) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + SizeHint::exactly(0) } } diff --git a/src/foreign/std/collections/hash_map.rs b/src/foreign/std/collections/hash_map.rs index d2e77af..3806ace 100644 --- a/src/foreign/std/collections/hash_map.rs +++ b/src/foreign/std/collections/hash_map.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::{ collections::hash_map::HashMap, hash::{BuildHasher, Hash}, @@ -21,7 +21,7 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } diff --git a/src/foreign/std/collections/hash_set.rs b/src/foreign/std/collections/hash_set.rs index 5cb63d2..7dc0023 100644 --- a/src/foreign/std/collections/hash_set.rs +++ b/src/foreign/std/collections/hash_set.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::{ collections::hash_set::HashSet, hash::{BuildHasher, Hash}, @@ -20,7 +20,7 @@ where } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (0, None) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + size_hint::SizeHint::at_least(0) } } diff --git a/src/foreign/std/ffi/os_str.rs b/src/foreign/std/ffi/os_str.rs index 9fa0cb3..a057739 100644 --- a/src/foreign/std/ffi/os_str.rs +++ b/src/foreign/std/ffi/os_str.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::ffi::OsString, }; @@ -9,8 +9,8 @@ impl<'a> Arbitrary<'a> for OsString { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - ::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + ::size_hint(context) } } diff --git a/src/foreign/std/net.rs b/src/foreign/std/net.rs index 41e1f8a..a7b97c3 100644 --- a/src/foreign/std/net.rs +++ b/src/foreign/std/net.rs @@ -1,5 +1,5 @@ use { - crate::{size_hint, Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, SizeHint, Unstructured}, std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, }; @@ -9,8 +9,8 @@ impl<'a> Arbitrary<'a> for Ipv4Addr { } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (4, Some(4)) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + SizeHint::exactly(4) } } @@ -20,8 +20,8 @@ impl<'a> Arbitrary<'a> for Ipv6Addr { } #[inline] - fn size_hint(_depth: usize) -> (usize, Option) { - (16, Some(16)) + fn size_hint(_context: &size_hint::Context) -> size_hint::SizeHint { + SizeHint::exactly(16) } } @@ -34,11 +34,8 @@ impl<'a> Arbitrary<'a> for IpAddr { } } - fn size_hint(depth: usize) -> (usize, Option) { - size_hint::and( - bool::size_hint(depth), - size_hint::or(Ipv4Addr::size_hint(depth), Ipv6Addr::size_hint(depth)), - ) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() + (context.get::() | context.get::()) } } @@ -48,8 +45,8 @@ impl<'a> Arbitrary<'a> for SocketAddrV4 { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - size_hint::and(Ipv4Addr::size_hint(depth), u16::size_hint(depth)) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() + context.get::() } } @@ -64,14 +61,11 @@ impl<'a> Arbitrary<'a> for SocketAddrV6 { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - size_hint::and( - Ipv6Addr::size_hint(depth), - size_hint::and( - u16::size_hint(depth), - size_hint::and(u32::size_hint(depth), u32::size_hint(depth)), - ), - ) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() + + context.get::() + + context.get::() + + context.get::() } } @@ -84,13 +78,7 @@ impl<'a> Arbitrary<'a> for SocketAddr { } } - fn size_hint(depth: usize) -> (usize, Option) { - size_hint::and( - bool::size_hint(depth), - size_hint::or( - SocketAddrV4::size_hint(depth), - SocketAddrV6::size_hint(depth), - ), - ) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() + (context.get::() | context.get::()) } } diff --git a/src/foreign/std/path.rs b/src/foreign/std/path.rs index c94797b..a51dfb7 100644 --- a/src/foreign/std/path.rs +++ b/src/foreign/std/path.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::{ffi::OsString, path::PathBuf}, }; @@ -9,7 +9,7 @@ impl<'a> Arbitrary<'a> for PathBuf { } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - ::size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + ::size_hint(context) } } diff --git a/src/foreign/std/sync.rs b/src/foreign/std/sync.rs index 312b37e..c8eb73a 100644 --- a/src/foreign/std/sync.rs +++ b/src/foreign/std/sync.rs @@ -1,5 +1,5 @@ use { - crate::{Arbitrary, MaxRecursionReached, Result, Unstructured}, + crate::{size_hint, Arbitrary, Result, Unstructured}, std::sync::Mutex, }; @@ -12,12 +12,7 @@ where } #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - A::try_size_hint(depth) + fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + context.get::() } } diff --git a/src/lib.rs b/src/lib.rs index 2535d3a..fd7b192 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ pub mod unstructured; mod tests; pub use error::*; +pub use size_hint::SizeHint; #[cfg(feature = "derive_arbitrary")] pub use derive_arbitrary::*; @@ -38,19 +39,6 @@ pub use derive_arbitrary::*; #[doc(inline)] pub use unstructured::Unstructured; -/// Error indicating that the maximum recursion depth has been reached while calculating [`Arbitrary::size_hint`]() -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct MaxRecursionReached {} - -impl core::fmt::Display for MaxRecursionReached { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("Maximum recursion depth has been reached") - } -} - -impl std::error::Error for MaxRecursionReached {} - /// Generate arbitrary structured values from raw, unstructured data. /// /// The `Arbitrary` trait allows you to generate valid structured values, like @@ -217,31 +205,29 @@ pub trait Arbitrary<'a>: Sized { Self::arbitrary(&mut u) } - /// Get a size hint for how many bytes out of an `Unstructured` this type + /// Get a [size hint][SizeHint] for how many bytes out of an [`Unstructured`] this type /// needs to construct itself. /// /// This is useful for determining how many elements we should insert when /// creating an arbitrary collection. /// - /// The return value is similar to [`Iterator::size_hint`]: it returns a - /// tuple where the first element is a lower bound on the number of bytes - /// required, and the second element is an optional upper bound. - /// - /// The default implementation return `(0, None)` which is correct for any + /// The default implementation returns [`SizeHint::UNKNOWN`] which is correct for any /// type, but not ultimately that useful. Using `#[derive(Arbitrary)]` will /// create a better implementation. If you are writing an `Arbitrary` /// implementation by hand, and your type can be part of a dynamically sized /// collection (such as `Vec`), you are strongly encouraged to override this - /// default with a better implementation, and also override - /// [`try_size_hint`]. + /// default with a better implementation. + /// + /// The [`Context`](size_hint::Context) parameter controls the maximum number of + /// computation steps in order to terminate unbounded recursions in a reasonable time. /// /// ## How to implement this /// /// If the size hint calculation is a trivial constant and does not recurse - /// into any other `size_hint` call, you should implement it in `size_hint`: + /// into any other `size_hint` call, you can ignore the `context` value:` /// /// ``` - /// use arbitrary::{size_hint, Arbitrary, Result, Unstructured}; + /// use arbitrary::{Arbitrary, Result, SizeHint, size_hint, Unstructured}; /// /// struct SomeStruct(u8); /// @@ -253,18 +239,17 @@ pub trait Arbitrary<'a>: Sized { /// } /// /// #[inline] - /// fn size_hint(depth: usize) -> (usize, Option) { - /// let _ = depth; - /// (1, Some(1)) + /// fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + /// let _ = context; + /// SizeHint::exactly(1) /// } /// } /// ``` /// - /// Otherwise, it should instead be implemented in [`try_size_hint`], - /// and the `size_hint` implementation should forward to it: + /// Otherwise, you must use [`Context::get()`] whenever recursing: /// /// ``` - /// use arbitrary::{size_hint, Arbitrary, MaxRecursionReached, Result, Unstructured}; + /// use arbitrary::{Arbitrary, Result, SizeHint, size_hint, Unstructured}; /// /// struct SomeStruct { /// a: A, @@ -277,28 +262,10 @@ pub trait Arbitrary<'a>: Sized { /// # todo!() /// } /// - /// fn size_hint(depth: usize) -> (usize, Option) { - /// // Return the value of try_size_hint - /// // - /// // If the recursion fails, return the default, always valid `(0, None)` - /// Self::try_size_hint(depth).unwrap_or_default() - /// } - /// - /// fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - /// // Protect against potential infinite recursion with - /// // `try_recursion_guard`. - /// size_hint::try_recursion_guard(depth, |depth| { - /// // If we aren't too deep, then `recursion_guard` calls - /// // this closure, which implements the natural size hint. - /// // Don't forget to use the new `depth` in all nested - /// // `try_size_hint` calls! We recommend shadowing the - /// // parameter, like what is done here, so that you can't - /// // accidentally use the wrong depth. - /// Ok(size_hint::and( - /// ::try_size_hint(depth)?, - /// ::try_size_hint(depth)?, - /// )) - /// }) + /// fn size_hint(context: &size_hint::Context) -> size_hint::SizeHint { + /// // Protect against potential infinite recursion by calling only `context.get()`, + /// // and never `Type::size_hint()` directly. + /// context.get::() + context.get::() /// } /// } /// ``` @@ -309,119 +276,16 @@ pub trait Arbitrary<'a>: Sized { /// of lengths bounded by these parameters. This applies to both /// [`Arbitrary::arbitrary`] and [`Arbitrary::arbitrary_take_rest`]. /// - /// This is trivially true for `(0, None)`. To restrict this further, it + /// This is trivially true for [`SizeHint::UNKNOWN`]. To restrict this further, it /// must be proven that all inputs that are now excluded produced redundant /// outputs which are still possible to produce using the reduced input /// space. /// /// [iterator-size-hint]: https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.size_hint - /// [`try_size_hint`]: Arbitrary::try_size_hint - #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - let _ = depth; - (0, None) - } - - /// Get a size hint for how many bytes out of an `Unstructured` this type - /// needs to construct itself. - /// - /// Unlike [`size_hint`], this function keeps the information that the - /// recursion limit was reached. This is required to "short circuit" the - /// calculation and avoid exponential blowup with recursive structures. - /// - /// If you are implementing [`size_hint`] for a struct that could be - /// recursive, you should implement `try_size_hint` and call the - /// `try_size_hint` when recursing - /// - /// - /// The return value is similar to [`core::iter::Iterator::size_hint`]: it - /// returns a tuple where the first element is a lower bound on the number - /// of bytes required, and the second element is an optional upper bound. - /// - /// The default implementation returns the value of [`size_hint`] which is - /// correct for any type, but might lead to exponential blowup when dealing - /// with recursive types. - /// - /// ## Invariant - /// - /// It must be possible to construct every possible output using only inputs - /// of lengths bounded by these parameters. This applies to both - /// [`Arbitrary::arbitrary`] and [`Arbitrary::arbitrary_take_rest`]. - /// - /// This is trivially true for `(0, None)`. To restrict this further, it - /// must be proven that all inputs that are now excluded produced redundant - /// outputs which are still possible to produce using the reduced input - /// space. - /// - /// ## When to implement `try_size_hint` - /// - /// If you 100% know that the type you are implementing `Arbitrary` for is - /// not a recursive type, or your implementation is not transitively calling - /// any other `size_hint` methods, you may implement [`size_hint`], and the - /// default `try_size_hint` implementation will use it. - /// - /// Note that if you are implementing `Arbitrary` for a generic type, you - /// cannot guarantee the lack of type recursion! - /// - /// Otherwise, when there is possible type recursion, you should implement - /// `try_size_hint` instead. - /// - /// ## The `depth` parameter - /// - /// When implementing `try_size_hint`, you need to use - /// [`arbitrary::size_hint::try_recursion_guard(depth)`][crate::size_hint::try_recursion_guard] - /// to prevent potential infinite recursion when calculating size hints for - /// potentially recursive types: - /// - /// ``` - /// use arbitrary::{size_hint, Arbitrary, MaxRecursionReached, Unstructured}; - /// - /// // This can potentially be a recursive type if `L` or `R` contain - /// // something like `Box>>`! - /// enum MyEither { - /// Left(L), - /// Right(R), - /// } - /// - /// impl<'a, L, R> Arbitrary<'a> for MyEither - /// where - /// L: Arbitrary<'a>, - /// R: Arbitrary<'a>, - /// { - /// fn arbitrary(u: &mut Unstructured) -> arbitrary::Result { - /// // ... - /// # unimplemented!() - /// } - /// - /// fn size_hint(depth: usize) -> (usize, Option) { - /// // Return the value of `try_size_hint` - /// // - /// // If the recursion fails, return the default `(0, None)` range, - /// // which is always valid. - /// Self::try_size_hint(depth).unwrap_or_default() - /// } - /// - /// fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - /// // Protect against potential infinite recursion with - /// // `try_recursion_guard`. - /// size_hint::try_recursion_guard(depth, |depth| { - /// // If we aren't too deep, then `recursion_guard` calls - /// // this closure, which implements the natural size hint. - /// // Don't forget to use the new `depth` in all nested - /// // `try_size_hint` calls! We recommend shadowing the - /// // parameter, like what is done here, so that you can't - /// // accidentally use the wrong depth. - /// Ok(size_hint::or( - /// ::try_size_hint(depth)?, - /// ::try_size_hint(depth)?, - /// )) - /// }) - /// } - /// } - /// ``` #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - Ok(Self::size_hint(depth)) + fn size_hint(context: &size_hint::Context) -> SizeHint { + let _ = context; + SizeHint::UNKNOWN } } diff --git a/src/size_hint.rs b/src/size_hint.rs index 95707ee..5d37172 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -1,149 +1,493 @@ -//! Utilities for working with and combining the results of -//! [`Arbitrary::size_hint`][crate::Arbitrary::size_hint]. +//! The [`SizeHint`] type, for use with +//! [`Arbitrary::size_hint()`]. -pub(crate) const MAX_DEPTH: usize = 20; +use core::cell::Cell; +use core::{fmt, ops}; -/// Protects against potential infinite recursion when calculating size hints -/// due to indirect type recursion. -/// -/// When the depth is not too deep, calls `f` with `depth + 1` to calculate the -/// size hint. -/// -/// Otherwise, returns the default size hint: `(0, None)`. -/// -/// -#[inline] -#[deprecated(note = "use `try_recursion_guard` instead")] -pub fn recursion_guard( - depth: usize, - f: impl FnOnce(usize) -> (usize, Option), -) -> (usize, Option) { - if depth > MAX_DEPTH { - (0, None) - } else { - f(depth + 1) - } -} - -/// Protects against potential infinite recursion when calculating size hints -/// due to indirect type recursion. -/// -/// When the depth is not too deep, calls `f` with `depth + 1` to calculate the -/// size hint. -/// -/// Otherwise, returns an error. +use crate::Arbitrary; + +/// Bounds on the number of bytes an [`Arbitrary::arbitrary()`] implementation might consume. /// -/// This should be used when implementing [`try_size_hint`](crate::Arbitrary::try_size_hint) -#[inline] -pub fn try_recursion_guard( - depth: usize, - f: impl FnOnce(usize) -> Result<(usize, Option), crate::MaxRecursionReached>, -) -> Result<(usize, Option), crate::MaxRecursionReached> { - if depth > MAX_DEPTH { - Err(crate::MaxRecursionReached {}) - } else { - f(depth + 1) +/// A size hint consists of a finite lower bound and a possibly infinite upper bound. +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +pub struct SizeHint { + /// Inclusive lower bound. + lower: usize, + + /// Exclusive upper bound. [`None`] means unbounded. + /// + /// Also contains some meta-information about the size hint as a whole. + upper: Upper, + + out_of_fuel: bool, +} + +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +enum Upper { + Bounded(usize), + /// The size is known to be unbounded (or overflow [`usize`]). + Unbounded, + /// The size is unknown either because a more specific size hint was not given, + /// or because computation of the size hint was cancelled before it completed. + /// This is functionally identical to `Unbounded` but prints differently. + /// + /// In these cases, the lower bound may also be lower than it should be. + Unknown, +} + +impl SizeHint { + /// Equal to [`SizeHint::exactly(0)`][Self::exactly]. + pub const ZERO: Self = Self { + lower: 0, + upper: Upper::Bounded(0), + out_of_fuel: false, + }; + + /// Indicates that a size hint is not available. + /// Returned by the default implementation of [`Arbitrary::size_hint()`]. + /// + /// This is the most uninformative bound: zero to infinity bytes. + /// In addition, it prints differently from [`SizeHint::at_least(0)`][SizeHint::at_least], + /// to indicate that it is unknown rather than an unbounded collection. + pub const UNKNOWN: Self = Self { + lower: 0, + upper: Upper::Unknown, + out_of_fuel: false, + }; + + /// Indicates that the [`Context`] exhausted its `fuel`. + pub const OUT_OF_FUEL: Self = Self { + lower: 0, + upper: Upper::Unbounded, + out_of_fuel: true, + }; + + /// Creates a [`SizeHint`] for exactly `size` bytes. + #[must_use] + pub const fn exactly(size: usize) -> Self { + Self { + lower: size, + upper: Upper::Bounded(size), + out_of_fuel: false, + } + } + + /// Creates a [`SizeHint`] for `size` bytes or more. + #[must_use] + pub const fn at_least(size: usize) -> Self { + Self { + lower: size, + upper: Upper::Unbounded, + out_of_fuel: false, + } + } + + /// Returns the lower bound on bytes that will be consumed. + pub const fn lower_bound(self) -> usize { + self.lower + } + + /// Returns the upper bound on bytes that will be consumed. + /// + /// Returns [`None`] if unlimited bytes could be consumed, + /// the number of bytes exceeds [`usize::MAX`], + /// or the bound is unknown. + pub const fn upper_bound(self) -> Option { + match self.upper { + Upper::Bounded(size) => Some(size), + Upper::Unbounded => None, + Upper::Unknown => None, + } + } + + /// Take the sum of the `self` and `rhs` size hints. + /// + /// Use this when an [`Arbitrary::arbitrary()`] implementation is going to consume both + /// `self` bytes and `rhs` bytes, such as for the fields of a `struct`. + /// + /// This operation can also be written as the `+` operator. + #[inline] + pub const fn and(self, rhs: Self) -> Self { + Self { + lower: self.lower.saturating_add(rhs.lower), + upper: match (self.upper, rhs.upper) { + // checked_add causes overflow to become infinite + (Upper::Bounded(lhs), Upper::Bounded(rhs)) => match lhs.checked_add(rhs) { + Some(upper) => Upper::Bounded(upper), + None => Upper::Unbounded, + }, + // If either is explicitly unbounded, then we can accurately say the result is. + (Upper::Unbounded, _) | (_, Upper::Unbounded) => Upper::Unbounded, + // If one is unknown and neither is unbounded, result is unknown. + (Upper::Unknown, _) | (_, Upper::Unknown) => Upper::Unknown, + }, + out_of_fuel: self.out_of_fuel | rhs.out_of_fuel, + } + } + + /// Take the sum of all of the given size hints. + /// + /// If `hints` is empty, returns `SizeHint::exactly(0)`, aka the size of consuming + /// nothing. + /// + /// Use this when an [`Arbitrary::arbitrary()`] implementation is going to consume as many + /// bytes as all of the hints together, such as for the fields of a `struct`. + #[inline] + pub fn and_all(hints: &[Self]) -> Self { + hints.iter().copied().fold(Self::ZERO, Self::and) + } + + /// Take the minimum of the lower bounds and maximum of the upper bounds in the + /// `self` and `rhs` size hints. + /// + /// Use this when an [`Arbitrary::arbitrary()`] implementation is going to choose one + /// alternative and consume that many bytes, such as for the variants of an `enum`. + /// + /// This operation can also be written as the `|` operator. + #[inline] + pub const fn or(self, rhs: Self) -> Self { + Self { + lower: if self.lower < rhs.lower { + self.lower + } else { + rhs.lower + }, + upper: match (self.upper, rhs.upper) { + (Upper::Bounded(lhs), Upper::Bounded(rhs)) => { + Upper::Bounded(if lhs > rhs { lhs } else { rhs }) + } + // If either is explicitly unbounded, then we can accurately say the result is. + (Upper::Unbounded, _) | (_, Upper::Unbounded) => Upper::Unbounded, + // If one is unknown and neither is unbounded, result is unknown. + (Upper::Unknown, _) | (_, Upper::Unknown) => Upper::Unknown, + }, + out_of_fuel: self.out_of_fuel | rhs.out_of_fuel, + } + } + + /// Take the maximum of the `lhs` and `rhs` size hints. + /// + /// If `hints` is empty, returns [`SizeHint::ZERO`], aka the size of consuming + /// nothing. + /// + /// Use this when an [`Arbitrary::arbitrary()`] implementation is going to choose one + /// alternative and consume that many bytes, such as for the variants of an `enum`. + #[inline] + pub fn or_all(hints: &[Self]) -> Self { + if let Some(head) = hints.first().copied() { + hints[1..].iter().copied().fold(head, Self::or) + } else { + Self::ZERO + } + } + + /// Multiply the bounds by `count`, as if to produce the size hint for constructing an array. + pub fn repeat(self, count: usize) -> Self { + Self { + lower: self.lower.saturating_mul(count), + upper: match self.upper { + Upper::Bounded(upper) => upper + .checked_mul(count) + .map_or(Upper::Unbounded, Upper::Bounded), + not_bounded => not_bounded, + }, + out_of_fuel: false, + } } } -/// Take the sum of the `lhs` and `rhs` size hints. -#[inline] -pub fn and(lhs: (usize, Option), rhs: (usize, Option)) -> (usize, Option) { - let lower = lhs.0 + rhs.0; - let upper = lhs.1.and_then(|lhs| rhs.1.map(|rhs| lhs + rhs)); - (lower, upper) +impl fmt::Debug for SizeHint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + lower, + upper, + out_of_fuel, + } = *self; + match upper { + Upper::Bounded(upper) => write!(f, "{lower}..={upper}")?, + Upper::Unbounded => write!(f, "{lower}..")?, + Upper::Unknown if lower == 0 => write!(f, "(unknown)")?, + Upper::Unknown => write!(f, "{lower}.. + (unknown)")?, + } + if out_of_fuel { + write!(f, " + (out of fuel)")?; + } + Ok(()) + } } -/// Take the sum of all of the given size hints. -/// -/// If `hints` is empty, returns `(0, Some(0))`, aka the size of consuming -/// nothing. -#[inline] -pub fn and_all(hints: &[(usize, Option)]) -> (usize, Option) { - hints.iter().copied().fold((0, Some(0)), and) +impl Default for SizeHint { + /// Returns [`SizeHint::UNKNOWN`], the value for when no + fn default() -> Self { + Self::UNKNOWN + } } -/// Take the minimum of the lower bounds and maximum of the upper bounds in the -/// `lhs` and `rhs` size hints. -#[inline] -pub fn or(lhs: (usize, Option), rhs: (usize, Option)) -> (usize, Option) { - let lower = std::cmp::min(lhs.0, rhs.0); - let upper = lhs - .1 - .and_then(|lhs| rhs.1.map(|rhs| std::cmp::max(lhs, rhs))); - (lower, upper) +impl From> for SizeHint { + fn from(range: ops::RangeFrom) -> Self { + Self { + lower: range.start, + upper: Upper::Unbounded, + out_of_fuel: false, + } + } +} +impl From> for SizeHint { + fn from(range: ops::RangeInclusive) -> Self { + Self { + lower: *range.start(), + upper: Upper::Bounded(*range.end()), + out_of_fuel: false, + } + } +} + +impl ops::RangeBounds for SizeHint { + fn start_bound(&self) -> ops::Bound<&usize> { + ops::Bound::Included(&self.lower) + } + + fn end_bound(&self) -> ops::Bound<&usize> { + match &self.upper { + Upper::Bounded(upper) => ops::Bound::Included(upper), + Upper::Unbounded | Upper::Unknown => ops::Bound::Unbounded, + } + } +} + +impl ops::Add for SizeHint { + type Output = Self; + /// Identical to [`SizeHint::and()`]. + fn add(self, rhs: Self) -> Self::Output { + self.and(rhs) + } +} +impl ops::AddAssign for SizeHint { + /// Identical to [`SizeHint::and()`]. + fn add_assign(&mut self, rhs: Self) { + *self = self.and(rhs) + } +} +impl ops::BitOr for SizeHint { + type Output = Self; + /// Identical to [`SizeHint::or()`]. + fn bitor(self, rhs: Self) -> Self::Output { + self.or(rhs) + } } -/// Take the maximum of the `lhs` and `rhs` size hints. +/// Context passed recursively through [`Arbitrary::size_hint()`] implementations. /// -/// If `hints` is empty, returns `(0, Some(0))`, aka the size of consuming -/// nothing. -#[inline] -pub fn or_all(hints: &[(usize, Option)]) -> (usize, Option) { - if let Some(head) = hints.first().copied() { - hints[1..].iter().copied().fold(head, or) - } else { - (0, Some(0)) +/// Used to ensure that `size_hint()` cannot execute unbounded recursion through generic types, +/// by terminating the recursion after a certain number of operations, measured by the “fuel” value +/// in this context. +/// +/// When you are implementing `size_hint()` for a generic type, use this context by calling +/// [`Context::get()`] instead of calling other `size_hint()` implementations directly, +/// and use `and()`, `or()`, `and_all()`, and `or_all()` to compose size hints. +pub struct Context { + fuel: Cell, +} + +impl Default for Context { + fn default() -> Self { + Self::new(100) } } +impl Context { + /// Creates a context which allows no more than `fuel` recursive calls. + pub fn new(fuel: u32) -> Self { + Context { + fuel: Cell::new(fuel), + } + } + + /// Returns [`T::size_hint()`][Arbitrary::size_hint], and consumes 1 unit of fuel too. + #[inline] + pub fn get<'a, T: Arbitrary<'a>>(&self) -> SizeHint { + if self.consume_fuel() { + T::size_hint(self) + } else { + SizeHint::OUT_OF_FUEL + } + } + + /// Consumes 1 unit of fuel if available, and returns whether that fuel was available. + #[inline(never)] + fn consume_fuel(&self) -> bool { + match self.fuel.get().checked_sub(1) { + Some(new_fuel) => { + self.fuel.set(new_fuel); + true + } + None => false, + } + } +} + +/// Creates a new [`Context`] and uses it to ask `T` for its size hint. +/// +/// Do not use this function inside of [`size_hint()`] implementations. +pub fn get<'a, T: Arbitrary<'a>>() -> SizeHint { + T::size_hint(&Context::default()) +} + #[cfg(test)] mod tests { + use super::{Context, SizeHint}; + + #[test] + fn formatting() { + fn fmt(s: SizeHint) -> String { + format!("{s:?}") + } + assert_eq!(fmt(SizeHint::ZERO), "0..=0"); + assert_eq!(fmt(SizeHint::UNKNOWN), "(unknown)"); + assert_eq!(fmt(SizeHint::exactly(10)), "10..=10"); + assert_eq!(fmt(SizeHint::at_least(10)), "10.."); + assert_eq!( + fmt(SizeHint::exactly(10) + SizeHint::UNKNOWN), + "10.. + (unknown)" + ); + assert_eq!( + fmt(SizeHint::exactly(10) + SizeHint::OUT_OF_FUEL), + "10.. + (out of fuel)" + ); + } + #[test] fn and() { - assert_eq!((5, Some(5)), super::and((2, Some(2)), (3, Some(3)))); - assert_eq!((5, None), super::and((2, Some(2)), (3, None))); - assert_eq!((5, None), super::and((2, None), (3, Some(3)))); - assert_eq!((5, None), super::and((2, None), (3, None))); + assert_eq!( + SizeHint::exactly(5), + SizeHint::and(SizeHint::exactly(2), SizeHint::exactly(3)) + ); + assert_eq!( + SizeHint::at_least(5), + SizeHint::and(SizeHint::exactly(2), SizeHint::at_least(3)) + ); + assert_eq!( + SizeHint::at_least(5), + SizeHint::and(SizeHint::at_least(2), SizeHint::exactly(3)) + ); + assert_eq!( + SizeHint::at_least(5), + SizeHint::and(SizeHint::at_least(2), SizeHint::at_least(3)) + ); } #[test] fn or() { - assert_eq!((2, Some(3)), super::or((2, Some(2)), (3, Some(3)))); - assert_eq!((2, None), super::or((2, Some(2)), (3, None))); - assert_eq!((2, None), super::or((2, None), (3, Some(3)))); - assert_eq!((2, None), super::or((2, None), (3, None))); + assert_eq!( + SizeHint::from(2..=3), + SizeHint::or(SizeHint::exactly(2), SizeHint::exactly(3)) + ); + assert_eq!( + SizeHint::at_least(2), + SizeHint::or(SizeHint::exactly(2), SizeHint::at_least(3)) + ); + assert_eq!( + SizeHint::at_least(2), + SizeHint::or(SizeHint::at_least(2), SizeHint::exactly(3)) + ); + assert_eq!( + SizeHint::at_least(2), + SizeHint::or(SizeHint::at_least(2), SizeHint::at_least(3)) + ); } #[test] fn and_all() { - assert_eq!((0, Some(0)), super::and_all(&[])); + assert_eq!(SizeHint::exactly(0), SizeHint::and_all(&[])); assert_eq!( - (7, Some(7)), - super::and_all(&[(1, Some(1)), (2, Some(2)), (4, Some(4))]) + SizeHint::exactly(7), + SizeHint::and_all(&[ + SizeHint::exactly(1), + SizeHint::exactly(2), + SizeHint::exactly(4) + ]) ); assert_eq!( - (7, None), - super::and_all(&[(1, Some(1)), (2, Some(2)), (4, None)]) + SizeHint::at_least(7), + SizeHint::and_all(&[ + SizeHint::exactly(1), + SizeHint::exactly(2), + SizeHint::at_least(4) + ]) ); assert_eq!( - (7, None), - super::and_all(&[(1, Some(1)), (2, None), (4, Some(4))]) + SizeHint::at_least(7), + SizeHint::and_all(&[ + SizeHint::exactly(1), + SizeHint::at_least(2), + SizeHint::exactly(4) + ]) ); assert_eq!( - (7, None), - super::and_all(&[(1, None), (2, Some(2)), (4, Some(4))]) + SizeHint::at_least(7), + SizeHint::and_all(&[ + SizeHint::at_least(1), + SizeHint::exactly(2), + SizeHint::exactly(4) + ]) ); } #[test] fn or_all() { - assert_eq!((0, Some(0)), super::or_all(&[])); + assert_eq!(SizeHint::exactly(0), SizeHint::or_all(&[])); assert_eq!( - (1, Some(4)), - super::or_all(&[(1, Some(1)), (2, Some(2)), (4, Some(4))]) + SizeHint::from(1..=4), + SizeHint::or_all(&[ + SizeHint::exactly(1), + SizeHint::exactly(2), + SizeHint::exactly(4) + ]) ); assert_eq!( - (1, None), - super::or_all(&[(1, Some(1)), (2, Some(2)), (4, None)]) + SizeHint::at_least(1), + SizeHint::or_all(&[ + SizeHint::exactly(1), + SizeHint::exactly(2), + SizeHint::at_least(4) + ]) ); assert_eq!( - (1, None), - super::or_all(&[(1, Some(1)), (2, None), (4, Some(4))]) + SizeHint::at_least(1), + SizeHint::or_all(&[ + SizeHint::exactly(1), + SizeHint::at_least(2), + SizeHint::exactly(4) + ]) ); assert_eq!( - (1, None), - super::or_all(&[(1, None), (2, Some(2)), (4, Some(4))]) + SizeHint::at_least(1), + SizeHint::or_all(&[ + SizeHint::at_least(1), + SizeHint::exactly(2), + SizeHint::exactly(4) + ]) + ); + } + + #[test] + fn context_short_circuit() { + struct ShouldNotBeCalled; + impl<'a> crate::Arbitrary<'a> for ShouldNotBeCalled { + fn arbitrary(_: &mut crate::Unstructured<'a>) -> crate::Result { + unimplemented!() + } + fn size_hint(_: &super::Context) -> SizeHint { + unreachable!() + } + } + + // Create a context that allows exactly 2 recursion operations, then show that + // it does exactly those 2 and no more. + let context = Context::new(2); + assert_eq!( + SizeHint::exactly(3) + SizeHint::OUT_OF_FUEL, + context.get::() + context.get::() + context.get::(), ); } } diff --git a/src/tests.rs b/src/tests.rs index 7746715..faf4b07 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,6 @@ use { super::{Arbitrary, Result, Unstructured}, + crate::{size_hint, SizeHint}, std::{collections::HashSet, fmt::Debug, hash::Hash, rc::Rc, sync::Arc}, }; @@ -7,6 +8,7 @@ use { /// /// Exhaustively enumerates all buffers up to length 10 containing the /// following bytes: `0x00`, `0x01`, `0x61` (aka ASCII 'a'), and `0xff` +#[track_caller] fn assert_generates(expected_values: impl IntoIterator) where T: Clone + Debug + Hash + Eq + for<'a> Arbitrary<'a>, @@ -55,29 +57,27 @@ where /// Generates an arbitrary `T`, and checks that the result is consistent with the /// `size_hint()` reported by `T`. +#[track_caller] fn checked_arbitrary<'a, T: Arbitrary<'a>>(u: &mut Unstructured<'a>) -> Result { - let (min, max) = T::size_hint(0); + let size_hint = size_hint::get::(); + let min = size_hint.lower_bound(); let len_before = u.len(); let result = T::arbitrary(u); let consumed = len_before - u.len(); - if let Some(max) = max { + if let Some(max) = size_hint.upper_bound() { assert!( consumed <= max, - "incorrect maximum size: indicated {}, actually consumed {}", - max, - consumed + "incorrect maximum size: indicated {max}, actually consumed {consumed}", ); } if result.is_ok() { assert!( consumed >= min, - "incorrect minimum size: indicated {}, actually consumed {}", - min, - consumed + "incorrect minimum size: indicated {min}, actually consumed {consumed}", ); } @@ -85,8 +85,9 @@ fn checked_arbitrary<'a, T: Arbitrary<'a>>(u: &mut Unstructured<'a>) -> Result>(u: Unstructured<'a>) -> Result { - let (min, _) = T::size_hint(0); + let min = size_hint::get::().lower_bound(); let len_before = u.len(); let result = T::arbitrary_take_rest(u); @@ -94,9 +95,7 @@ fn checked_arbitrary_take_rest<'a, T: Arbitrary<'a>>(u: Unstructured<'a>) -> Res if result.is_ok() { assert!( len_before >= min, - "incorrect minimum size: indicated {}, worked with {}", - min, - len_before + "incorrect minimum size: indicated {min}, worked with {len_before}", ); } @@ -309,8 +308,23 @@ fn arbitrary_take_rest() { #[test] fn size_hint_for_tuples() { assert_eq!( - (7, Some(7)), - <(bool, u16, i32) as Arbitrary<'_>>::size_hint(0) + SizeHint::exactly(7), + <(bool, u16, i32) as Arbitrary<'_>>::size_hint(&Default::default()) ); - assert_eq!((1, None), <(u8, Vec) as Arbitrary>::size_hint(0)); + assert_eq!( + SizeHint::at_least(1), + <(u8, Vec) as Arbitrary>::size_hint(&Default::default()) + ); +} + +#[test] +fn size_hint_not_overridden() { + struct Example; + impl<'a> Arbitrary<'a> for Example { + fn arbitrary(_: &mut Unstructured<'a>) -> Result { + unimplemented!() + } + } + + assert_eq!(SizeHint::UNKNOWN, size_hint::get::()); } diff --git a/src/unstructured.rs b/src/unstructured.rs index b31f523..e904f28 100644 --- a/src/unstructured.rs +++ b/src/unstructured.rs @@ -8,7 +8,7 @@ //! Wrappers around raw, unstructured bytes. -use crate::{Arbitrary, Error, Result}; +use crate::{size_hint, Arbitrary, Error, Result}; use std::marker::PhantomData; use std::ops::ControlFlow; use std::{mem, ops}; @@ -216,8 +216,10 @@ impl<'a> Unstructured<'a> { ElementType: Arbitrary<'a>, { let byte_size = self.arbitrary_byte_size()?; - let (lower, upper) = ::size_hint(0); - let elem_size = upper.unwrap_or(lower * 2); + let elem_size_hint = size_hint::get::(); + let elem_size = elem_size_hint + .upper_bound() + .unwrap_or(elem_size_hint.lower_bound() * 2); let elem_size = std::cmp::max(1, elem_size); Ok(byte_size / elem_size) } diff --git a/tests/derive.rs b/tests/derive.rs index c45cc24..b16c488 100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -27,7 +27,7 @@ fn struct_with_named_fields() { assert_eq!(rgb.g, 5); assert_eq!(rgb.b, 6); - assert_eq!((3, Some(3)), ::size_hint(0)); + assert_eq!(SizeHint::exactly(3), size_hint::get::()); } #[derive(Copy, Clone, Debug, Arbitrary)] @@ -43,7 +43,7 @@ fn tuple_struct() { assert_eq!(s.0, 42); assert_eq!(s.1, true); - assert_eq!((2, Some(2)), ::size_hint(0)); + assert_eq!(SizeHint::exactly(2), size_hint::get::()); } #[derive(Clone, Debug, Arbitrary)] @@ -116,7 +116,7 @@ fn derive_enum() { assert!(saw_tuple); assert!(saw_struct); - assert_eq!((4, Some(17)), ::size_hint(0)); + assert_eq!(SizeHint::from(4..=17), size_hint::get::()); } // This should result in a compiler-error: @@ -213,28 +213,36 @@ struct WideRecursiveMixedStruct { #[test] fn recursive() { let raw = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; + // These should all succeed without recursing infinitely. let _rec: RecursiveTree = arbitrary_from(&raw); let _rec: WideRecursiveStruct = arbitrary_from(&raw); let _rec: WideRecursiveEnum = arbitrary_from(&raw); let _rec: WideRecursiveMixedStruct = arbitrary_from(&raw); let _rec: WideRecursiveMixedEnum = arbitrary_from(&raw); - assert_eq!((0, None), ::size_hint(0)); - assert_eq!((0, None), ::size_hint(0)); + // These should all report unbounded (meaning the size is effectively unbounded). assert_eq!( - (0, None), - ::size_hint(0) + size_hint::get::(), + // The actual minimum is 10, but in practice we run out of fuel handling the first branch + // so only determine that at least 1 byte is needed. + SizeHint::at_least(1) + SizeHint::OUT_OF_FUEL ); assert_eq!( - (0, None), - ::size_hint(0) + size_hint::get::(), + // Enums use 4 bytes because derive(Arbitrary) uses u32 to generate enum discriminants. + SizeHint::at_least(4) + SizeHint::OUT_OF_FUEL ); - - let (lower, upper) = ::size_hint(0); - assert_eq!(lower, 0, "Cannot compute size hint of recursive structure"); - assert!( - upper.is_none(), - "potentially infinitely recursive, so no upper bound" + assert_eq!( + size_hint::get::(), + SizeHint::at_least(1) + SizeHint::OUT_OF_FUEL + ); + assert_eq!( + size_hint::get::(), + SizeHint::at_least(4) + SizeHint::OUT_OF_FUEL + ); + assert_eq!( + size_hint::get::(), + SizeHint::at_least(4) + SizeHint::OUT_OF_FUEL ); } @@ -249,9 +257,9 @@ fn generics() { let gen: Generic = arbitrary_from(&raw); assert!(gen.inner); - let (lower, upper) = as Arbitrary>::size_hint(0); - assert_eq!(lower, 4); - assert_eq!(upper, Some(4)); + let size_hint = size_hint::get::>(); + assert_eq!(size_hint.lower_bound(), 4); + assert_eq!(size_hint.upper_bound(), Some(4)); } #[derive(Arbitrary, Debug)] @@ -266,9 +274,9 @@ fn one_lifetime() { let lifetime: OneLifetime = arbitrary_from(&raw); assert_eq!("abc", lifetime.alpha); - let (lower, upper) = ::size_hint(0); - assert_eq!(lower, 0); - assert_eq!(upper, None); + let size_hint = size_hint::get::(); + assert_eq!(size_hint.lower_bound(), 0); + assert_eq!(size_hint.upper_bound(), None); } #[derive(Arbitrary, Debug)] @@ -285,9 +293,9 @@ fn two_lifetimes() { assert_eq!("abc", lifetime.alpha); assert_eq!("def", lifetime.beta); - let (lower, upper) = ::size_hint(0); - assert_eq!(lower, 0); - assert_eq!(upper, None); + let size_hint = size_hint::get::(); + assert_eq!(size_hint.lower_bound(), 0); + assert_eq!(size_hint.upper_bound(), None); } #[test]