Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameterize Matcher::matches and Matcher::explain_match. #369

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

hovinen
Copy link
Collaborator

@hovinen hovinen commented Mar 25, 2024

Note: I've created this PR for now just to check that the changes below fully pass CI.

This allows the use of smart pointers for the actual value when invoking Matcher::matches and Matcher::explain_match. The Protobuf implementation namely uses a kind of smart pointer. The lifetime on that smart pointer has no a priori relation to that of the parameter actual, causing the borrow checker to complain whenever invoking an inner matcher on the output of as_view.

This commit addresses the problem by allowing the ViewProxy -- rather than a reference to it -- to be passed directly to Matcher::matches and Matcher::explain_match. It does this by making those two methods generic with respect to the input type. They must only implement Deref<Target = Self::ActualT>.

The new test googletest/tests/arena_test.rs illustrates this with a simplified example.

Introducing these generics unfortunately makes Matcher no longer object-safe. Since several matchers construct trait objts on Matcher, this also introduces an internal-only wrapper trait ObjectSafeMatcher which every Matcher auto-implements.

While this makes some changes to how Matcher is defined and implemented, it should have no effect on how it is used, other than to fix the aforementioned problem. So it should not affect compatibility for most users of the crate.

Fixes #323.

@hovinen hovinen force-pushed the generalize_actual_value_types branch from 5ff7aa5 to 2b43bd4 Compare March 28, 2024 12:43
Previously, `IsEncodedStringMatcher` converted its actual value input to
a `Vec` and constructed a `String` out of that in order to match it.
This is because using a string slice caused problems with the borrow
checker as described in #323.

The fundamental problem is that the type against which the inner matcher
is matching is a reference whose lifetime must be named in the trait
bound:

```
impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher
    for IsEncodedStringMatcher<ActualT, InnerMatcherT>
where
    InnerMatcherT: Matcher<ActualT = &'what str>,
                                       ^^^^ What goes here???
```

One option is just to remove the reference:

```
impl<ActualT: AsRef<[u8]> + Debug, InnerMatcherT> Matcher
    for IsEncodedStringMatcher<ActualT, InnerMatcherT>
where
    InnerMatcherT: Matcher<ActualT = str>,
```

This doesn't work when the inner matcher is `eq`, since one would then
be comparing an `str` and a string slice:

```
verify_that!("A string".as_bytes(), is_utf8_string(eq("A string"))) // Compile error: cannot compare str and &str
```

However, this problem does not occur with `StrMatcher`:

```
verify_that!("A string".as_bytes(), is_utf8_string(constains_substring("A string"))) // Passes
```

So this also introduces an easy way to obtain a `StrMatcher` to check
string equality:

```
verify_that!("A string".as_bytes(), is_utf8_string(eq_str("A string"))) // Passes
```

This is slightly inconvenient, since one must use a different matcher
to compare string equality in this case. But it is well-documented and
not too inconvenient to use. So it should be okay.

The introduction of `eq_str` also allows fixing a longstanding
limitation of the matcher `property!` -- that it could not match on
properties returning string slices. This PR therefore also updates the
documentation accordingly and adds an appropriate test.
This allows the use of smart pointers for the actual value when invoking
`Matcher::matches` and `Matcher::explain_match`. The Protobuf
implementation namely uses a
[kind of smart pointer](https://github.com/protocolbuffers/protobuf/blob/62d5d9bf67d27d3b0084dabcf67bff2f8238162b/rust/proxied.rs#L113).
The lifetime on that smart pointer has no a priori relation to that of
the parameter `actual`, causing the borrow checker to complain whenever
invoking an inner matcher on the output of `as_view`.

This commit addresses the problem by allowing the `ViewProxy` -- rather
than a reference to it -- to be passed directly to `Matcher::matches`
and `Matcher::explain_match`. It does this by making those two methods
generic with respect to the input type. They must only implement
`Deref<Target = Self::ActualT>`.

The new test `googletest/tests/arena_test.rs` illustrates this with a
simplified example.

Introducing these generics unfortunately makes `Matcher` no longer
object-safe. Since several matchers construct trait objts on `Matcher`,
this also introduces an internal-only wrapper trait `ObjectSafeMatcher`
which every `Matcher` auto-implements.

While this makes some changes to how `Matcher` is defined and
implemented, it should have no effect on how it is _used_, other than
to fix the aforementioned problem. So it should not affect compatibility
for most users of the crate.

Fixes #323.
@hovinen hovinen force-pushed the generalize_actual_value_types branch from 2b43bd4 to 14ca943 Compare March 28, 2024 13:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Limitations of the Matcher Trait with Proxy reference type
1 participant