Macro unimock::matching

source ·
matching!() { /* proc-macro */ }
Expand description

Macro to ease call pattern matching for function arguments. The macro produces a closure reference expression suitable for passing to some_call, etc.

Its syntax takes inspiration from std::matches and works similarly, except that the value to match can be removed as a macro argument, since it is instead received as the closure argument.

Two main forms of syntaxes are supported:

  1. Simple form, e.g. matching!(1, 2): A single tuple pattern to match the entire input tuple.
  2. Disjunctive form, e.g. matching!((1, 2) | (3, 4) | (5, 6)): Each operand to the | sigil is a standalone tuple pattern, with the behaviour that the complete pattern is matching if at least one of the standalone tuple patterns are matching.

if guards are also supported.

Example


#[unimock(api=Mock)]
trait Trait {
    fn one(&self, a: &str);
    fn three(&self, a: &str, b: &str, c: &str);
}

fn one_str() {
    fn args(_: &dyn Fn(&mut macro_api::Matching<Mock::one>)) {}
    args(matching!("a"));
}

fn three_strs() {
    fn args(_: &dyn Fn(&mut macro_api::Matching<Mock::three>)) {}
    args(matching!("a", _, "c"));
    args(matching!(("a", "b", "c") | ("d", "e", "f")));
    args(matching!(("a", b, "c") if b.contains("foo")));
}

Auto-“coercions”

Since the input expression being matched is generated by the macro, you would normally suffer from the following problem when matching some non-&str function input:

let string = String::new();
match &string {
    "foo" => true, // expected struct `String`, found `str`
    _ => false,
}

To help ergonomics, the matching macro recognizes certain literals used in the patterns, and performs appropriate type conversion at the correct places:

pub struct Newtype(String);

#[unimock(api=Mock)]
trait Trait {
    fn interesting_args(
        &self,
        a: String,
        b: std::borrow::Cow<'static, str>,
        c: Newtype,
        d: i32
    );
}

fn args(_: &dyn Fn(&mut macro_api::Matching<Mock::interesting_args>)) {}

args(matching! {("a", _, "c", _) | (_, "b", _, 42)});

// Newtype works by implementing the following:
impl std::convert::AsRef<str> for Newtype {
    fn as_ref(&self) -> &str {
        self.0.as_str()
    }
}

Internally it works by calling macro_api::as_str_ref on inputs matched by a string literal.

Matching using Eq

Since patterns in Rust are somewhat limited, the matching macro also supports matching using Eq.

A single argument changes to Eq matching by enclosing that argument within eq!(_) or ne!(_):

#[derive(Eq, PartialEq)]
pub struct Data(Vec<i32>);

#[unimock(api=Mock)]
trait Trait {
    fn func(&self, arg: Data) -> &str;
}

let u = Unimock::new((
    Mock::func
        .each_call(matching!(eq!(&Data(vec![]))))
        .returns("empty"),
    Mock::func
        .each_call(matching!(ne!(&Data(vec![0]))))
        .returns("non-zero"),
    Mock::func
        .each_call(matching!(_))
        .returns("other")
));

assert_eq!("empty", <Unimock as Trait>::func(&u, Data(vec![])));
assert_eq!("non-zero", <Unimock as Trait>::func(&u, Data(vec![42])));
assert_eq!("other", <Unimock as Trait>::func(&u, Data(vec![0])));