use syn::{self, Meta, NestedMeta, Lit, Ident, Attribute, Expr, Type};
use crate::util;
use crate::interp;
use crate::error::{self, Ctx, DeriveResult};
#[derive(Clone)]
pub struct ParsedAttributes {
pub skip: bool,
pub weight: Option<u32>,
pub params: ParamsMode,
pub strategy: StratMode,
pub filter: Vec<syn::Expr>,
pub no_bound: bool,
}
#[derive(Clone)]
pub enum StratMode {
Arbitrary,
Value(Expr),
Strategy(Expr),
Regex(Expr),
}
#[derive(Clone)]
pub enum ParamsMode {
Passthrough,
Default,
Specified(Type),
}
impl ParamsMode {
pub fn is_set(&self) -> bool {
if let ParamsMode::Passthrough = *self { false } else { true }
}
pub fn into_option(self) -> Option<Option<Type>> {
use self::ParamsMode::*;
match self {
Passthrough => None,
Specified(ty) => Some(Some(ty)),
Default => Some(None),
}
}
}
impl StratMode {
pub fn is_set(&self) -> bool {
if let StratMode::Arbitrary = self { false } else { true }
}
}
pub fn parse_attributes(ctx: Ctx, attrs: &[Attribute])
-> DeriveResult<ParsedAttributes>
{
let attrs = parse_attributes_base(ctx, attrs)?;
if attrs.no_bound {
error::no_bound_set_on_non_tyvar(ctx);
}
Ok(attrs)
}
pub fn parse_top_attributes(ctx: Ctx, attrs: &[Attribute])
-> DeriveResult<ParsedAttributes>
{
parse_attributes_base(ctx, attrs)
}
pub fn has_no_bound(ctx: Ctx, attrs: &[Attribute]) -> DeriveResult<bool> {
let attrs = parse_attributes_base(ctx, attrs)?;
error::if_anything_specified(ctx, &attrs, error::TY_VAR);
Ok(attrs.no_bound)
}
fn parse_attributes_base(ctx: Ctx, attrs: &[Attribute])
-> DeriveResult<ParsedAttributes>
{
let acc = parse_accumulate(ctx, attrs);
Ok(ParsedAttributes {
skip: acc.skip.is_some(),
weight: acc.weight,
filter: acc.filter,
params: parse_params_mode(ctx, acc.no_params, acc.params)?,
strategy: parse_strat_mode(ctx, acc.strategy, acc.value, acc.regex)?,
no_bound: acc.no_bound.is_some()
})
}
#[derive(Default)]
struct ParseAcc {
skip: Option<()>,
weight: Option<u32>,
no_params: Option<()>,
params: Option<Type>,
strategy: Option<Expr>,
value: Option<Expr>,
regex: Option<Expr>,
filter: Vec<Expr>,
no_bound: Option<()>,
}
fn parse_accumulate(ctx: Ctx, attrs: &[Attribute]) -> ParseAcc {
let mut state = ParseAcc::default();
for attr in attrs {
if is_proptest_attr(&attr) {
state = extract_modifiers(ctx, &attr).into_iter()
.fold(state, |state, meta| dispatch_attribute(ctx, state, meta))
}
}
state
}
fn is_proptest_attr(attr: &Attribute) -> bool {
util::eq_simple_path("proptest", &attr.path)
}
fn extract_modifiers(ctx: Ctx, attr: &Attribute) -> Vec<Meta> {
if !is_outer_attr(&attr) {
error::inner_attr(ctx);
}
match attr.interpret_meta() {
Some(Meta::List(list)) => return list.nested.into_iter()
.filter_map(|nm| extract_metas(ctx, nm)).collect(),
Some(Meta::Word(_)) => error::bare_proptest_attr(ctx),
Some(Meta::NameValue(_)) => error::literal_set_proptest(ctx),
None => error::no_interp_meta(ctx),
}
vec![]
}
fn extract_metas(ctx: Ctx, nested: NestedMeta) -> Option<Meta> {
match nested {
NestedMeta::Literal(_) => {
error::immediate_literals(ctx);
None
},
NestedMeta::Meta(meta) => Some(meta),
}
}
fn is_outer_attr(attr: &Attribute) -> bool {
syn::AttrStyle::Outer == attr.style
}
fn dispatch_attribute(ctx: Ctx, mut acc: ParseAcc, meta: Meta) -> ParseAcc {
let name = meta.name().to_string();
let name = name.as_ref();
match name {
"skip" => parse_skip(ctx, &mut acc, meta),
"w" | "weight" => parse_weight(ctx, &mut acc, &meta),
"no_params" => parse_no_params(ctx, &mut acc, meta),
"params" => parse_params(ctx, &mut acc, meta),
"strategy" => parse_strategy(ctx, &mut acc, &meta),
"value" => parse_value(ctx, &mut acc, &meta),
"regex" => parse_regex(ctx, &mut acc, &meta),
"filter" => parse_filter(ctx, &mut acc, &meta),
"no_bound" => parse_no_bound(ctx, &mut acc, meta),
name => dispatch_unknown_mod(ctx, name),
}
acc
}
fn dispatch_unknown_mod(ctx: Ctx, name: &str) {
match name {
"no_bounds" =>
error::did_you_mean(ctx, name, "no_bound"),
"weights" | "weighted" =>
error::did_you_mean(ctx, name, "weight"),
"strat" | "strategies" =>
error::did_you_mean(ctx, name, "strategy"),
"values" | "valued" | "fix" | "fixed" =>
error::did_you_mean(ctx, name, "value"),
"regexes" | "regexp" | "re" =>
error::did_you_mean(ctx, name, "regex"),
"param" | "parameters" =>
error::did_you_mean(ctx, name, "params"),
"no_param" | "no_parameters" =>
error::did_you_mean(ctx, name, "no_params"),
name =>
error::unkown_modifier(ctx, name),
}
}
fn parse_no_bound(ctx: Ctx, acc: &mut ParseAcc, meta: Meta) {
parse_bare_modifier(ctx, &mut acc.no_bound, meta, error::no_bound_malformed)
}
fn parse_skip(ctx: Ctx, acc: &mut ParseAcc, meta: Meta) {
parse_bare_modifier(ctx, &mut acc.skip, meta, error::skip_malformed)
}
fn parse_weight(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
use std::u32;
error_if_set(ctx, &acc.weight, &meta);
let value = normalize_meta(meta.clone())
.and_then(extract_lit)
.and_then(extract_expr)
.as_ref().and_then(interp::eval_expr)
.filter(|&value| value <= u128::from(u32::MAX))
.map(|value| value as u32);
if let v@Some(_) = value {
acc.weight = v;
} else {
error::weight_malformed(ctx, meta)
}
}
fn parse_filter(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
if let Some(filter) = match normalize_meta(meta.clone()) {
Some(NormMeta::Lit(Lit::Str(lit))) => lit.parse().ok(),
Some(NormMeta::Word(ident)) => Some(parse_quote!( #ident )),
_ => None,
} {
acc.filter.push(filter);
} else {
error::filter_malformed(ctx, meta)
}
}
fn parse_regex(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
error_if_set(ctx, &acc.regex, &meta);
if let expr@Some(_) = match normalize_meta(meta.clone()) {
Some(NormMeta::Word(fun)) => Some(function_call(fun)),
Some(NormMeta::Lit(lit@Lit::Str(_))) => Some(lit_to_expr(lit)),
_ => None,
} {
acc.regex = expr;
} else {
error::regex_malformed(ctx)
}
}
fn parse_value(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
parse_strategy_base(ctx, &mut acc.value, meta)
}
fn parse_strategy(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
parse_strategy_base(ctx, &mut acc.strategy, meta)
}
fn parse_strategy_base(ctx: Ctx, loc: &mut Option<Expr>, meta: &Meta) {
error_if_set(ctx, &loc, &meta);
if let expr@Some(_) = match normalize_meta(meta.clone()) {
Some(NormMeta::Word(fun)) => Some(function_call(fun)),
Some(NormMeta::Lit(lit)) => extract_expr(lit),
_ => None,
} {
*loc = expr;
} else {
error::strategy_malformed(ctx, meta)
}
}
fn parse_strat_mode
(ctx: Ctx, strat: Option<Expr>, value: Option<Expr>, regex: Option<Expr>)
-> DeriveResult<StratMode>
{
Ok(match (strat, value, regex) {
(None, None, None ) => StratMode::Arbitrary,
(None, None, Some(re)) => StratMode::Regex(re),
(None, Some(vl), None ) => StratMode::Value(vl),
(Some(st), None, None ) => StratMode::Strategy(st),
_ => error::overspecified_strat(ctx)?,
})
}
fn parse_params_mode(ctx: Ctx, no_params: Option<()>, ty_params: Option<Type>)
-> DeriveResult<ParamsMode>
{
Ok(match (no_params, ty_params) {
(None, None ) => ParamsMode::Passthrough,
(None, Some(ty)) => ParamsMode::Specified(ty),
(Some(_), None ) => ParamsMode::Default,
(Some(_), Some(_) ) => error::overspecified_param(ctx)?,
})
}
fn parse_params(ctx: Ctx, acc: &mut ParseAcc, meta: Meta) {
error_if_set(ctx, &acc.params, &meta);
let typ = match normalize_meta(meta) {
Some(NormMeta::Word(ident)) => Some(ident_to_type(ident)),
Some(NormMeta::Lit(Lit::Str(lit))) => lit.parse().ok(),
_ => None,
};
if let typ@Some(_) = typ {
acc.params = typ;
} else {
error::param_malformed(ctx)
}
}
fn parse_no_params(ctx: Ctx, acc: &mut ParseAcc, meta: Meta) {
parse_bare_modifier(ctx, &mut acc.no_params, meta, error::no_params_malformed)
}
fn parse_bare_modifier
(ctx: Ctx, loc: &mut Option<()>, meta: Meta, malformed: fn(Ctx))
{
error_if_set(ctx, loc, &meta);
if let Some(NormMeta::Plain) = normalize_meta(meta) {
*loc = Some(());
} else {
malformed(ctx);
}
}
fn error_if_set<T>(ctx: Ctx, loc: &Option<T>, meta: &Meta) {
if loc.is_some() {
error::set_again(ctx, meta)
}
}
fn ident_to_type(ident: Ident) -> Type {
Type::Path(syn::TypePath { qself: None, path: ident.into() })
}
fn extract_lit(meta: NormMeta) -> Option<Lit> {
if let NormMeta::Lit(lit) = meta {
Some(lit)
} else {
None
}
}
fn extract_expr(lit: Lit) -> Option<Expr> {
match lit {
Lit::Str(lit) => lit.parse().ok(),
lit@Lit::Int(_) => Some(lit_to_expr(lit)),
_ => None,
}
}
fn lit_to_expr(lit: Lit) -> Expr {
syn::ExprLit { attrs: vec![], lit }.into()
}
fn function_call(fun: Ident) -> Expr {
parse_quote!( #fun() )
}
#[derive(Debug)]
enum NormMeta {
Plain,
Lit(Lit),
Word(Ident)
}
fn normalize_meta(meta: Meta) -> Option<NormMeta> {
Some(match meta {
Meta::Word(_) => NormMeta::Plain,
Meta::NameValue(nv) => NormMeta::Lit(nv.lit),
Meta::List(ml) => if let Some(nm) = util::match_singleton(ml.nested) {
match nm {
NestedMeta::Literal(lit) => NormMeta::Lit(lit),
NestedMeta::Meta(Meta::Word(word)) => NormMeta::Word(word),
_ => return None
}
} else {
return None
},
})
}