From 4d4fe77bf85e3eb49c9ab113836784b93f74d902 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Tue, 14 May 2024 07:06:35 +0100 Subject: [PATCH] wip --- Cargo.toml | 18 +++++ hugr/Cargo.toml | 2 +- hugr/src/hugr/ident.rs | 5 +- hugr/src/hugr/serialize/test.rs | 6 +- hugr/src/lib.rs | 3 +- hugr/src/ops/constant.rs | 10 +-- hugr/src/ops/module.rs | 26 ++----- hugr/src/proptest.rs | 70 ++++++++++++++--- hugr/src/types.rs | 132 +++++++++++++++++++------------- hugr/src/types/custom.rs | 25 +++--- hugr/src/types/poly_func.rs | 40 +++++----- hugr/src/types/signature.rs | 40 ++++------ hugr/src/types/type_param.rs | 16 ++-- hugr/src/types/type_row.rs | 4 +- justfile | 3 +- 15 files changed, 232 insertions(+), 168 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b353a61fa..1d86879ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,26 @@ insta = { version = "1.34.0" } [profile.dev.package] insta.opt-level = 3 + +# optimising these packages were found to contribute to property testing +# execution time. +# +# `flamegraph` below is https://github.com/flamegraph-rs/flamegraph +# $ perf record --call-graph=dwarf -F 99999 prop_roundtrip_ +# $ flamegraph --no-inline --perfdata perf.data rand_chacha.opt-level = 3 +rand_chacha.debug = 1 regex.opt-level = 3 +regex.debug = 1 regex-automata.opt-level = 3 +regex-automata.debug = 1 regex-syntax.opt-level = 3 +regex-syntax.debug = 1 proptest.opt-level = 3 +proptest.debug = 1 +serde.opt-level = 3 +serde.debug = 1 +serde_json.opt-level = 3 +serde_json.debug = 1 +jsonschema.opt-level = 3 +jsonschema.debug = 1 diff --git a/hugr/Cargo.toml b/hugr/Cargo.toml index 83c1207df..7bc37abd1 100644 --- a/hugr/Cargo.toml +++ b/hugr/Cargo.toml @@ -22,7 +22,7 @@ bench = false path = "src/lib.rs" [features] -default = [] +default = ["proptest"] extension_inference = [] proptest = ["dep:proptest","dep:proptest-derive","dep:regex-syntax"] diff --git a/hugr/src/hugr/ident.rs b/hugr/src/hugr/ident.rs index 6ab9bfaae..1d296a888 100644 --- a/hugr/src/hugr/ident.rs +++ b/hugr/src/hugr/ident.rs @@ -7,8 +7,6 @@ use smol_str::SmolStr; use thiserror::Error; pub static PATH_COMPONENT_REGEX_STR: &str = r"[\w--\d]\w*"; -#[cfg(all(test, feature = "proptest"))] -pub static PATH_COMPONENT_NICE_REGEX_STR: &str = r"[[:alpha:]][[[:alpha:]]0-9]*"; lazy_static! { pub static ref PATH_REGEX: Regex = Regex::new(&format!(r"^{0}(\.{0})*$", PATH_COMPONENT_REGEX_STR)).unwrap(); @@ -26,8 +24,8 @@ lazy_static! { serde::Serialize, serde::Deserialize, )] -/// A non-empty dot-separated list of valid identifiers +/// A non-empty dot-separated list of valid identifiers pub struct IdentList(SmolStr); impl IdentList { @@ -91,7 +89,6 @@ mod test { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { use crate::proptest::any_ident_string; use proptest::collection::vec; - // we shrink to more readable (i.e. :alpha:) names vec(any_ident_string(), 1..2) .prop_map(|vs| { IdentList::new( diff --git a/hugr/src/hugr/serialize/test.rs b/hugr/src/hugr/serialize/test.rs index 1ae401ce2..18437a264 100644 --- a/hugr/src/hugr/serialize/test.rs +++ b/hugr/src/hugr/serialize/test.rs @@ -177,7 +177,9 @@ pub fn check_hugr_roundtrip(hugr: &Hugr, check_schema: bool) -> Hugr { new_hugr } -#[allow(unused)] +// for now this is only used in property testing, so otherwise configured out to +// avoid unused warnings. +#[cfg(feature = "proptest")] fn check_testing_roundtrip(t: impl Into) { let before = Versioned::new(t.into()); let after_strict = ser_roundtrip_validate(&before, Some(&TESTING_SCHEMA_STRICT)); @@ -389,7 +391,7 @@ mod proptest { type Strategy = BoxedStrategy; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { ( - (0..(std::u32::MAX / 2) as usize).prop_map(|x| portgraph::NodeIndex::new(x).into()), + (0..i32::MAX as usize).prop_map(|x| portgraph::NodeIndex::new(x).into()), any::>(), any::(), ) diff --git a/hugr/src/lib.rs b/hugr/src/lib.rs index af11e8b19..d448af7f9 100644 --- a/hugr/src/lib.rs +++ b/hugr/src/lib.rs @@ -135,11 +135,10 @@ // Unstable check, may cause false positives. // https://github.com/rust-lang/rust-clippy/issues/5112 #![warn(clippy::debug_assert_with_mut_call)] - // proptest-derive generates many of these warnings. // https://github.com/rust-lang/rust/issues/120363 // https://github.com/proptest-rs/proptest/issues/447 -#![cfg_attr(all(test,feature = "proptest"), allow(non_local_definitions))] +#![cfg_attr(all(test, feature = "proptest"), allow(non_local_definitions))] pub mod algorithm; pub mod builder; diff --git a/hugr/src/ops/constant.rs b/hugr/src/ops/constant.rs index bf5bb4b75..86e05800c 100644 --- a/hugr/src/ops/constant.rs +++ b/hugr/src/ops/constant.rs @@ -641,7 +641,7 @@ mod test { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { use proptest::collection::vec; let signed_strat = (..=LOG_WIDTH_MAX).prop_flat_map(|log_width| { - use std::i64; + use i64; let max_val = (2u64.pow(log_width as u32) / 2) as i64; let min_val = -max_val - 1; (min_val..=max_val).prop_map(move |v| { @@ -684,11 +684,7 @@ mod test { use ::proptest::collection::vec; let leaf_strat = prop_oneof![ any::().prop_map(|e| Self::Extension { e }), - prop_oneof![ - // TODO we need an example of each legal root, in particular FuncDe{fn,cl} - Just(crate::builder::test::simple_dfg_hugr()), - ] - .prop_map(|x| Value::function(x).unwrap()) + crate::proptest::any_hugr().prop_map(|x| Value::function(x).unwrap()) ]; leaf_strat .prop_recursive( @@ -701,7 +697,7 @@ mod test { ( any::(), vec(element.clone(), 0..3), - any_with::(1.into()) + any_with::(1.into()) // for speed: don't generate large sum types for now ) .prop_map( |(tag, values, sum_type)| { diff --git a/hugr/src/ops/module.rs b/hugr/src/ops/module.rs index 2de1f60f3..891f737d7 100644 --- a/hugr/src/ops/module.rs +++ b/hugr/src/ops/module.rs @@ -133,8 +133,13 @@ impl OpTrait for AliasDefn { /// A type alias declaration. Resolved at link time. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[cfg_attr(all(feature = "proptest", test), derive(proptest_derive::Arbitrary))] pub struct AliasDecl { /// Alias name + #[cfg_attr( + all(feature = "proptest", test), + proptest(strategy = "crate::proptest::any_nonempty_smolstr()") + )] pub name: SmolStr, /// Flag to signify type is classical pub bound: TypeBound, @@ -168,24 +173,3 @@ impl OpTrait for AliasDecl { ::TAG } } - -#[cfg(test)] -mod test { - #[cfg(feature = "proptest")] - mod proptest { - use crate::types::TypeBound; - use proptest::prelude::*; - - impl Arbitrary for super::super::AliasDecl { - type Parameters = (); - type Strategy = BoxedStrategy; - fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { - use crate::proptest::any_ident_string; - let bound = any::(); - (any_ident_string(), bound) - .prop_map(|(name, bound)| Self::new(name, bound)) - .boxed() - } - } - } -} diff --git a/hugr/src/proptest.rs b/hugr/src/proptest.rs index 749d49842..4dae0dfc9 100644 --- a/hugr/src/proptest.rs +++ b/hugr/src/proptest.rs @@ -3,10 +3,34 @@ use ::proptest::prelude::*; use lazy_static::lazy_static; use smol_str::SmolStr; -#[derive(Clone, Copy, Debug)] -pub struct TypeDepth(usize); +use crate::Hugr; -impl TypeDepth { +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)] +/// The types [Type], [TypeEnum], [SumType], [FunctionType], [TypeArg], +/// [TypeParam], as well as several others, form a mutually recursive heirarchy. +/// +/// The proptest `Strategy::prop_recursive` is inadequate to generate values for +/// these types. Instead, ther Arbitrary instances take a `RecursionDepth` as +/// their (or part of their) [Arbitrary::Parameters]. We then use that parameter +/// to generate children of that value. Usually we forward it unchanged, but in +/// crucial locations we instead forward the `descend` of it. +/// +/// Consider the tree of values generated. Each node is labelled with a +/// [RecursionDepth]. +/// +/// Consider a path between two nodes of the same type(e.g. two [Type]s, or two +/// [FunctionType]s). The path must be decreasing in [RecursionDepth] because +/// each child's [RecursionDepth] is derived from it's parents. +/// +/// We must maintain the invariant that the [RecursionDepth] of the start of the +/// path is strictly greater than the [RecursionDepth] of the end of the path. +/// +/// With this invariant in place we are guaranteed to terminate in producing a +/// value, because there are only finitely many different types a node can take. +pub struct RecursionDepth(usize); + +impl RecursionDepth { + const DEFAULT_RECURSION_DEPTH: usize = 4; pub fn descend(&self) -> Self { if self.leaf() { *self @@ -18,17 +42,24 @@ impl TypeDepth { pub fn leaf(&self) -> bool { self.0 == 0 } + + pub fn new() -> Self { + Self(Self::DEFAULT_RECURSION_DEPTH) + } } -impl Default for TypeDepth { +impl Default for RecursionDepth { fn default() -> Self { - Self(3) + Self::new() } } -impl From for TypeDepth { - fn from(s: usize) -> Self { - Self(s) +impl> From for RecursionDepth +where + >::Error: std::fmt::Debug, +{ + fn from(s: I) -> Self { + Self(s.try_into().unwrap()) } } @@ -36,8 +67,8 @@ lazy_static! { static ref ANY_IDENT_STRING: SBoxedStrategy = { use proptest::string::string_regex; prop_oneof![ - string_regex(r"[[:alpha:]]{1,3}").unwrap(), - string_regex(crate::hugr::ident::PATH_COMPONENT_NICE_REGEX_STR).unwrap(), + // we shrink to more readable (i.e. :alpha:) names + string_regex(r"[[:alpha:]]+").unwrap(), string_regex(crate::hugr::ident::PATH_COMPONENT_REGEX_STR).unwrap(), ].sboxed() }; @@ -45,7 +76,7 @@ lazy_static! { static ref ANY_NONEMPTY_STRING: SBoxedStrategy = { use proptest::string::string_regex; prop_oneof![ - string_regex(r"[[:alpha:]]{1,3}").unwrap(), + // we shrink to more readable (i.e. :alpha:) names string_regex(r"[[:alpha:]]+").unwrap(), string_regex(r".+").unwrap(), ].sboxed() @@ -54,7 +85,7 @@ lazy_static! { static ref ANY_STRING: SBoxedStrategy = { use proptest::string::string_regex; prop_oneof![ - string_regex(r"[[:alpha:]]{0,3}").unwrap(), + // we shrink to more readable (i.e. :alpha:) names string_regex(r"[[:alpha:]]*").unwrap(), string_regex(r".*").unwrap(), ].sboxed() @@ -67,11 +98,22 @@ lazy_static! { any::().prop_map_into(), any::().prop_map_into(), any::().prop_map_into(), + // floats don't round trip !?! // any::().prop_map_into(), Just(Value::Number(3.into())), any_string().prop_map_into(), ].sboxed() }; + + static ref ANY_HUGR: SBoxedStrategy= { + // TODO we need more examples + // This is currently used for Value::Function + // With more uses we may need variants that return more constrained + // HUGRs. + prop_oneof![ + Just(crate::builder::test::simple_dfg_hugr()), + ].sboxed() + }; } pub fn any_nonempty_string() -> SBoxedStrategy { @@ -110,3 +152,7 @@ pub fn any_serde_yaml_value() -> impl Strategy { ) .boxed() } + +pub fn any_hugr() -> SBoxedStrategy { + ANY_HUGR.clone() +} diff --git a/hugr/src/types.rs b/hugr/src/types.rs index 42afbe498..4260574a8 100644 --- a/hugr/src/types.rs +++ b/hugr/src/types.rs @@ -22,6 +22,8 @@ pub use type_row::TypeRow; use itertools::FoldWhile::{Continue, Done}; use itertools::{repeat_n, Itertools}; use serde::{Deserialize, Serialize}; +#[cfg(all(test, feature = "proptest"))] +use {crate::proptest::RecursionDepth, ::proptest::prelude::*}; use crate::extension::{ExtensionRegistry, SignatureError}; use crate::ops::AliasDecl; @@ -70,7 +72,7 @@ impl EdgeKind { #[derive( Copy, Default, Clone, PartialEq, Eq, Hash, Debug, derive_more::Display, Serialize, Deserialize, )] -#[cfg_attr(all(test,feature = "proptest"), derive(proptest_derive::Arbitrary))] +#[cfg_attr(all(test, feature = "proptest"), derive(proptest_derive::Arbitrary))] /// Bounds on the valid operations on a type in a HUGR program. pub enum TypeBound { /// The equality operation is valid on this type. @@ -195,25 +197,48 @@ impl From for Type { } #[derive(Clone, PartialEq, Debug, Eq, derive_more::Display)] +#[cfg_attr( + all(test, feature = "proptest"), + derive(proptest_derive::Arbitrary), + proptest(params = "RecursionDepth") +)] /// Core types pub enum TypeEnum { // TODO optimise with Box ? // or some static version of this? #[allow(missing_docs)] - Extension(CustomType), + Extension( + #[cfg_attr( + all(test, feature = "proptest"), + proptest(strategy = "any_with::(params.into())") + )] + CustomType, + ), #[allow(missing_docs)] #[display(fmt = "Alias({})", "_0.name()")] Alias(AliasDecl), #[allow(missing_docs)] #[display(fmt = "Function({})", "_0")] - Function(Box), + Function( + #[cfg_attr( + all(test, feature = "proptest"), + proptest(strategy = "any_with::(params).prop_map(Box::new)") + )] + Box, + ), // Index into TypeParams, and cache of TypeBound (checked in validation) #[allow(missing_docs)] #[display(fmt = "Variable({})", _0)] Variable(usize, TypeBound), #[allow(missing_docs)] #[display(fmt = "{}", "_0")] - Sum(SumType), + Sum( + #[cfg_attr( + all(test, feature = "proptest"), + proptest(strategy = "any_with::(params)") + )] + SumType, + ), } impl TypeEnum { /// The smallest type bound that covers the whole type. @@ -451,55 +476,6 @@ pub(crate) mod test { use crate::extension::prelude::USIZE_T; - #[cfg(feature = "proptest")] - mod proptest { - use crate::ops::AliasDecl; - use crate::proptest::TypeDepth; - use crate::types::custom::test::proptest::CustomTypeArbitraryParameters; - use crate::types::{CustomType, FunctionType, SumType, TypeBound, TypeEnum, TypeRow}; - use ::proptest::prelude::*; - impl Arbitrary for super::TypeEnum { - type Parameters = TypeDepth; - type Strategy = BoxedStrategy; - fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { - prop_oneof![ - (0..10usize, any::()).prop_map(|(us, tb)| Self::Variable(us, tb)), - any::().prop_map(Self::Alias), - any_with::(depth).prop_map(Self::Sum), - any_with::(CustomTypeArbitraryParameters::new(depth)) - .prop_map(Self::Extension), - any_with::(depth).prop_map(|x| Self::Function(Box::new(x))), - ] - .boxed() - } - } - - impl Arbitrary for super::SumType { - type Parameters = TypeDepth; - type Strategy = BoxedStrategy; - fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { - use proptest::collection::vec; - let unit_strat = any::().prop_map(Self::new_unary); - let general_strat = vec(any_with::(depth), 0..3).prop_map(SumType::new); - if depth.leaf() { - unit_strat.boxed() - } else { - general_strat.boxed() - } - } - } - - impl Arbitrary for super::Type { - type Parameters = TypeDepth; - type Strategy = BoxedStrategy; - fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { - any_with::(depth.descend()) - .prop_map(Self::new) - .boxed() - } - } - } - #[test] fn construct() { let t: Type = Type::new_tuple(vec![ @@ -529,4 +505,54 @@ pub(crate) mod test { let pred_direct = SumType::Unit { size: 2 }; assert_eq!(pred1, pred_direct.into()) } + + #[cfg(feature = "proptest")] + mod proptest { + + use crate::proptest::RecursionDepth; + + use crate::types::{SumType, TypeEnum, TypeRow}; + use ::proptest::prelude::*; + // impl Arbitrary for super::TypeEnum { + // type Parameters = RecursionDepth; + // type Strategy = BoxedStrategy; + // fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { + // prop_oneof![ + // (0..10usize, any::()).prop_map(|(us, tb)| Self::Variable(us, tb)), + // any::().prop_map(Self::Alias), + // any_with::(depth).prop_map(Self::Sum), + // any_with::(CustomTypeArbitraryParameters::new(depth)) + // .prop_map(Self::Extension), + // any_with::(depth).prop_map(|x| Self::Function(Box::new(x))), + // ] + // .boxed() + // } + // } + + impl Arbitrary for super::SumType { + type Parameters = RecursionDepth; + type Strategy = BoxedStrategy; + fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { + use proptest::collection::vec; + let unit_strat = any::().prop_map(Self::new_unary); + let general_strat = vec(any_with::(depth), 0..3).prop_map(SumType::new); + if depth.leaf() { + unit_strat.boxed() + } else { + general_strat.boxed() + } + } + } + + impl Arbitrary for super::Type { + type Parameters = RecursionDepth; + type Strategy = BoxedStrategy; + fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { + // We descend here, because a TypeEnum may contain a Type + any_with::(depth.descend()) + .prop_map(Self::new) + .boxed() + } + } + } } diff --git a/hugr/src/types/custom.rs b/hugr/src/types/custom.rs index 23d651ce2..330b96441 100644 --- a/hugr/src/types/custom.rs +++ b/hugr/src/types/custom.rs @@ -144,46 +144,47 @@ pub mod test { #[cfg(feature = "proptest")] pub mod proptest { use crate::extension::ExtensionId; - use crate::proptest::TypeDepth; + use crate::proptest::any_nonempty_string; + use crate::proptest::RecursionDepth; use crate::types::type_param::TypeArg; use crate::types::{CustomType, TypeBound}; + use ::proptest::collection::vec; use ::proptest::prelude::*; #[derive(Default)] - pub struct CustomTypeArbitraryParameters(TypeDepth, Option); + pub struct CustomTypeArbitraryParameters(RecursionDepth, Option); - impl From for CustomTypeArbitraryParameters { - fn from(v: TypeDepth) -> Self { + impl From for CustomTypeArbitraryParameters { + fn from(v: RecursionDepth) -> Self { Self::new(v) } } impl CustomTypeArbitraryParameters { + pub fn new(depth: RecursionDepth) -> Self { + Self(depth, None) + } + pub fn with_bound(mut self, bound: TypeBound) -> Self { self.1 = Some(bound); self } - - pub fn new(depth: TypeDepth) -> Self { - Self(depth, None) - } } + impl Arbitrary for CustomType { type Parameters = CustomTypeArbitraryParameters; type Strategy = BoxedStrategy; fn arbitrary_with( CustomTypeArbitraryParameters(depth, mb_bound): Self::Parameters, ) -> Self::Strategy { - use crate::proptest::any_nonempty_string; - use proptest::collection::vec; - let extension = any::(); let bound = mb_bound.map_or(any::().boxed(), |x| Just(x).boxed()); let args = if depth.leaf() { Just(vec![]).boxed() } else { + // a TypeArg may contain a CustomType, so we descend here vec(any_with::(depth.descend()), 0..3).boxed() }; - (any_nonempty_string(), args, extension, bound) + (any_nonempty_string(), args, any::(), bound) .prop_map(|(id, args, extension, bound)| Self::new(id, args, extension, bound)) .boxed() } diff --git a/hugr/src/types/poly_func.rs b/hugr/src/types/poly_func.rs index e46fae88e..d69e6c2d5 100644 --- a/hugr/src/types/poly_func.rs +++ b/hugr/src/types/poly_func.rs @@ -2,6 +2,11 @@ use crate::extension::{ExtensionRegistry, SignatureError}; use itertools::Itertools; +#[cfg(all(test, feature = "proptest"))] +use { + crate::proptest::RecursionDepth, + ::proptest::{collection::vec, prelude::*}, +}; use super::type_param::{check_type_args, TypeArg, TypeParam}; use super::{FunctionType, Substitution}; @@ -15,6 +20,11 @@ use super::{FunctionType, Substitution}; #[derive( Clone, PartialEq, Debug, Default, Eq, derive_more::Display, serde::Serialize, serde::Deserialize, )] +#[cfg_attr( + all(test, feature = "proptest"), + derive(proptest_derive::Arbitrary), + proptest(params = "RecursionDepth") +)] #[display( fmt = "forall {}. {}", "params.iter().map(ToString::to_string).join(\" \")", @@ -24,8 +34,16 @@ pub struct PolyFuncType { /// The declared type parameters, i.e., these must be instantiated with /// the same number of [TypeArg]s before the function can be called. This /// defines the indices used by variables inside the body. + #[cfg_attr( + all(test, feature = "proptest"), + proptest(strategy = "vec(any_with::(params), 0..3)") + )] params: Vec, /// Template for the function. May contain variables up to length of [Self::params] + #[cfg_attr( + all(test, feature = "proptest"), + proptest(strategy = "any_with::(params)") + )] body: FunctionType, } @@ -332,26 +350,4 @@ pub(crate) mod test { )?; Ok(()) } - - #[cfg(feature = "proptest")] - mod proptest { - use super::PolyFuncType; - use crate::proptest::TypeDepth; - use crate::types::type_param::TypeParam; - use crate::types::FunctionType; - use ::proptest::prelude::*; - impl Arbitrary for PolyFuncType { - type Parameters = TypeDepth; - type Strategy = BoxedStrategy; - fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { - use proptest::collection::vec; - ( - vec(any_with::(depth), 0..3), - any_with::(depth), - ) - .prop_map(|(params, body)| PolyFuncType { params, body }) - .boxed() - } - } - } } diff --git a/hugr/src/types/signature.rs b/hugr/src/types/signature.rs index cf7e4c93a..0d9980dc6 100644 --- a/hugr/src/types/signature.rs +++ b/hugr/src/types/signature.rs @@ -10,7 +10,15 @@ use super::{subst_row, Substitution, Type, TypeRow}; use crate::extension::{ExtensionRegistry, ExtensionSet, SignatureError}; use crate::{Direction, IncomingPort, OutgoingPort, Port}; +#[cfg(all(test, feature = "proptest"))] +use {crate::proptest::RecursionDepth, ::proptest::prelude::*}; + #[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[cfg_attr( + all(test, feature = "proptest"), + derive(proptest_derive::Arbitrary), + proptest(params = "RecursionDepth") +)] /// Describes the edges required to/from a node, and thus, also the type of a [Graph]. /// This includes both the concept of "signature" in the spec, /// and also the target (value) of a call (static). @@ -18,8 +26,16 @@ use crate::{Direction, IncomingPort, OutgoingPort, Port}; /// [Graph]: crate::ops::constant::Value::Function pub struct FunctionType { /// Value inputs of the function. + #[cfg_attr( + all(test, feature = "proptest"), + proptest(strategy = "any_with::(params)") + )] pub input: TypeRow, /// Value outputs of the function. + #[cfg_attr( + all(test, feature = "proptest"), + proptest(strategy = "any_with::(params)") + )] pub output: TypeRow, /// The extension requirements which are added by the operation pub extension_reqs: ExtensionSet, @@ -243,28 +259,4 @@ mod test { assert_eq!(f_type.input_types(), &[Type::UNIT]); assert_eq!(f_type.output_types(), &[USIZE_T]); } - - #[cfg(feature = "proptest")] - mod proptest { - use crate::extension::ExtensionSet; - use crate::proptest::TypeDepth; - use crate::types::{FunctionType, TypeRow}; - use ::proptest::prelude::*; - impl Arbitrary for super::FunctionType { - type Parameters = TypeDepth; - type Strategy = BoxedStrategy; - fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { - let input = any_with::(depth); - let output = any_with::(depth); - let extension = any::(); - (input, output, extension) - .prop_map(|(input, output, extension_reqs)| FunctionType { - input, - output, - extension_reqs, - }) - .boxed() - } - } - } } diff --git a/hugr/src/types/type_param.rs b/hugr/src/types/type_param.rs index b6554a00a..07d34d7b4 100644 --- a/hugr/src/types/type_param.rs +++ b/hugr/src/types/type_param.rs @@ -408,11 +408,11 @@ mod test { use super::super::{CustomTypeArg, TypeArg, TypeArgVariable, TypeParam, UpperBound}; use crate::extension::ExtensionSet; - use crate::proptest::{any_serde_yaml_value, TypeDepth}; + use crate::proptest::{any_serde_yaml_value, RecursionDepth}; use crate::types::{CustomType, Type, TypeBound}; impl Arbitrary for CustomTypeArg { - type Parameters = TypeDepth; + type Parameters = RecursionDepth; type Strategy = BoxedStrategy; fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { ( @@ -427,7 +427,7 @@ mod test { } impl Arbitrary for TypeArgVariable { - type Parameters = TypeDepth; + type Parameters = RecursionDepth; type Strategy = BoxedStrategy; fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { (any::(), any_with::(depth)) @@ -437,7 +437,7 @@ mod test { } impl Arbitrary for TypeParam { - type Parameters = TypeDepth; + type Parameters = RecursionDepth; type Strategy = BoxedStrategy; fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { use proptest::collection::vec; @@ -453,6 +453,7 @@ mod test { .boxed(), ]); if !depth.leaf() { + // we descend here because we these constructors contain TypeParams strat = strat .or(any_with::(depth.descend()) .prop_map(|x| Self::List { param: Box::new(x) }) @@ -467,7 +468,7 @@ mod test { } impl Arbitrary for TypeArg { - type Parameters = TypeDepth; + type Parameters = RecursionDepth; type Strategy = BoxedStrategy; fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { use proptest::collection::vec; @@ -483,11 +484,16 @@ mod test { any_with::(depth) .prop_map(|arg| Self::Opaque { arg }) .boxed(), + // TODO this is a bit dodgy, TypeArgVariables are supposed + // to be constructed from TypeArg::new_var_use. We are only + // using this instance for serialisation now, but if we want + // to generate valid TypeArgs this will need to change. any_with::(depth) .prop_map(|v| Self::Variable { v }) .boxed(), ]); if !depth.leaf() { + // We descend here because this constructor contains TypeArg> strat = strat.or(vec(any_with::(depth.descend()), 0..3) .prop_map(|elems| Self::Sequence { elems }) .boxed()); diff --git a/hugr/src/types/type_row.rs b/hugr/src/types/type_row.rs index 354df2e99..9220d038e 100644 --- a/hugr/src/types/type_row.rs +++ b/hugr/src/types/type_row.rs @@ -123,12 +123,12 @@ impl DerefMut for TypeRow { mod test { #[cfg(feature = "proptest")] mod proptest { - use crate::proptest::TypeDepth; + use crate::proptest::RecursionDepth; use crate::{type_row, types::Type}; use ::proptest::prelude::*; impl Arbitrary for super::super::TypeRow { - type Parameters = TypeDepth; + type Parameters = RecursionDepth; type Strategy = BoxedStrategy; fn arbitrary_with(depth: Self::Parameters) -> Self::Strategy { use proptest::collection::vec; diff --git a/justfile b/justfile index 525b6d9ee..4ed1894b2 100644 --- a/justfile +++ b/justfile @@ -18,8 +18,9 @@ test language="[rust|python]" : (_run_lang language \ "poetry run pytest" ) +# Run all proptests proptest: - cargo test --all-features + cargo test --feature proptest '::proptest' # Run all the benchmarks. bench language="[rust|python]": (_run_lang language \