proptest_derive/
attr.rs

1// Copyright 2018 The proptest developers
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Provides a parser from syn attributes to our logical model.
10
11use quote::ToTokens;
12use syn::parse::Parser;
13use syn::punctuated::Punctuated;
14use syn::{self, Attribute, Expr, Ident, Lit, Meta, Type};
15
16use crate::error::{self, Ctx, DeriveResult};
17use crate::interp;
18use crate::util;
19
20//==============================================================================
21// Public API
22//==============================================================================
23
24/// Parsed attributes in our logical model.
25#[derive(Clone)]
26pub struct ParsedAttributes {
27    /// If we've been ordered to skip this item.
28    /// This is only valid for enum variants.
29    pub skip: bool,
30    /// The potential weight assigned to an enum variant.
31    /// This must be `None` for things that are not enum variants.
32    pub weight: Option<u32>,
33    /// The mode for `Parameters` to use. See that type for more.
34    pub params: ParamsMode,
35    /// The mode for `Strategy` to use. See that type for more.
36    pub strategy: StratMode,
37    /// Filter expressions if any.
38    pub filter: Vec<syn::Expr>,
39    /// True if no_bound was specified.
40    pub no_bound: bool,
41}
42
43/// The mode for the associated item `Strategy` to use.
44#[derive(Clone)]
45pub enum StratMode {
46    /// This means that no explicit strategy was specified
47    /// and that we thus should use `Arbitrary` for whatever
48    /// it is that needs a strategy.
49    Arbitrary,
50    /// This means that an explicit value has been provided.
51    /// The result of this is to use a strategy that always
52    /// returns the given value.
53    Value(Expr),
54    /// This means that an explicit strategy has been provided.
55    /// This strategy will be used to generate whatever it
56    /// is that the attribute was set on.
57    Strategy(Expr),
58    /// This means that an explicit *regex* strategy has been provided.
59    /// We don't reuse `Strategy(..)` so that we can produce better and
60    /// more tailored error messages.
61    Regex(Expr),
62}
63
64/// The mode for the associated item `Parameters` to use.
65#[derive(Clone)]
66pub enum ParamsMode {
67    /// Nothing has been specified. The children are now free to
68    /// specify their parameters, and if nothing is specified, then
69    /// `<X as Arbitrary>::Parameters` will be used for a type `X`.
70    Passthrough,
71    /// We've been ordered to use the Default value of
72    /// `<X as Arbitrary>::Parameters` for some field where applicable.
73    /// For the top level item, this means that `Parameters` will be
74    /// the unit type. For children, it means that this child should
75    /// not count towards the product type that is being built up.
76    Default,
77    /// An explicit type has been specified on some item.
78    /// If the top level item has this specified on it, this means
79    /// that `Parameters` will have the given type.
80    /// If it is specified on a child of the top level item, this
81    /// entails that the given type will be added to the resultant
82    /// product type.
83    Specified(Type),
84}
85
86impl ParamsMode {
87    /// Returns `true` iff the mode was explicitly set.
88    pub fn is_set(&self) -> bool {
89        if let ParamsMode::Passthrough = *self {
90            false
91        } else {
92            true
93        }
94    }
95
96    /// Converts the mode to an `Option` of an `Option` of a type
97    /// where the outer `Option` is `None` iff the mode wasn't set
98    /// and the inner `Option` is `None` iff the mode was `Default`.
99    pub fn into_option(self) -> Option<Option<Type>> {
100        use self::ParamsMode::*;
101        match self {
102            Passthrough => None,
103            Specified(ty) => Some(Some(ty)),
104            Default => Some(None),
105        }
106    }
107}
108
109impl StratMode {
110    /// Returns `true` iff the mode was explicitly set.
111    pub fn is_set(&self) -> bool {
112        if let StratMode::Arbitrary = self {
113            false
114        } else {
115            true
116        }
117    }
118}
119
120/// Parse the attributes specified on an item and parsed by syn
121/// into our logical model that we work with.
122pub fn parse_attributes(
123    ctx: Ctx,
124    attrs: &[Attribute],
125) -> DeriveResult<ParsedAttributes> {
126    let attrs = parse_attributes_base(ctx, attrs)?;
127    if attrs.no_bound {
128        error::no_bound_set_on_non_tyvar(ctx);
129    }
130    Ok(attrs)
131}
132
133/// Parse the attributes specified on a type definition...
134pub fn parse_top_attributes(
135    ctx: Ctx,
136    attrs: &[Attribute],
137) -> DeriveResult<ParsedAttributes> {
138    parse_attributes_base(ctx, attrs)
139}
140
141/// Parses the attributes specified on an item and parsed by syn
142/// and returns true if we've been ordered to not set an `Arbitrary`
143/// bound on the given type variable the attributes are from,
144/// no matter what.
145pub fn has_no_bound(ctx: Ctx, attrs: &[Attribute]) -> DeriveResult<bool> {
146    let attrs = parse_attributes_base(ctx, attrs)?;
147    error::if_anything_specified(ctx, &attrs, error::TY_VAR);
148    Ok(attrs.no_bound)
149}
150
151/// Parse the attributes specified on an item and parsed by syn
152/// into our logical model that we work with.
153fn parse_attributes_base(
154    ctx: Ctx,
155    attrs: &[Attribute],
156) -> DeriveResult<ParsedAttributes> {
157    let acc = parse_accumulate(ctx, attrs);
158
159    Ok(ParsedAttributes {
160        skip: acc.skip.is_some(),
161        weight: acc.weight,
162        filter: acc.filter,
163        // Process params and no_params together to see which one to use.
164        params: parse_params_mode(ctx, acc.no_params, acc.params)?,
165        // Process strategy and value together to see which one to use.
166        strategy: parse_strat_mode(ctx, acc.strategy, acc.value, acc.regex)?,
167        no_bound: acc.no_bound.is_some(),
168    })
169}
170
171//==============================================================================
172// Internals: Initialization
173//==============================================================================
174
175/// The internal state of the attribute parser.
176#[derive(Default)]
177struct ParseAcc {
178    skip: Option<()>,
179    weight: Option<u32>,
180    no_params: Option<()>,
181    params: Option<Type>,
182    strategy: Option<Expr>,
183    value: Option<Expr>,
184    regex: Option<Expr>,
185    filter: Vec<Expr>,
186    no_bound: Option<()>,
187}
188
189//==============================================================================
190// Internals: Extraction & Filtering
191//==============================================================================
192
193fn parse_accumulate(ctx: Ctx, attrs: &[Attribute]) -> ParseAcc {
194    let mut state = ParseAcc::default();
195
196    // Get rid of attributes we don't care about:
197    for attr in attrs {
198        if is_proptest_attr(&attr) {
199            // Flatten attributes so we deal with them uniformly.
200            state = extract_modifiers(ctx, &attr)
201                .into_iter()
202                // Accumulate attributes into a form for final processing.
203                .fold(state, |state, meta| dispatch_attribute(ctx, state, meta))
204        }
205    }
206
207    state
208}
209
210/// Returns `true` iff the attribute has to do with proptest.
211/// Otherwise, the attribute is irrevant to us and we will simply
212/// ignore it in our processing.
213fn is_proptest_attr(attr: &Attribute) -> bool {
214    util::eq_simple_path("proptest", attr.path())
215}
216
217/// Extract all individual attributes inside one `#[proptest(..)]`.
218/// We do this to treat all pieces uniformly whether a single
219/// `#[proptest(..)]` was used or many. This simplifies the
220/// logic somewhat.
221fn extract_modifiers(ctx: Ctx, attr: &Attribute) -> Vec<Meta> {
222    // Ensure we've been given an outer attribute form.
223    if !is_outer_attr(&attr) {
224        error::inner_attr(ctx);
225    }
226
227    match &attr.meta {
228        Meta::List(list) => {
229            if syn::parse2::<Lit>(list.tokens.clone()).is_ok() {
230                error::immediate_literals(ctx);
231            } else {
232                let parser = Punctuated::<Meta, Token![,]>::parse_separated_nonempty;
233                let metas = parser.parse2(list.tokens.clone()).unwrap();
234                return metas.into_iter().collect();
235            }
236        }
237        Meta::Path(_) => error::bare_proptest_attr(ctx),
238        Meta::NameValue(_) => error::literal_set_proptest(ctx),
239    }
240
241    vec![]
242}
243
244/// Returns true iff the given attribute is an outer one, i.e: `#[<attr>]`.
245/// An inner attribute is the other possibility and has the syntax `#![<attr>]`.
246/// Note that `<attr>` is a meta-variable for the contents inside.
247fn is_outer_attr(attr: &Attribute) -> bool {
248    syn::AttrStyle::Outer == attr.style
249}
250
251//==============================================================================
252// Internals: Dispatch
253//==============================================================================
254
255/// Dispatches an attribute modifier to handlers and
256/// let's them add stuff into our accumulartor.
257fn dispatch_attribute(ctx: Ctx, mut acc: ParseAcc, meta: Meta) -> ParseAcc {
258    // Dispatch table for attributes:
259    let path = meta.path();
260    if let Some(name) = path.get_ident().map(ToString::to_string) {
261        match name.as_ref() {
262            // Valid modifiers:
263            "skip" => parse_skip(ctx, &mut acc, meta),
264            "w" | "weight" => parse_weight(ctx, &mut acc, &meta),
265            "no_params" => parse_no_params(ctx, &mut acc, meta),
266            "params" => parse_params(ctx, &mut acc, meta),
267            "strategy" => parse_strategy(ctx, &mut acc, &meta),
268            "value" => parse_value(ctx, &mut acc, &meta),
269            "regex" => parse_regex(ctx, &mut acc, &meta),
270            "filter" => parse_filter(ctx, &mut acc, &meta),
271            "no_bound" => parse_no_bound(ctx, &mut acc, meta),
272            // Invalid modifiers:
273            name => dispatch_unknown_mod(ctx, name),
274        }
275    } else {
276        // Occurs when passed path is something other than a single ident
277        error::unkown_modifier(ctx, &path.into_token_stream().to_string());
278    }
279    acc
280}
281
282fn dispatch_unknown_mod(ctx: Ctx, name: &str) {
283    match name {
284        "no_bounds" => error::did_you_mean(ctx, name, "no_bound"),
285        "weights" | "weighted" => error::did_you_mean(ctx, name, "weight"),
286        "strat" | "strategies" => error::did_you_mean(ctx, name, "strategy"),
287        "values" | "valued" | "fix" | "fixed" => {
288            error::did_you_mean(ctx, name, "value")
289        }
290        "regexes" | "regexp" | "re" => error::did_you_mean(ctx, name, "regex"),
291        "param" | "parameters" => error::did_you_mean(ctx, name, "params"),
292        "no_param" | "no_parameters" => {
293            error::did_you_mean(ctx, name, "no_params")
294        }
295        name => error::unkown_modifier(ctx, name),
296        // TODO: consider levenshtein distance.
297    }
298}
299
300//==============================================================================
301// Internals: no_bound
302//==============================================================================
303
304/// Parse a no_bound attribute.
305/// Valid forms are:
306/// + `#[proptest(no_bound)]`
307fn parse_no_bound(ctx: Ctx, acc: &mut ParseAcc, meta: Meta) {
308    parse_bare_modifier(ctx, &mut acc.no_bound, meta, error::no_bound_malformed)
309}
310
311//==============================================================================
312// Internals: Skip
313//==============================================================================
314
315/// Parse a skip attribute.
316/// Valid forms are:
317/// + `#[proptest(skip)]`
318fn parse_skip(ctx: Ctx, acc: &mut ParseAcc, meta: Meta) {
319    parse_bare_modifier(ctx, &mut acc.skip, meta, error::skip_malformed)
320}
321
322//==============================================================================
323// Internals: Weight
324//==============================================================================
325
326/// Parses a weight.
327/// Valid forms are:
328/// + `#[proptest(weight = <integer>)]`
329/// + `#[proptest(weight = "<expr>")]`
330/// + `#[proptest(weight(<integer>))]`
331/// + `#[proptest(weight("<expr>""))]`
332///
333/// The `<integer>` must also fit within an `u32` and be unsigned.
334fn parse_weight(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
335    use std::u32;
336    error_if_set(ctx, &acc.weight, &meta);
337
338    // Convert to value if possible:
339    let value = normalize_meta(meta.clone())
340        .and_then(extract_lit)
341        .and_then(extract_expr)
342        // Evaluate the expression into a value:
343        .as_ref()
344        .and_then(interp::eval_expr)
345        // Ensure that `val` fits within an `u32` as proptest requires that:
346        .filter(|&value| value <= u128::from(u32::MAX))
347        .map(|value| value as u32);
348
349    if let v @ Some(_) = value {
350        acc.weight = v;
351    } else {
352        error::weight_malformed(ctx, meta)
353    }
354}
355
356//==============================================================================
357// Internals: Filter
358//==============================================================================
359
360/// Parses an explicit value as a strategy.
361/// Valid forms are:
362/// + `#[proptest(filter(<ident>))]`
363/// + `#[proptest(filter = "<expr>")]`
364/// + `#[proptest(filter("<expr>")]`
365fn parse_filter(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
366    if let Some(filter) = match normalize_meta(meta.clone()) {
367        Some(NormMeta::Lit(Lit::Str(lit))) => lit.parse().ok(),
368        Some(NormMeta::Word(ident)) => Some(parse_quote!( #ident )),
369        _ => None,
370    } {
371        acc.filter.push(filter);
372    } else {
373        error::filter_malformed(ctx, meta)
374    }
375}
376
377//==============================================================================
378// Internals: Strategy
379//==============================================================================
380
381/// Parses an explicit value as a strategy.
382/// Valid forms are:
383/// + `#[proptest(regex = "<string>")]`
384/// + `#[proptest(regex("<string>")]`
385/// + `#[proptest(regex(<ident>)]`
386fn parse_regex(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
387    error_if_set(ctx, &acc.regex, &meta);
388
389    if let expr @ Some(_) = match normalize_meta(meta.clone()) {
390        Some(NormMeta::Word(fun)) => Some(function_call(fun)),
391        Some(NormMeta::Lit(lit @ Lit::Str(_))) => Some(lit_to_expr(lit)),
392        _ => None,
393    } {
394        acc.regex = expr;
395    } else {
396        error::regex_malformed(ctx)
397    }
398}
399
400/// Parses an explicit value as a strategy.
401/// Valid forms are:
402/// + `#[proptest(value = <literal>)]`
403/// + `#[proptest(value = "<expr>")]`
404/// + `#[proptest(value("<expr>")]`
405/// + `#[proptest(value(<literal>)]`
406/// + `#[proptest(value(<ident>)]`
407fn parse_value(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
408    parse_strategy_base(ctx, &mut acc.value, meta)
409}
410
411/// Parses an explicit strategy.
412/// Valid forms are:
413/// + `#[proptest(strategy = <literal>)]`
414/// + `#[proptest(strategy = "<expr>")]`
415/// + `#[proptest(strategy("<expr>")]`
416/// + `#[proptest(strategy(<literal>)]`
417/// + `#[proptest(strategy(<ident>)]`
418fn parse_strategy(ctx: Ctx, acc: &mut ParseAcc, meta: &Meta) {
419    parse_strategy_base(ctx, &mut acc.strategy, meta)
420}
421
422/// Parses an explicit strategy. This is a helper.
423/// Valid forms are:
424/// + `#[proptest(<meta.name()> = <literal>)]`
425/// + `#[proptest(<meta.name()> = "<expr>")]`
426/// + `#[proptest(<meta.name()>("<expr>")]`
427/// + `#[proptest(<meta.name()>(<literal>)]`
428/// + `#[proptest(<meta.name()>(<ident>)]`
429fn parse_strategy_base(ctx: Ctx, loc: &mut Option<Expr>, meta: &Meta) {
430    error_if_set(ctx, &loc, &meta);
431
432    if let expr @ Some(_) = match normalize_meta(meta.clone()) {
433        Some(NormMeta::Word(fun)) => Some(function_call(fun)),
434        Some(NormMeta::Lit(lit)) => extract_expr(lit),
435        _ => None,
436    } {
437        *loc = expr;
438    } else {
439        error::strategy_malformed(ctx, meta)
440    }
441}
442
443/// Combines any parsed explicit strategy, value, and regex into a single
444/// value and fails if both an explicit strategy / value / regex was set.
445/// Only one of them can be set, or none.
446fn parse_strat_mode(
447    ctx: Ctx,
448    strat: Option<Expr>,
449    value: Option<Expr>,
450    regex: Option<Expr>,
451) -> DeriveResult<StratMode> {
452    Ok(match (strat, value, regex) {
453        (None, None, None) => StratMode::Arbitrary,
454        (None, None, Some(re)) => StratMode::Regex(re),
455        (None, Some(vl), None) => StratMode::Value(vl),
456        (Some(st), None, None) => StratMode::Strategy(st),
457        _ => error::overspecified_strat(ctx)?,
458    })
459}
460
461//==============================================================================
462// Internals: Parameters
463//==============================================================================
464
465/// Combines a potentially set `params` and `no_params` into a single value
466/// and fails if both have been set. Only one of them can be set, or none.
467fn parse_params_mode(
468    ctx: Ctx,
469    no_params: Option<()>,
470    ty_params: Option<Type>,
471) -> DeriveResult<ParamsMode> {
472    Ok(match (no_params, ty_params) {
473        (None, None) => ParamsMode::Passthrough,
474        (None, Some(ty)) => ParamsMode::Specified(ty),
475        (Some(_), None) => ParamsMode::Default,
476        (Some(_), Some(_)) => error::overspecified_param(ctx)?,
477    })
478}
479
480/// Parses an explicit Parameters type.
481///
482/// Valid forms are:
483/// + `#[proptest(params(<type>)]`
484/// + `#[proptest(params("<type>")]`
485/// + `#[proptest(params = "<type>"]`
486///
487/// The latter form is required for more complex types.
488fn parse_params(ctx: Ctx, acc: &mut ParseAcc, meta: Meta) {
489    error_if_set(ctx, &acc.params, &meta);
490
491    let typ = match normalize_meta(meta) {
492        // Form is: `#[proptest(params(<type>)]`.
493        Some(NormMeta::Word(ident)) => Some(ident_to_type(ident)),
494        // Form is: `#[proptest(params = "<type>"]` or,
495        // Form is: `#[proptest(params("<type>")]`..
496        Some(NormMeta::Lit(Lit::Str(lit))) => lit.parse().ok(),
497        _ => None,
498    };
499
500    if let typ @ Some(_) = typ {
501        acc.params = typ;
502    } else {
503        error::param_malformed(ctx)
504    }
505}
506
507/// Parses an order to use the default Parameters type and value.
508/// Valid forms are:
509/// + `#[proptest(no_params)]`
510fn parse_no_params(ctx: Ctx, acc: &mut ParseAcc, meta: Meta) {
511    parse_bare_modifier(
512        ctx,
513        &mut acc.no_params,
514        meta,
515        error::no_params_malformed,
516    )
517}
518
519//==============================================================================
520// Internals: Utilities
521//==============================================================================
522
523/// Parses a bare attribute of the form `#[proptest(<attr>)]` and sets `loc`.
524fn parse_bare_modifier(
525    ctx: Ctx,
526    loc: &mut Option<()>,
527    meta: Meta,
528    malformed: fn(Ctx),
529) {
530    error_if_set(ctx, loc, &meta);
531
532    if let Some(NormMeta::Plain) = normalize_meta(meta) {
533        *loc = Some(());
534    } else {
535        malformed(ctx);
536    }
537}
538
539/// Emits a "set again" error iff the given option `.is_some()`.
540fn error_if_set<T>(ctx: Ctx, loc: &Option<T>, meta: &Meta) {
541    if loc.is_some() {
542        error::set_again(ctx, meta)
543    }
544}
545
546/// Constructs a type out of an identifier.
547fn ident_to_type(ident: Ident) -> Type {
548    Type::Path(syn::TypePath {
549        qself: None,
550        path: ident.into(),
551    })
552}
553
554/// Extract a `lit` in `NormMeta::Lit(<lit>)`.
555fn extract_lit(meta: NormMeta) -> Option<Lit> {
556    if let NormMeta::Lit(lit) = meta {
557        Some(lit)
558    } else {
559        None
560    }
561}
562
563/// Extract expression out of literal if possible.
564fn extract_expr(lit: Lit) -> Option<Expr> {
565    match lit {
566        Lit::Str(lit) => lit.parse().ok(),
567        lit @ Lit::Int(_) => Some(lit_to_expr(lit)),
568        // TODO(centril): generalize to other literals, e.g. floats
569        _ => None,
570    }
571}
572
573/// Construct an expression from a literal.
574fn lit_to_expr(lit: Lit) -> Expr {
575    syn::ExprLit { attrs: vec![], lit }.into()
576}
577
578/// Construct a function call expression for an identifier.
579fn function_call(fun: Ident) -> Expr {
580    parse_quote!( #fun() )
581}
582
583/// Normalized `Meta` into all the forms we will possibly accept.
584#[derive(Debug)]
585enum NormMeta {
586    /// Accepts: `#[proptest(<word>)]`
587    Plain,
588    /// Accepts: `#[proptest(<word> = <lit>)]` and `#[proptest(<word>(<lit>))]`
589    Lit(Lit),
590    /// Accepts: `#[proptest(<word>(<word>))`.
591    Word(Ident),
592}
593
594/// Normalize a `meta: Meta` into the forms accepted in `#[proptest(<meta>)]`.
595fn normalize_meta(meta: Meta) -> Option<NormMeta> {
596    match meta {
597        Meta::Path(_) => Some(NormMeta::Plain),
598        Meta::NameValue(nv) => match nv.value {
599            Expr::Lit(elit) => Some(NormMeta::Lit(elit.lit)),
600            _ => None,
601        }
602        Meta::List(ml) => {
603            let mut output: Option<NormMeta> = None;
604
605            if let Ok(lit) = syn::parse2(ml.tokens.clone()) {
606                output = Some(NormMeta::Lit(lit));
607            } else if let Ok(ident) = syn::parse2(ml.tokens.clone()) {
608                output = Some(NormMeta::Word(ident));
609            }
610
611            output
612        }
613    }
614}