| // Copyright 2018 The proptest developers |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| //! High level IR and abstract syntax tree (AST) of impls. |
| //! |
| //! We compile to this AST and then linearise that to Rust code. |
| |
| use std::ops::{Add, AddAssign}; |
| |
| use proc_macro2::{Span, TokenStream}; |
| use quote::{ToTokens, TokenStreamExt}; |
| use syn; |
| use syn::spanned::Spanned; |
| |
| use crate::error::{Ctx, DeriveResult}; |
| use crate::use_tracking::UseTracker; |
| use crate::util::self_ty; |
| |
| //============================================================================== |
| // Config |
| //============================================================================== |
| |
| /// The `MAX - 1` number of strategies that `TupleUnion` supports. |
| /// Increase this if the behaviour is changed in `proptest`. |
| /// Keeping this lower than what `proptest` supports will also work |
| /// but for optimality this should follow what `proptest` supports. |
| const UNION_CHUNK_SIZE: usize = 9; |
| |
| /// The `MAX - 1` tuple length `Arbitrary` is implemented for. After this number, |
| /// tuples are expanded as nested tuples of up to `MAX` elements. The value should |
| /// be kept in sync with the largest impl in `proptest/src/arbitrary/tuples.rs`. |
| const NESTED_TUPLE_CHUNK_SIZE: usize = 9; |
| |
| /// The name of the top parameter variable name given in `arbitrary_with`. |
| /// Changing this is not a breaking change because a user is expected not |
| /// to rely on this (and the user shouldn't be able to..). |
| const TOP_PARAM_NAME: &str = "_top"; |
| |
| /// The name of the variable name used for user facing parameter types |
| /// specified in a `#[proptest(params = "<type>")]` attribute. |
| /// |
| /// Changing the value of this constant constitutes a breaking change! |
| const API_PARAM_NAME: &str = "params"; |
| |
| //============================================================================== |
| // AST Root |
| //============================================================================== |
| |
| /// Top level AST and everything required to implement `Arbitrary` for any |
| /// given type. Linearizing this AST gives you the impl wrt. Rust code. |
| pub struct Impl { |
| /// Name of the type. |
| typ: syn::Ident, |
| /// Tracker for uses of Arbitrary trait for a generic type. |
| tracker: UseTracker, |
| /// The three main parts, see description of `ImplParts` for details. |
| parts: ImplParts, |
| } |
| |
| /// The three main parts to deriving `Arbitrary` for a type. |
| /// That is: the associated items `Parameters` (`Params`), |
| /// `Strategy` (`Strategy`) as well as the construction of the |
| /// strategy itself (`Ctor`). |
| pub type ImplParts = (Params, Strategy, Ctor); |
| |
| impl Impl { |
| /// Constructs a new `Impl` from the parts as described on the type. |
| pub fn new(typ: syn::Ident, tracker: UseTracker, parts: ImplParts) -> Self { |
| Self { |
| typ, |
| tracker, |
| parts, |
| } |
| } |
| |
| /// Linearises the impl into a sequence of tokens. |
| /// This produces the actual Rust code for the impl. |
| pub fn into_tokens(self, ctx: Ctx) -> DeriveResult<TokenStream> { |
| let Impl { |
| typ, |
| mut tracker, |
| parts: (params, strategy, ctor), |
| } = self; |
| |
| /// A `Debug` bound on a type variable. |
| fn debug_bound() -> syn::TypeParamBound { |
| parse_quote!(::std::fmt::Debug) |
| } |
| |
| /// An `Arbitrary` bound on a type variable. |
| fn arbitrary_bound() -> syn::TypeParamBound { |
| parse_quote!(_proptest::arbitrary::Arbitrary) |
| } |
| |
| // Add bounds and get generics for the impl. |
| tracker.add_bounds(ctx, &arbitrary_bound(), Some(debug_bound()))?; |
| let generics = tracker.consume(); |
| let (impl_generics, ty_generics, where_clause) = |
| generics.split_for_impl(); |
| |
| let _top = call_site_ident(TOP_PARAM_NAME); |
| |
| let _const = call_site_ident(&format!("_IMPL_ARBITRARY_FOR_{}", typ)); |
| |
| // Linearise everything. We're done after this. |
| let q = quote! { |
| #[allow(non_upper_case_globals)] |
| const #_const: () = { |
| extern crate proptest as _proptest; |
| |
| impl #impl_generics _proptest::arbitrary::Arbitrary |
| for #typ #ty_generics #where_clause { |
| type Parameters = #params; |
| |
| type Strategy = #strategy; |
| |
| fn arbitrary_with(#_top: Self::Parameters) -> Self::Strategy { |
| #ctor |
| } |
| } |
| |
| }; |
| }; |
| |
| Ok(q) |
| } |
| } |
| |
| //============================================================================== |
| // Smart construcors, StratPair |
| //============================================================================== |
| |
| /// A pair of `Strategy` and `Ctor`. These always come in pairs. |
| pub type StratPair = (Strategy, Ctor); |
| |
| /// The type and constructor for `any::<Type>()`. |
| pub fn pair_any(ty: syn::Type, span: Span) -> StratPair { |
| let q = Ctor::Arbitrary(ty.clone(), None, span); |
| (Strategy::Arbitrary(ty, span), q) |
| } |
| |
| /// The type and constructor for `any_with::<Type>(parameters)`. |
| pub fn pair_any_with(ty: syn::Type, var: usize, span: Span) -> StratPair { |
| let q = Ctor::Arbitrary(ty.clone(), Some(var), span); |
| (Strategy::Arbitrary(ty, span), q) |
| } |
| |
| /// The type and constructor for a specific strategy value constructed by the |
| /// given expression. Currently, the type is erased and a `BoxedStrategy<Type>` |
| /// is given back instead. |
| /// |
| /// This is a temporary restriction. Once `impl Trait` is stabilized, |
| /// the boxing and dynamic dispatch can be replaced with a statically |
| /// dispatched anonymous type instead. |
| pub fn pair_existential(ty: syn::Type, strat: syn::Expr) -> StratPair { |
| (Strategy::Existential(ty), Ctor::Existential(strat)) |
| } |
| |
| /// The type and constructor for a strategy that always returns the value |
| /// provided in the expression `val`. |
| /// This is statically dispatched since no erasure is needed or used. |
| pub fn pair_value(ty: syn::Type, val: syn::Expr) -> StratPair { |
| (Strategy::Value(ty), Ctor::Value(val)) |
| } |
| |
| /// Same as `pair_existential` for the `Self` type. |
| pub fn pair_existential_self(strat: syn::Expr) -> StratPair { |
| pair_existential(self_ty(), strat) |
| } |
| |
| /// Same as `pair_value` for the `Self` type. |
| pub fn pair_value_self(val: syn::Expr) -> StratPair { |
| pair_value(self_ty(), val) |
| } |
| |
| /// Erased strategy for a fixed value. |
| pub fn pair_value_exist(ty: syn::Type, strat: syn::Expr) -> StratPair { |
| (Strategy::Existential(ty), Ctor::ValueExistential(strat)) |
| } |
| |
| /// Erased strategy for a fixed value. |
| pub fn pair_value_exist_self(strat: syn::Expr) -> StratPair { |
| pair_value_exist(self_ty(), strat) |
| } |
| |
| /// Same as `pair_value` but for a unit variant or unit struct. |
| pub fn pair_unit_self(path: &syn::Path) -> StratPair { |
| pair_value_self(parse_quote!( #path {} )) |
| } |
| |
| /// The type and constructor for `#[proptest(regex(..))]`. |
| pub fn pair_regex(ty: syn::Type, regex: syn::Expr) -> StratPair { |
| (Strategy::Regex(ty.clone()), Ctor::Regex(ty, regex)) |
| } |
| |
| /// Same as `pair_regex` for the `Self` type. |
| pub fn pair_regex_self(regex: syn::Expr) -> StratPair { |
| pair_regex(self_ty(), regex) |
| } |
| |
| /// The type and constructor for .prop_map:ing a set of strategies |
| /// into the type we are implementing for. The closure for the |
| /// `.prop_map(<closure>)` must also be given. |
| pub fn pair_map( |
| (strats, ctors): (Vec<Strategy>, Vec<Ctor>), |
| closure: MapClosure, |
| ) -> StratPair { |
| ( |
| Strategy::Map(strats.into()), |
| Ctor::Map(ctors.into(), closure), |
| ) |
| } |
| |
| /// The type and constructor for a union of strategies which produces a new |
| /// strategy that used the given strategies with probabilities based on the |
| /// assigned relative weights for each strategy. |
| pub fn pair_oneof( |
| (strats, ctors): (Vec<Strategy>, Vec<(u32, Ctor)>), |
| ) -> StratPair { |
| (Strategy::Union(strats.into()), Ctor::Union(ctors.into())) |
| } |
| |
| /// Potentially apply a filter to a strategy type and its constructor. |
| pub fn pair_filter( |
| filter: Vec<syn::Expr>, |
| ty: syn::Type, |
| pair: StratPair, |
| ) -> StratPair { |
| filter.into_iter().fold(pair, |(strat, ctor), filter| { |
| ( |
| Strategy::Filter(Box::new(strat), ty.clone()), |
| Ctor::Filter(Box::new(ctor), filter), |
| ) |
| }) |
| } |
| |
| //============================================================================== |
| // Parameters |
| //============================================================================== |
| |
| /// Represents the associated item of `Parameters` of an `Arbitrary` impl. |
| pub struct Params(Vec<syn::Type>); |
| |
| impl Params { |
| /// Construct an `empty` list of parameters. |
| /// This is equivalent to the unit type `()`. |
| pub fn empty() -> Self { |
| Params(Vec::new()) |
| } |
| |
| /// Computes and returns the number of parameter types. |
| pub fn len(&self) -> usize { |
| self.0.len() |
| } |
| } |
| |
| impl From<Params> for syn::Type { |
| fn from(x: Params) -> Self { |
| let tys = x.0; |
| parse_quote!( (#(#tys),*) ) |
| } |
| } |
| |
| impl Add<syn::Type> for Params { |
| type Output = Params; |
| |
| fn add(mut self, rhs: syn::Type) -> Self::Output { |
| self.0.push(rhs); |
| self |
| } |
| } |
| |
| impl AddAssign<syn::Type> for Params { |
| fn add_assign(&mut self, rhs: syn::Type) { |
| self.0.push(rhs); |
| } |
| } |
| |
| impl ToTokens for Params { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| NestedTuple(self.0.as_slice()).to_tokens(tokens) |
| } |
| } |
| |
| /// Returns for a given type `ty` the associated item `Parameters` of the |
| /// type's `Arbitrary` implementation. |
| pub fn arbitrary_param(ty: &syn::Type) -> syn::Type { |
| parse_quote!(<#ty as _proptest::arbitrary::Arbitrary>::Parameters) |
| } |
| |
| //============================================================================== |
| // Strategy |
| //============================================================================== |
| |
| /// The type of a given `Strategy`. |
| pub enum Strategy { |
| /// Assuming the metavariable `$ty` for a given type, this models the |
| /// strategy type `<$ty as Arbitrary>::Strategy`. |
| Arbitrary(syn::Type, Span), |
| /// This models <$ty as StrategyFromRegex>::Strategy. |
| Regex(syn::Type), |
| /// Assuming the metavariable `$ty` for a given type, this models the |
| /// strategy type `BoxedStrategy<$ty>`, i.e: an existentially typed strategy. |
| /// |
| /// The dynamic dispatch used here is an implementation detail that may be |
| /// changed. Such a change does not count as a breakage semver wise. |
| Existential(syn::Type), |
| /// Assuming the metavariable `$ty` for a given type, this models a |
| /// non-shrinking strategy that simply always returns a value of the |
| /// given type. |
| Value(syn::Type), |
| /// Assuming a sequence of strategies, this models a mapping from that |
| /// sequence to `Self`. |
| Map(Box<[Strategy]>), |
| /// Assuming a sequence of relative-weighted strategies, this models a |
| /// weighted choice of those strategies. The resultant strategy will in |
| /// other words randomly pick one strategy with probabilities based on the |
| /// specified weights. |
| Union(Box<[Strategy]>), |
| /// A filtered strategy with `.prop_filter`. |
| Filter(Box<Strategy>, syn::Type), |
| } |
| |
| macro_rules! quote_append { |
| ($tokens: expr, $($quasi: tt)*) => { |
| $tokens.append_all(quote!($($quasi)*)) |
| }; |
| } |
| |
| impl Strategy { |
| fn types(&self) -> Vec<syn::Type> { |
| use self::Strategy::*; |
| match self { |
| Arbitrary(ty, _) => vec![ty.clone()], |
| Regex(ty) => vec![ty.clone()], |
| Existential(ty) => vec![ty.clone()], |
| Value(ty) => vec![ty.clone()], |
| Map(strats) => strats.iter().flat_map(|s| s.types()).collect(), |
| Union(strats) => strats.iter().flat_map(|s| s.types()).collect(), |
| Filter(_, ty) => vec![ty.clone()], |
| } |
| } |
| } |
| |
| impl ToTokens for Strategy { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| // The logic of each of these are pretty straight forward save for |
| // union which is described separately. |
| use self::Strategy::*; |
| match self { |
| Arbitrary(ty, span) => tokens.append_all(quote_spanned!(*span=> |
| <#ty as _proptest::arbitrary::Arbitrary>::Strategy |
| )), |
| Regex(ty) => quote_append!(tokens, |
| <#ty as _proptest::string::StrategyFromRegex>::Strategy |
| ), |
| Existential(ty) => quote_append!(tokens, |
| _proptest::strategy::BoxedStrategy<#ty> |
| ), |
| Value(ty) => quote_append!(tokens, fn() -> #ty ), |
| Map(strats) => { |
| let types = self.types(); |
| let field_tys = NestedTuple(&types); |
| let strats = NestedTuple(&strats); |
| quote_append!(tokens, |
| _proptest::strategy::Map< ( #strats ), |
| fn( #field_tys ) -> Self |
| > |
| ) |
| } |
| Union(strats) => union_strat_to_tokens(tokens, strats), |
| Filter(strat, ty) => quote_append!(tokens, |
| _proptest::strategy::Filter<#strat, fn(&#ty) -> bool> |
| ), |
| } |
| } |
| } |
| |
| //============================================================================== |
| // Constructor |
| //============================================================================== |
| |
| /// The right hand side (RHS) of a let binding of parameters. |
| pub enum FromReg { |
| /// Denotes a move from the top parameter given in the arguments of |
| /// `arbitrary_with`. |
| Top, |
| /// Denotes a move from a variable `params_<x>` where `<x>` is the given |
| /// number. |
| Num(usize), |
| } |
| |
| /// The left hand side (LHS) of a let binding of parameters. |
| pub enum ToReg { |
| /// Denotes a move and declaration to a sequence of variables from |
| /// `params_0` to `params_x`. |
| Range(usize), |
| /// Denotes a move and declaration of a special variable `params` that is |
| /// user facing and is ALWAYS named `params`. |
| /// |
| /// To change the name this linearises to is considered a breaking change |
| /// wrt. semver. |
| API, |
| } |
| |
| /// Models an expression that generates a proptest `Strategy`. |
| pub enum Ctor { |
| /// A strategy generated by using the `Arbitrary` impl for the given `Ty´. |
| /// If `Some(idx)` is specified, then a parameter at `params_<idx>` is used |
| /// and provided to `any_with::<Ty>(params_<idx>)`. |
| Arbitrary(syn::Type, Option<usize>, Span), |
| /// A strategy that is generated by a mapping a regex in the form of a |
| /// string slice to the actual regex. |
| Regex(syn::Type, syn::Expr), |
| /// An exact strategy value given by the expression. |
| Existential(syn::Expr), |
| /// A strategy that always produces the given expression. |
| Value(syn::Expr), |
| /// A strategy that always produces the given expression but which is erased. |
| ValueExistential(syn::Expr), |
| /// A strategy that maps from a sequence of strategies into `Self`. |
| Map(Box<[Ctor]>, MapClosure), |
| /// A strategy that randomly selects one of the given relative-weighted |
| /// strategies. |
| Union(Box<[(u32, Ctor)]>), |
| /// A let binding that moves to and declares the `ToReg` from the `FromReg` |
| /// as well as the strategy that uses the `ToReg`. |
| Extract(Box<Ctor>, ToReg, FromReg), |
| /// A filtered strategy with `.prop_filter`. |
| Filter(Box<Ctor>, syn::Expr), |
| } |
| |
| /// Wraps the given strategy producing expression with a move into |
| /// `params_<to>` from `FromReg`. This is used when the given `c` expects |
| /// `params_<to>` to be there. |
| pub fn extract_all(c: Ctor, to: usize, from: FromReg) -> Ctor { |
| extract(c, ToReg::Range(to), from) |
| } |
| |
| /// Wraps the given strategy producing expression with a move into `params` |
| /// (literally named like that) from `FromReg`. This is used when the given |
| /// `c` expects `params` to be there. |
| pub fn extract_api(c: Ctor, from: FromReg) -> Ctor { |
| extract(c, ToReg::API, from) |
| } |
| |
| impl ToTokens for FromReg { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self { |
| FromReg::Top => call_site_ident(TOP_PARAM_NAME).to_tokens(tokens), |
| FromReg::Num(reg) => param(*reg).to_tokens(tokens), |
| } |
| } |
| } |
| |
| impl ToTokens for ToReg { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match *self { |
| ToReg::Range(to) if to == 1 => param(0).to_tokens(tokens), |
| ToReg::Range(to) => { |
| let params: Vec<_> = (0..to).map(param).collect(); |
| NestedTuple(¶ms).to_tokens(tokens) |
| }, |
| ToReg::API => call_site_ident(API_PARAM_NAME).to_tokens(tokens), |
| } |
| } |
| } |
| |
| impl ToTokens for Ctor { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| // The logic of each of these are pretty straight forward save for |
| // union which is described separately. |
| use self::Ctor::*; |
| match self { |
| Filter(ctor, filter) => quote_append!(tokens, |
| _proptest::strategy::Strategy::prop_filter( |
| #ctor, stringify!(#filter), #filter) |
| ), |
| Extract(ctor, to, from) => quote_append!(tokens, { |
| let #to = #from; #ctor |
| }), |
| Arbitrary(ty, fv, span) => { |
| tokens.append_all(if let Some(fv) = fv { |
| let args = param(*fv); |
| quote_spanned!(*span=> |
| _proptest::arbitrary::any_with::<#ty>(#args) |
| ) |
| } else { |
| quote_spanned!(*span=> |
| _proptest::arbitrary::any::<#ty>() |
| ) |
| }) |
| } |
| Regex(ty, regex) => quote_append!(tokens, |
| <#ty as _proptest::string::StrategyFromRegex>::from_regex(#regex) |
| ), |
| Existential(expr) => quote_append!(tokens, |
| _proptest::strategy::Strategy::boxed( #expr ) ), |
| Value(expr) => quote_append!(tokens, || #expr ), |
| ValueExistential(expr) => quote_append!(tokens, |
| _proptest::strategy::Strategy::boxed( |
| _proptest::strategy::LazyJust::new(move || #expr) |
| ) |
| ), |
| Map(ctors, closure) => map_ctor_to_tokens(tokens, &ctors, closure), |
| Union(ctors) => union_ctor_to_tokens(tokens, ctors), |
| } |
| } |
| } |
| |
| struct NestedTuple<'a, T>(&'a [T]); |
| |
| impl<'a, T: ToTokens> ToTokens for NestedTuple<'a, T> { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let NestedTuple(elems) = self; |
| if elems.is_empty() { |
| quote_append!(tokens, ()); |
| } |
| else if let [x] = elems { |
| x.to_tokens(tokens); |
| } else { |
| let chunks = elems.chunks(NESTED_TUPLE_CHUNK_SIZE); |
| Recurse(&chunks).to_tokens(tokens); |
| } |
| |
| struct Recurse<'a, T: ToTokens>(&'a ::std::slice::Chunks<'a, T>); |
| |
| impl<'a, T: ToTokens> ToTokens for Recurse<'a, T> { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let mut chunks = self.0.clone(); |
| if let Some(head) = chunks.next() { |
| if let [c] = head { |
| // Only one element left - no need to nest. |
| quote_append!(tokens, #c); |
| } else { |
| let tail = Recurse(&chunks); |
| quote_append!(tokens, (#(#head,)* #tail)); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| fn map_ctor_to_tokens(tokens: &mut TokenStream, ctors: &[Ctor], closure: &MapClosure) { |
| let ctors = NestedTuple(ctors); |
| |
| quote_append!(tokens, |
| _proptest::strategy::Strategy::prop_map( |
| #ctors, |
| #closure |
| ) |
| ); |
| } |
| |
| /// Tokenizes a weighted list of `Ctor`. |
| /// |
| /// The logic is that the output should be as linear as possible while still |
| /// supporting enums with an unbounded number of variants without any boxing |
| /// (erasure) or dynamic dispatch. |
| /// |
| /// As `TupleUnion` is (currently) limited to 10 summands in the coproduct |
| /// we can't just emit the entire thing linearly as this will fail on the 11:th |
| /// variant. |
| /// |
| /// A naive approach to solve might be to simply use a cons-list like so: |
| /// |
| /// ```ignore |
| /// TupleUnion::new( |
| /// (w_1, s_1), |
| /// (w_2 + w_3 + w_4 + w_5, |
| /// TupleUnion::new( |
| /// (w_2, s_2), |
| /// (w_3 + w_4 + w_5, |
| /// TupleUnion::new( |
| /// (w_3, s_3), |
| /// (w_4 + w_5, |
| /// TupleUnion::new( |
| /// (w_4, s_4), |
| /// (w_5, s_5), |
| /// )) |
| /// )) |
| /// )) |
| /// ) |
| /// ``` |
| /// |
| /// However, we can do better by being linear for the `10 - 1` first |
| /// strategies and then switch to nesting like so: |
| /// |
| /// ```ignore |
| /// (1, 2, 3, 4, 5, 6, 7, 8, 9, |
| /// (10, 11, 12, 13, 14, 15, 16, 17, 18, |
| /// (19, ..))) |
| /// ``` |
| fn union_ctor_to_tokens(tokens: &mut TokenStream, ctors: &[(u32, Ctor)]) { |
| if ctors.is_empty() { |
| return; |
| } |
| |
| if let [(_, ctor)] = ctors { |
| // This is not a union at all - user provided an enum with one variant. |
| ctor.to_tokens(tokens); |
| return; |
| } |
| |
| let mut chunks = ctors.chunks(UNION_CHUNK_SIZE); |
| let chunk = chunks.next().unwrap(); |
| let head = chunk.iter().map(wrap_arc); |
| let tail = Recurse(weight_sum(ctors) - weight_sum(chunk), chunks); |
| |
| quote_append!(tokens, |
| _proptest::strategy::TupleUnion::new(( #(#head,)* #tail )) |
| ); |
| |
| struct Recurse<'a>(u32, ::std::slice::Chunks<'a, (u32, Ctor)>); |
| |
| impl<'a> ToTokens for Recurse<'a> { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let (tweight, mut chunks) = (self.0, self.1.clone()); |
| |
| if let Some(chunk) = chunks.next() { |
| if let [(w, c)] = chunk { |
| // Only one element left - no need to nest. |
| quote_append!(tokens, (#w, ::std::sync::Arc::new(#c)) ); |
| } else { |
| let head = chunk.iter().map(wrap_arc); |
| let tail = Recurse(tweight - weight_sum(chunk), chunks); |
| quote_append!(tokens, |
| (#tweight, ::std::sync::Arc::new( |
| _proptest::strategy::TupleUnion::new(( |
| #(#head,)* #tail |
| )))) |
| ); |
| } |
| } |
| } |
| } |
| |
| fn weight_sum(ctors: &[(u32, Ctor)]) -> u32 { |
| use std::num::Wrapping; |
| let Wrapping(x) = ctors.iter().map(|&(w, _)| Wrapping(w)).sum(); |
| x |
| } |
| |
| fn wrap_arc(arg: &(u32, Ctor)) -> TokenStream { |
| let (w, c) = arg; |
| quote!( (#w, ::std::sync::Arc::new(#c)) ) |
| } |
| } |
| |
| /// Tokenizes a weighted list of `Strategy`. |
| /// For details, see `union_ctor_to_tokens`. |
| fn union_strat_to_tokens(tokens: &mut TokenStream, strats: &[Strategy]) { |
| if strats.is_empty() { |
| return; |
| } |
| |
| if let [strat] = strats { |
| // This is not a union at all - user provided an enum with one variant. |
| strat.to_tokens(tokens); |
| return; |
| } |
| |
| let mut chunks = strats.chunks(UNION_CHUNK_SIZE); |
| let chunk = chunks.next().unwrap(); |
| let head = chunk.iter().map(wrap_arc); |
| let tail = Recurse(chunks); |
| |
| quote_append!(tokens, |
| _proptest::strategy::TupleUnion<( #(#head,)* #tail )> |
| ); |
| |
| struct Recurse<'a>(::std::slice::Chunks<'a, Strategy>); |
| |
| impl<'a> ToTokens for Recurse<'a> { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let mut chunks = self.0.clone(); |
| |
| if let Some(chunk) = chunks.next() { |
| if let [s] = chunk { |
| // Only one element left - no need to nest. |
| quote_append!(tokens, (u32, ::std::sync::Arc<#s>) ); |
| } else { |
| let head = chunk.iter().map(wrap_arc); |
| let tail = Recurse(chunks); |
| quote_append!(tokens, |
| (u32, |
| ::std::sync::Arc<_proptest::strategy::TupleUnion<( |
| #(#head,)* #tail |
| )>>) |
| ); |
| } |
| } |
| } |
| } |
| |
| fn wrap_arc(s: &Strategy) -> TokenStream { |
| quote!( (u32, ::std::sync::Arc<#s>) ) |
| } |
| } |
| |
| /// Wraps a `Ctor` that expects the `to` "register" to be filled with |
| /// contents of the `from` register. The correctness of this wrt. the |
| /// generated Rust code has to be verified externally by checking the |
| /// construction of the particular `Ctor`. |
| fn extract(c: Ctor, to: ToReg, from: FromReg) -> Ctor { |
| Ctor::Extract(Box::new(c), to, from) |
| } |
| |
| /// Construct a `FreshVar` prefixed by `param_`. |
| fn param<'a>(fv: usize) -> FreshVar<'a> { |
| fresh_var("param", fv) |
| } |
| |
| //============================================================================== |
| // MapClosure |
| //============================================================================== |
| |
| /// Constructs a `MapClosure` for the given `path` and a list of fields. |
| pub fn map_closure(path: syn::Path, fs: &[syn::Field]) -> MapClosure { |
| MapClosure(path, fs.to_owned()) |
| } |
| |
| /// A `MapClosure` models the closure part inside a `.prop_map(..)` call. |
| #[derive(Debug)] |
| pub struct MapClosure(syn::Path, Vec<syn::Field>); |
| |
| impl ToTokens for MapClosure { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| fn tmp_var<'a>(idx: usize) -> FreshVar<'a> { |
| fresh_var("tmp", idx) |
| } |
| |
| let MapClosure(path, fields) = self; |
| let count = fields.len(); |
| let tmps: Vec<_> = (0..count).map(tmp_var).collect(); |
| let inits = fields.iter().enumerate().map(|(idx, field)| { |
| let tv = tmp_var(idx); |
| if let Some(name) = &field.ident { |
| quote_spanned!(field.span()=> #name: #tv ) |
| } else { |
| let name = syn::Member::Unnamed(syn::Index::from(idx)); |
| quote_spanned!(field.span()=> #name: #tv ) |
| } |
| }); |
| let tmps = NestedTuple(&tmps); |
| quote_append!(tokens, | #tmps | #path { #(#inits),* } ); |
| } |
| } |
| |
| //============================================================================== |
| // FreshVar |
| //============================================================================== |
| |
| /// Construct a `FreshVar` with the given `prefix` and the number it has in the |
| /// count of temporaries for that prefix. |
| fn fresh_var(prefix: &str, count: usize) -> FreshVar { |
| FreshVar { prefix, count } |
| } |
| |
| /// A `FreshVar` is an internal implementation detail and models a temporary |
| /// variable on the stack. |
| struct FreshVar<'a> { |
| prefix: &'a str, |
| count: usize, |
| } |
| |
| impl<'a> ToTokens for FreshVar<'a> { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let ident = format!("{}_{}", self.prefix, self.count); |
| call_site_ident(&ident).to_tokens(tokens) |
| } |
| } |
| |
| fn call_site_ident(ident: &str) -> syn::Ident { |
| syn::Ident::new(ident, Span::call_site()) |
| } |
| |
| //============================================================================== |
| // Util |
| //============================================================================== |
| |
| /// A comma separated tuple to a token stream when more than 1, or just flat |
| /// when 1. |
| #[derive(Copy, Clone)] |
| struct Tuple2<S>(S); |
| |
| impl<'a, T: ToTokens> ToTokens for Tuple2<&'a [T]> { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| match self.0 { |
| [x] => x.to_tokens(tokens), |
| _ => Tuple(self.0).to_tokens(tokens), |
| } |
| } |
| } |
| |
| /// Append a comma separated tuple to a token stream. |
| struct Tuple<I>(I); |
| |
| impl<T: ToTokens, I: Clone + IntoIterator<Item = T>> ToTokens for Tuple<I> { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let iter = self.0.clone(); |
| quote_append!(tokens, ( #(#iter),* ) ); |
| } |
| } |