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

use unimock::*;
#[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 unimock::private::Matching<Mock::one>)) {}
    args(matching!("a"));
}

fn three_strs() {
    fn args(_: &dyn Fn(&mut unimock::private::Matching<Mock::three>)) {}
    args(matching!("a", _, "c" | "C"));
    args(matching!(("a", "b", "c") | ("d", "e", "f" | "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 unimock::private::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()
    }
}

§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])));