blob: 3f018813e3493786a04b7d8cab2f9a32ab21d640 [file] [log] [blame]
// 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.
//! Provides error messages and some checkers.
use std::fmt::Display;
use proc_macro2::TokenStream;
use syn;
use crate::attr::ParsedAttributes;
//==============================================================================
// Item descriptions
//==============================================================================
/// Item name of structs.
pub const STRUCT: &str = "struct";
/// Item name of struct fields.
pub const STRUCT_FIELD: &str = "struct field";
/// Item name of enums.
pub const ENUM: &str = "enum";
/// Item name of enum variants.
pub const ENUM_VARIANT: &str = "enum variant";
/// Item name of enum variant fields.
pub const ENUM_VARIANT_FIELD: &str = "enum variant field";
/// Item name for a type variable.
pub const TY_VAR: &str = "a type variable";
//==============================================================================
// Checkers
//==============================================================================
/// Ensures that the type is not parametric over lifetimes.
pub fn if_has_lifetimes(ctx: Ctx, ast: &syn::DeriveInput) {
if ast.generics.lifetimes().count() > 0 {
has_lifetimes(ctx);
}
}
/// Ensures that no attributes were specified on `item`.
pub fn if_anything_specified(ctx: Ctx, attrs: &ParsedAttributes, item: &str) {
if_enum_attrs_present(ctx, attrs, item);
if_strategy_present(ctx, attrs, item);
if_specified_params(ctx, attrs, item);
if_specified_filter(ctx, attrs, item);
}
/// Ensures that things only allowed on an enum variant is not present on
/// `item` which is not an enum variant.
pub fn if_enum_attrs_present(ctx: Ctx, attrs: &ParsedAttributes, item: &str) {
if_skip_present(ctx, attrs, item);
if_weight_present(ctx, attrs, item);
}
/// Ensures that parameters is not present on `item`.
pub fn if_specified_filter(ctx: Ctx, attrs: &ParsedAttributes, item: &str) {
if !attrs.filter.is_empty() { meaningless_filter(ctx, item); }
}
/// Ensures that parameters is not present on `item`.
pub fn if_specified_params(ctx: Ctx, attrs: &ParsedAttributes, item: &str) {
if attrs.params.is_set() { parent_has_param(ctx, item); }
}
/// Ensures that an explicit strategy or value is not present on `item`.
pub fn if_strategy_present(ctx: Ctx, attrs: &ParsedAttributes, item: &str) {
use crate::attr::StratMode::*;
match attrs.strategy {
Arbitrary => {},
Strategy(_) => illegal_strategy(ctx, "strategy", item),
Value(_) => illegal_strategy(ctx, "value", item),
Regex(_) => illegal_regex(ctx, item),
}
}
/// Ensures that a strategy, value, params, filter is not present on a unit variant.
pub fn if_present_on_unit_variant(ctx: Ctx, attrs: &ParsedAttributes) {
/// Ensures that an explicit strategy or value is not present on a unit variant.
use crate::attr::StratMode::*;
match attrs.strategy {
Arbitrary => {},
Strategy(_) => strategy_on_unit_variant(ctx, "strategy"),
Value(_) => strategy_on_unit_variant(ctx, "value"),
Regex(_) => regex_on_unit_variant(ctx),
}
if attrs.params.is_set() {
params_on_unit_variant(ctx)
}
if !attrs.filter.is_empty() {
filter_on_unit_variant(ctx)
}
}
/// Ensures that parameters or filter is not present on a unit struct.
pub fn if_present_on_unit_struct(ctx: Ctx, attrs: &ParsedAttributes) {
if attrs.params.is_set() {
params_on_unit_struct(ctx)
}
if !attrs.filter.is_empty() {
filter_on_unit_struct(ctx)
}
}
/// Ensures that skip is not present on `item`.
pub fn if_skip_present(ctx: Ctx, attrs: &ParsedAttributes, item: &str) {
if attrs.skip {
illegal_skip(ctx, item)
}
}
/// Ensures that a weight is not present on `item`.
pub fn if_weight_present(ctx: Ctx, attrs: &ParsedAttributes, item: &str) {
if attrs.weight.is_some() {
illegal_weight(ctx, item)
}
}
//==============================================================================
// Messages
//==============================================================================
/// Denotes that a fatal error happened in dealing somewhere in the
/// procedural macro pipeline. A fatal error is different from a
/// normal error in the sense that it halts progress in the macro
/// immediately instead of allowing other errors to be accumulated.
#[derive(Debug)]
pub struct Fatal;
/// The return type of a possibly fatal computation in the macro.
pub type DeriveResult<T> = Result<T, Fatal>;
/// A mutable view / shorthand for the context.
/// Prefer this type over `Context` in functions.
pub type Ctx<'ctx> = &'ctx mut Context;
/// The context / environment that the macro is operating in.
/// Right now, it simply tracks all the errors collected during
/// the running of the macro.
#[derive(Default)]
pub struct Context {
errors: Vec<String>,
}
impl Context {
/// Add a non-fatal error to the context.
pub fn error<T: Display>(&mut self, msg: T) {
self.errors.push(msg.to_string());
}
/// Add an error to the context and procuce and produce an erroring
/// computation that will halt the macro.
pub fn fatal<T: Display, A>(&mut self, msg: T) -> DeriveResult<A> {
self.error(msg);
Err(Fatal)
}
/// Consume the context and if there were any errors,
/// emit `compile_error!(..)` such that the crate using
/// `#[derive(Arbitrary)]` will fail to compile.
pub fn check(mut self) -> Result<(), TokenStream> {
fn compile_error(msg: &str) -> TokenStream {
quote! {
compile_error!(#msg);
}
}
match self.errors.len() {
0 => Ok(()),
1 => Err(compile_error(&self.errors.pop().unwrap())),
n => {
let mut msg = format!("{} errors:", n);
for err in self.errors {
msg.push_str("\n\t# ");
msg.push_str(&err);
}
Err(compile_error(&msg))
}
}
}
}
//==============================================================================
// Messages
//==============================================================================
/// Produce an error string with the error `$code` which corresponds
/// to the given `$message`.
macro_rules! mk_err_msg {
($code: ident, $msg: expr) => {
concat!(
"[proptest_derive, ", stringify!($code), "]",
" during #[derive(Arbitrary)]:\n",
$msg,
" Please see: https://PATH/TO/foo#", stringify!($code),
" for more information.")
}
}
/// A macro constructing errors that do halt compilation immediately.
macro_rules! fatal {
($error: ident, $code: ident, $msg: expr) => {
pub fn $error<T>(ctx: Ctx) -> DeriveResult<T> {
ctx.fatal(mk_err_msg!($code, $msg))
}
};
($error: ident ($($arg: ident: $arg_ty: ty),*), $code: ident,
$msg: expr, $($fmt: tt)+) => {
pub fn $error<T>(ctx: Ctx, $($arg: $arg_ty),*) -> DeriveResult<T> {
ctx.fatal(format!(mk_err_msg!($code, $msg), $($fmt)+))
}
};
}
/// A macro constructing fatal errors that do not halt compilation immediately.
macro_rules! error {
($error: ident, $code: ident, $msg: expr) => {
pub fn $error(ctx: Ctx) {
ctx.error(mk_err_msg!($code, $msg))
}
};
($error: ident ($($arg: ident: $arg_ty: ty),*), $code: ident,
$msg: expr, $($fmt: tt)+) => {
pub fn $error(ctx: Ctx, $($arg: $arg_ty),*) {
ctx.error(format!(mk_err_msg!($code, $msg), $($fmt)+))
}
};
}
// Happens when we've been asked to derive `Arbitrary` for a type
// that is parametric over lifetimes. Since proptest does not support
// such types (yet), neither can we.
error!(has_lifetimes, E0001,
"Cannot derive `Arbitrary` for types with generic lifetimes, such as: \
`struct Foo<'a> { bar: &'a str }`. Currently, strategies for such types \
are impossible to define.");
// Happens when we've been asked to derive `Arbitrary` for something
// that is neither an enum nor a struct. Most likely, we've been given
// a union type. This might be supported in the future, but not yet.
fatal!(not_struct_or_enum, E0002,
"Deriving is only possible for structs and enums. \
It is currently not defined unions.");
// Happens when a struct has at least one field that is uninhabited.
// There must at least exist one variant that we can construct.
error!(uninhabited_struct, E0003,
"The struct you are deriving `Arbitrary` for is uninhabited since one of \
its fields is uninhabited. An uninhabited type is by definition impossible \
to generate.");
// Happens when an enum has zero variants. Such an enum is obviously
// uninhabited and can not be constructed. There must at least exist
// one variant that we can construct.
fatal!(uninhabited_enum_with_no_variants, E0004,
"The enum you are deriving `Arbitrary` for is uninhabited since it has no \
variants. An example of such an `enum` is: `enum Void {}`. \
An uninhabited type is by definition impossible to generate.");
// Happens when an enum is uninhabited due all its variants being
// uninhabited (why has the user given us such a weird enum?..
// Nonetheless, we do our best to ensure soundness).
// There must at least exist one variant that we can construct.
fatal!(uninhabited_enum_variants_uninhabited, E0005,
"The enum you are deriving `Arbitrary` for is uninhabited since all its \
variants are uninhabited. \
An uninhabited type is by definition impossible to generate.");
// Happens when an enum becomes effectively uninhabited due
// to all inhabited variants having been skipped. There must
// at least exist one variant that we can construct.
error!(uninhabited_enum_because_of_skipped_variants, E0006,
"The enum you are deriving `Arbitrary` for is uninhabited for all intents \
and purposes since you have `#[proptest(skip)]`ed all inhabited variants. \
An uninhabited type is by definition impossible to generate.");
// Happens when `#[proptest(strategy = "<expr>")]` or
// `#[proptest(value = "<expr>")]` is specified on an `item`
// that does not support setting an explicit value or strategy.
// An enum or struct does not support that.
error!(illegal_strategy(attr: &str, item: &str), E0007,
"`#[proptest({0} = \"<expr>\")]` is not allowed on {1}. Only struct fields, \
enum variants and fields inside those can use an explicit {0}.",
attr, item);
// Happens when `#[proptest(regex = "<string>")]` is specified on an `item`
// that does not support setting an explicit value or strategy.
// See `illegal_strategy` for more.
error!(illegal_regex(item: &str), E0007,
"`#[proptest(regex = \"<string>\")]` is not allowed on {0}. Only struct \
fields, enum variant fields can use an explicit regex.",
item);
// Happens when `#[proptest(skip)]` is specified on an `item` that does
// not support skipping. Only enum variants support skipping.
error!(illegal_skip(item: &str), E0008,
"A {} can't be `#[proptest(skip)]`ed, only enum variants can be skipped.",
item);
// Happens when `#[proptest(weight = <integer>)]` is specified on an
// `item` that does not support weighting.
error!(illegal_weight(item: &str), E0009,
"`#[proptest(weight = <integer>)]` is not allowed on {} as it is \
meaningless. Only enum variants can be assigned weights.",
item);
// Happens when `#[proptest(params = <type>)]` is set on `item`
// but also on the parent of `item`. If the parent has set `params`
// then that applies, and the `params` on `item` would be meaningless
// wherefore it is forbidden.
error!(parent_has_param(item: &str), E0010,
"Cannot set the associated type `Parameters` of `Arbitrary` with either \
`#[proptest(no_params)]` or `#[proptest(params(<type>)]` on {} since it \
was set on the parent.",
item);
// Happens when `#[proptest(params = <type>)]` is set on `item`
// but not `#[proptest(strategy = <expr>)]`.
// This does not apply to the top level type declaration.
fatal!(cant_set_param_but_not_strat(self_ty: &syn::Type, item: &str), E0011,
"Cannot set `#[proptest(params = <type>)]` on {0} while not providing a \
strategy for the {0} to use it since `<{1} as Arbitrary<'a>>::Strategy` \
may require a different type than the one provided in `<type>`.",
item, quote! { #self_ty });
// Happens when `#[proptest(filter = "<expr>")]` is set on `item`,
// but the parent of the `item` explicitly specifies a value or strategy,
// which would cause the value to be generated without consulting the
// `filter`.
error!(meaningless_filter(item: &str), E0012,
"Cannot set `#[proptest(filter = <expr>)]` on {} since it is set on the \
item which it is inside of that outer item specifies how to generate \
itself.",
item);
// Happens when the form `#![proptest<..>]` is used. This will probably never
// happen - but just in case it does, we catch it and emit an error.
error!(inner_attr, E0013,
"Inner attributes `#![proptest(..)]` are not currently supported.");
// Happens when the form `#[proptest]` is used. The form contains no
// information for us to process, so we disallow it.
error!(bare_proptest_attr, E0014,
"Bare `#[proptest]` attributes are not allowed.");
// Happens when the form `#[proptest = <literal>)]` is used.
// Only the form `#[proptest(<contents>)]` is supported.
error!(literal_set_proptest, E0015,
"The attribute form `#[proptest = <literal>]` is not allowed.");
// Happens when `<modifier>` in `#[proptest(<modifier>)]` is a literal and
// not a real modifier.
error!(immediate_literals, E0016,
"Literals immediately inside `#[proptest(..)]` as in \
`#[proptest(<lit>, ..)]` are not allowed.");
// Happens when `<modifier>` in `#[proptest(<modifier>)]` is set more than
// once.
error!(set_again(meta: &syn::Meta), E0017,
"The attribute modifier `{}` inside `#[proptest(..)]` has already been \
set. To fix the error, please remove at least one such modifier.",
meta.name());
// Happens when `<modifier>` in `#[proptest(<modifier>)]` is unknown to
// us but we can make an educated guess as to what the user meant.
error!(did_you_mean(found: &str, expected: &str), E0018,
"Unknown attribute modifier `{}` inside #[proptest(..)] is not allowed. \
Did you mean to use `{}` instead?",
found, expected);
// Happens when `<modifier>` in `#[proptest(<modifier>)]` is unknown to us.
error!(unkown_modifier(modifier: &str), E0018,
"Unknown attribute modifier `{}` inside `#[proptest(..)]` is not allowed.",
modifier);
// Happens when `#[proptest(no_params)]` is malformed.
error!(no_params_malformed, E0019,
"The attribute modifier `no_params` inside `#[proptest(..)]` does not \
support any further configuration and must be a plain modifier as in \
`#[proptest(no_params)]`.");
// Happens when `#[proptest(skip)]` is malformed.
error!(skip_malformed, E0020,
"The attribute modifier `skip` inside `#[proptest(..)]` does not support \
any further configuration and must be a plain modifier as in \
`#[proptest(skip)]`.");
// Happens when `#[proptest(weight..)]` is malformed.
error!(weight_malformed(meta: &syn::Meta), E0021,
"The attribute modifier `{0}` inside `#[proptest(..)]` must have the \
format `#[proptest({0} = <integer>)]` where `<integer>` is an integer that \
fits within a `u32`. An example: `#[proptest({0} = 2)]` to set a relative \
weight of 2.",
meta.name());
// Happens when both `#[proptest(params = "<type>")]` and
// `#[proptest(no_params)]` were specified. They are mutually
// exclusive choices. The user can resolve this by picking one.
fatal!(overspecified_param, E0022,
"Cannot set `#[proptest(no_params)]` as well as \
`#[proptest(params(<type>))]` simultaneously. \
Please pick one of these attributes.");
// This happens when `#[proptest(params..)]` is malformed.
// For example, `#[proptest(params)]` is malformed. Another example is when
// `<type>` inside `#[proptest(params = "<type>")]` or
// `#[proptest(params("<type>"))]` is malformed. In other words, `<type>` is
// not a valid Rust type. Note that `syn` may not cover all valid Rust types.
error!(param_malformed, E0023,
"The attribute modifier `params` inside #[proptest(..)] must have the \
format `#[proptest(params = \"<type>\")]` where `<type>` is a valid type \
in Rust. An example: `#[proptest(params = \"ComplexType<Foo>\")]`.");
// Happens when syn can't interpret <tts> in `#[proptest <tts>]`.
error!(no_interp_meta, E0024,
"The tokens `<tts>` in #[proptest <tts>] do not make for a valid attribute.");
// Happens when more than one of `#[proptest(strategy..)]`,
// `#[proptest(value..)]`, or `#[proptest(regex..)]` were specified.
// They are mutually exclusive choices.
// The user can resolve this by picking one.
fatal!(overspecified_strat, E0025,
"Cannot set more than one of `#[proptest(value = \"<expr>\")]`,
`#[proptest(strategy = \"<expr>\")]`, `#[proptest(regex = \"<string>\")]` \
simultaneously. Please pick one of these attributes.");
// Happens when `#[proptest(strategy..)]` or `#[proptest(value..)]` is
// malformed. For example, `<expr>` inside `#[proptest(strategy = "<expr>")]`
// or `#[proptest(value = "<expr>")]` is malformed. In other words, `<expr>`
// is not a valid Rust expression.
error!(strategy_malformed(meta: &syn::Meta), E0026,
"The attribute modifier `{0}` inside `#[proptest(..)]` must have the \
format `#[proptest({0} = \"<expr>\")]` where `<expr>` is a valid Rust \
expression.",
meta.name());
// Happens when `#[proptest(filter..)]` is malformed.
// For example, `<expr>` inside `#[proptest(filter = "<expr>")]` or
// is malformed. In other words, `<expr>` is not a valid Rust expression.
error!(filter_malformed(meta: &syn::Meta), E0027,
"The attribute modifier `{0}` inside `#[proptest(..)]` must have the \
format `#[proptest({0} = \"<expr>\")]` where `<expr>` is a valid Rust \
expression.",
meta.name());
// Any attributes on a skipped variant has no effect - so we emit this error
// to the user so that they are aware.
error!(skipped_variant_has_weight(item: &str), E0028,
"A variant has been skipped. Setting `#[proptest(weight = <value>)]` on \
the {} is meaningless and is not allowed.",
item);
// Any attributes on a skipped variant has no effect - so we emit this error
// to the user so that they are aware.
error!(skipped_variant_has_param(item: &str), E0028,
"A variant has been skipped. Setting `#[proptest(no_param)]` or \
`#[proptest(params(<type>))]` on the {} is meaningless and is not allowed.",
item);
// Any attributes on a skipped variant has no effect - so we emit this error
// to the user so that they are aware.
error!(skipped_variant_has_strat(item: &str), E0028,
"A variant has been skipped. Setting `#[proptest(value = \"<expr>\")]` or \
`#[proptest(strategy = \"<expr>\")]` on the {} is meaningless and is not \
allowed.",
item);
// Any attributes on a skipped variant has no effect - so we emit this error
// to the user so that they are aware. Unfortunately, there's no way to
// emit a warning to the user, so we emit an error instead.
error!(skipped_variant_has_filter(item: &str), E0028,
"A variant has been skipped. Setting `#[proptest(filter = \"<expr>\")]` or \
on the {} is meaningless and is not allowed.",
item);
// There's only one way to produce a specific unit variant, so setting
// `#[proptest(strategy = "<expr>")]` or `#[proptest(value = "<expr>")]`
// would be pointless.
error!(strategy_on_unit_variant(what: &str), E0029,
"Setting `#[proptest({0} = \"<expr>\")]` on a unit variant has no effect \
and is redundant because there is nothing to configure.",
what);
// See `strategy_on_unit_variant`.
error!(regex_on_unit_variant, E0029,
"Setting `#[proptest(regex = \"<string>\")]` on a unit variant has no effect \
and is redundant because there is nothing to configure.");
// There's only one way to produce a specific unit variant, so setting
// `#[proptest(params = "<type>")]` would be pointless.
error!(params_on_unit_variant, E0029,
"Setting `#[proptest(params = \"<type>\")]` on a unit variant has \
no effect and is redundant because there is nothing to configure.");
// There's only one way to produce a specific unit variant, so setting
// `#[proptest(filter = "<expr>")]` would be pointless.
error!(filter_on_unit_variant, E0029,
"Setting `#[proptest(filter = \"<expr>\")]` on a unit variant has \
no effect and is redundant because there is nothing to further filter.");
// Occurs when `#[proptest(params = "<type>")]` is specified on a unit
// struct. There's only one way to produce a unit struct, so specifying
// `Parameters` would be pointless.
error!(params_on_unit_struct, E0030,
"Setting `#[proptest(params = \"<type>\")]` on a unit struct has no effect \
and is redundant because there is nothing to configure.");
// Occurs when `#[proptest(filter = "<expr>")]` is specified on a unit
// struct. There's only one way to produce a unit struct, so filtering
// would be pointless.
error!(filter_on_unit_struct, E0030,
"Setting `#[proptest(filter = \"<expr>\")]` on a unit struct has no effect \
and is redundant because there is nothing to filter.");
// Occurs when `#[proptest(no_bound)]` is specified
// on something that is not a type variable.
error!(no_bound_set_on_non_tyvar, E0031,
"Setting `#[proptest(no_bound)]` on something that is not a type variable \
has no effect and is redundant. Therefore it is not allowed.");
// Happens when `#[proptest(no_bound)]` is malformed.
error!(no_bound_malformed, E0032,
"The attribute modifier `no_bound` inside `#[proptest(..)]` does not \
support any further configuration and must be a plain modifier as in \
`#[proptest(no_bound)]`.");
// Happens when the sum of weights on enum variants overflowing an u32.
error!(weight_overflowing, E0033,
"The sum of the weights specified on variants of the enum you are \
deriving `Arbitrary` for overflows an `u32` which it can't do.");
// Happens when `#[proptest(regex..)]` is malformed.
// For example, `#[proptest(regex = 1)]` is not a valid form.
error!(regex_malformed, E0034,
"The attribute modifier `regex` inside `#[proptest(..)]` must have the \
format `#[proptest(regex = \"<string>\")]` where `<string>` is a valid
regular expression embedded in a Rust string slice.");
// Happens when `#[proptest(params = <type>)]` is set on `item` and then
// `#[proptest(regex = "<string>")]` is also set. We reject this because
// the params can't be used. TODO: reduce this to a warning once we can
// emit warnings.
error!(cant_set_param_and_regex(item: &str), E0035,
"Cannot set #[proptest(regex = \"<string>\")] and \
`#[proptest(params = <type>)]` on {0} because the latter is a logic bug \
since `params` cannot be used in `<string>`.",
item);