diff --git a/googletest/crate_docs.md b/googletest/crate_docs.md index 8c8b47cf..0a1498c8 100644 --- a/googletest/crate_docs.md +++ b/googletest/crate_docs.md @@ -216,7 +216,7 @@ struct MyEqMatcher { expected: T, } -impl Matcher for MyEqMatcher { +impl Matcher<'_> for MyEqMatcher { type ActualT = T; fn matches(&self, actual: &Self::ActualT) -> MatcherResult { @@ -250,7 +250,7 @@ impl Matcher for MyEqMatcher { # expected: T, # } # - # impl Matcher for MyEqMatcher { + # impl Matcher<'_> for MyEqMatcher { # type ActualT = T; # # fn matches(&self, actual: &Self::ActualT) -> MatcherResult { @@ -273,7 +273,7 @@ impl Matcher for MyEqMatcher { # } # } # - pub fn eq_my_way(expected: T) -> impl Matcher { + pub fn eq_my_way(expected: T) -> MyEqMatcher { MyEqMatcher { expected } } ``` @@ -289,7 +289,7 @@ impl Matcher for MyEqMatcher { # expected: T, # } # -# impl Matcher for MyEqMatcher { +# impl Matcher<'_> for MyEqMatcher { # type ActualT = T; # # fn matches(&self, actual: &Self::ActualT) -> MatcherResult { @@ -312,7 +312,7 @@ impl Matcher for MyEqMatcher { # } # } # -# pub fn eq_my_way(expected: T) -> impl Matcher { +# pub fn eq_my_way(expected: T) -> MyEqMatcher { # MyEqMatcher { expected } # } # /* The attribute macro would prevent the function from being compiled in a doctest. diff --git a/googletest/src/assertions.rs b/googletest/src/assertions.rs index 26680281..8920d4b2 100644 --- a/googletest/src/assertions.rs +++ b/googletest/src/assertions.rs @@ -497,9 +497,9 @@ pub mod internal { /// /// **For internal use only. API stablility is not guaranteed!** #[must_use = "The assertion result must be evaluated to affect the test result."] - pub fn check_matcher( - actual: &T, - expected: impl Matcher, + pub fn check_matcher<'a, T: Debug + ?Sized>( + actual: &'a T, + expected: impl Matcher<'a, ActualT = T>, actual_expr: &'static str, source_location: SourceLocation, ) -> Result<(), TestAssertionFailure> { diff --git a/googletest/src/matcher.rs b/googletest/src/matcher.rs index 071b0d14..932dba5d 100644 --- a/googletest/src/matcher.rs +++ b/googletest/src/matcher.rs @@ -22,7 +22,7 @@ use crate::matchers::__internal_unstable_do_not_depend_on_these::DisjunctionMatc use std::fmt::Debug; /// An interface for checking an arbitrary condition on a datum. -pub trait Matcher { +pub trait Matcher<'a> { /// The type against which this matcher matches. type ActualT: Debug + ?Sized; @@ -32,7 +32,7 @@ pub trait Matcher { /// matching condition is based on data stored in the matcher. For example, /// `eq` matches when its stored expected value is equal (in the sense of /// the `==` operator) to the value `actual`. - fn matches(&self, actual: &Self::ActualT) -> MatcherResult; + fn matches(&self, actual: &'a Self::ActualT) -> MatcherResult; /// Returns a description of `self` or a negative description if /// `matcher_result` is `DoesNotMatch`. @@ -133,7 +133,7 @@ pub trait Matcher { /// .nested(self.expected.explain_match(actual.deref())) /// } /// ``` - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match(&self, actual: &'a Self::ActualT) -> Description { format!("which {}", self.describe(self.matches(actual))).into() } @@ -160,7 +160,7 @@ pub trait Matcher { // TODO(b/264518763): Replace the return type with impl Matcher and reduce // visibility of ConjunctionMatcher once impl in return position in trait // methods is stable. - fn and>( + fn and>( self, right: Right, ) -> ConjunctionMatcher @@ -190,7 +190,7 @@ pub trait Matcher { // TODO(b/264518763): Replace the return type with impl Matcher and reduce // visibility of DisjunctionMatcher once impl in return position in trait // methods is stable. - fn or>( + fn or>( self, right: Right, ) -> DisjunctionMatcher @@ -210,9 +210,9 @@ const PRETTY_PRINT_LENGTH_THRESHOLD: usize = 60; /// /// The parameter `actual_expr` contains the expression which was evaluated to /// obtain `actual`. -pub(crate) fn create_assertion_failure( - matcher: &impl Matcher, - actual: &T, +pub(crate) fn create_assertion_failure<'a, T: Debug + ?Sized>( + matcher: &impl Matcher<'a, ActualT = T>, + actual: &'a T, actual_expr: &'static str, source_location: SourceLocation, ) -> TestAssertionFailure { diff --git a/googletest/src/matcher_support/count_elements.rs b/googletest/src/matcher_support/count_elements.rs index 662dcc96..70aa418a 100644 --- a/googletest/src/matcher_support/count_elements.rs +++ b/googletest/src/matcher_support/count_elements.rs @@ -18,9 +18,9 @@ /// unambiguous answer, i.e., the upper bound exists and the lower and upper /// bounds agree. Otherwise it iterates through `value` and counts the /// elements. -pub(crate) fn count_elements(value: &ContainerT) -> usize +pub(crate) fn count_elements<'b, ContainerT: ?Sized>(value: &'b ContainerT) -> usize where - for<'b> &'b ContainerT: IntoIterator, + &'b ContainerT: IntoIterator, { let iterator = value.into_iter(); if let (lower, Some(higher)) = iterator.size_hint() { diff --git a/googletest/src/matchers/all_matcher.rs b/googletest/src/matchers/all_matcher.rs index f2e6d069..e0113b51 100644 --- a/googletest/src/matchers/all_matcher.rs +++ b/googletest/src/matchers/all_matcher.rs @@ -74,23 +74,23 @@ pub mod internal { /// /// For internal use only. API stablility is not guaranteed! #[doc(hidden)] - pub struct AllMatcher<'a, T: Debug + ?Sized, const N: usize> { - components: [Box + 'a>; N], + pub struct AllMatcher<'a, 'e, T: Debug + ?Sized, const N: usize> { + components: [Box + 'a>; N], } - impl<'a, T: Debug + ?Sized, const N: usize> AllMatcher<'a, T, N> { + impl<'a, 'e, T: Debug + ?Sized, const N: usize> AllMatcher<'a, 'e, T, N> { /// Constructs an [`AllMatcher`] with the given component matchers. /// /// Intended for use only by the [`all`] macro. - pub fn new(components: [Box + 'a>; N]) -> Self { + pub fn new(components: [Box + 'a>; N]) -> Self { Self { components } } } - impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AllMatcher<'a, T, N> { + impl<'a, 'e, T: Debug + ?Sized, const N: usize> Matcher<'e> for AllMatcher<'a, 'e, T, N> { type ActualT = T; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches(&self, actual: &'e Self::ActualT) -> MatcherResult { for component in &self.components { match component.matches(actual) { MatcherResult::NoMatch => { @@ -102,7 +102,7 @@ pub mod internal { MatcherResult::Match } - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match(&self, actual: &'e Self::ActualT) -> Description { match N { 0 => anything::().explain_match(actual), 1 => self.components[0].explain_match(actual), diff --git a/googletest/src/matchers/any_matcher.rs b/googletest/src/matchers/any_matcher.rs index 95d53fe5..a7432549 100644 --- a/googletest/src/matchers/any_matcher.rs +++ b/googletest/src/matchers/any_matcher.rs @@ -76,27 +76,27 @@ pub mod internal { /// /// For internal use only. API stablility is not guaranteed! #[doc(hidden)] - pub struct AnyMatcher<'a, T: Debug + ?Sized, const N: usize> { - components: [Box + 'a>; N], + pub struct AnyMatcher<'a, 'e, T: Debug + ?Sized, const N: usize> { + components: [Box + 'a>; N], } - impl<'a, T: Debug + ?Sized, const N: usize> AnyMatcher<'a, T, N> { + impl<'a, 'e, T: Debug + ?Sized, const N: usize> AnyMatcher<'a, 'e, T, N> { /// Constructs an [`AnyMatcher`] with the given component matchers. /// /// Intended for use only by the [`all`] macro. - pub fn new(components: [Box + 'a>; N]) -> Self { + pub fn new(components: [Box + 'a>; N]) -> Self { Self { components } } } - impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AnyMatcher<'a, T, N> { + impl<'a, 'e, T: Debug + ?Sized, const N: usize> Matcher<'e> for AnyMatcher<'a, 'e, T, N> { type ActualT = T; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches(&self, actual: &'e Self::ActualT) -> MatcherResult { MatcherResult::from(self.components.iter().any(|c| c.matches(actual).is_match())) } - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match(&self, actual: &'e Self::ActualT) -> Description { match N { 0 => format!("which {}", anything::().describe(MatcherResult::NoMatch)).into(), 1 => self.components[0].explain_match(actual), diff --git a/googletest/src/matchers/anything_matcher.rs b/googletest/src/matchers/anything_matcher.rs index 36be478b..2ce36d1c 100644 --- a/googletest/src/matchers/anything_matcher.rs +++ b/googletest/src/matchers/anything_matcher.rs @@ -32,13 +32,13 @@ use std::{fmt::Debug, marker::PhantomData}; /// # } /// # should_pass().unwrap(); /// ``` -pub fn anything() -> impl Matcher { +pub fn anything() -> Anything { Anything::(Default::default()) } -struct Anything(PhantomData); +pub struct Anything(PhantomData); -impl Matcher for Anything { +impl Matcher<'_> for Anything { type ActualT = T; fn matches(&self, _: &T) -> MatcherResult { diff --git a/googletest/src/matchers/char_count_matcher.rs b/googletest/src/matchers/char_count_matcher.rs index 70977d74..7574a4d1 100644 --- a/googletest/src/matchers/char_count_matcher.rs +++ b/googletest/src/matchers/char_count_matcher.rs @@ -56,9 +56,12 @@ use std::{fmt::Debug, marker::PhantomData}; /// # } /// # should_pass().unwrap(); /// ``` -pub fn char_count, E: Matcher>( +pub fn char_count<'a, T: Debug + ?Sized + AsRef, E>( expected: E, -) -> impl Matcher { +) -> impl Matcher<'a, ActualT = T> +where + E: for<'c> Matcher<'c, ActualT = usize>, +{ CharLenMatcher { expected, phantom: Default::default() } } @@ -67,7 +70,10 @@ struct CharLenMatcher { phantom: PhantomData, } -impl, E: Matcher> Matcher for CharLenMatcher { +impl, E> Matcher<'_> for CharLenMatcher +where + E: for<'b> Matcher<'b, ActualT = usize>, +{ type ActualT = T; fn matches(&self, actual: &T) -> MatcherResult { @@ -131,7 +137,7 @@ mod tests { #[test] fn char_count_explains_match() -> Result<()> { struct TestMatcher(PhantomData); - impl Matcher for TestMatcher { + impl Matcher<'_> for TestMatcher { type ActualT = T; fn matches(&self, _: &T) -> MatcherResult { diff --git a/googletest/src/matchers/conjunction_matcher.rs b/googletest/src/matchers/conjunction_matcher.rs index dc50833b..f02bf839 100644 --- a/googletest/src/matchers/conjunction_matcher.rs +++ b/googletest/src/matchers/conjunction_matcher.rs @@ -36,20 +36,20 @@ impl ConjunctionMatcher { } } -impl> Matcher for ConjunctionMatcher +impl<'a, M1: Matcher<'a>, M2: Matcher<'a, ActualT = M1::ActualT>> Matcher<'a> for ConjunctionMatcher where M1::ActualT: Debug, { type ActualT = M1::ActualT; - fn matches(&self, actual: &M1::ActualT) -> MatcherResult { + fn matches(&self, actual: &'a M1::ActualT) -> MatcherResult { match (self.m1.matches(actual), self.m2.matches(actual)) { (MatcherResult::Match, MatcherResult::Match) => MatcherResult::Match, _ => MatcherResult::NoMatch, } } - fn explain_match(&self, actual: &M1::ActualT) -> Description { + fn explain_match(&self, actual: &'a M1::ActualT) -> Description { match (self.m1.matches(actual), self.m2.matches(actual)) { (MatcherResult::Match, MatcherResult::Match) => Description::new() .nested(self.m1.explain_match(actual)) diff --git a/googletest/src/matchers/container_eq_matcher.rs b/googletest/src/matchers/container_eq_matcher.rs index d4f872c7..42068755 100644 --- a/googletest/src/matchers/container_eq_matcher.rs +++ b/googletest/src/matchers/container_eq_matcher.rs @@ -104,23 +104,23 @@ pub struct ContainerEqMatcher { phantom: PhantomData, } -impl Matcher +impl<'a, ActualElementT: 'a, ActualContainerT: 'a, ExpectedElementT, ExpectedContainerT> Matcher<'a> for ContainerEqMatcher where ActualElementT: PartialEq + Debug + ?Sized, ActualContainerT: PartialEq + Debug + ?Sized, ExpectedElementT: Debug, ExpectedContainerT: Debug, - for<'a> &'a ActualContainerT: IntoIterator, - for<'a> &'a ExpectedContainerT: IntoIterator, + &'a ActualContainerT: IntoIterator, + for<'b> &'b ExpectedContainerT: IntoIterator, { type ActualT = ActualContainerT; - fn matches(&self, actual: &ActualContainerT) -> MatcherResult { + fn matches(&self, actual: &'a ActualContainerT) -> MatcherResult { (*actual == self.expected).into() } - fn explain_match(&self, actual: &ActualContainerT) -> Description { + fn explain_match(&self, actual: &'a ActualContainerT) -> Description { build_explanation(self.get_missing_items(actual), self.get_unexpected_items(actual)).into() } @@ -132,20 +132,25 @@ where } } -impl +impl<'a, ActualContainerT: ?Sized + 'a, ExpectedContainerT, ExpectedElementT> ContainerEqMatcher where - ActualElementT: PartialEq + ?Sized, - ActualContainerT: PartialEq + ?Sized, - for<'a> &'a ActualContainerT: IntoIterator, - for<'a> &'a ExpectedContainerT: IntoIterator, + &'a ActualContainerT: IntoIterator, + for<'b> &'b ExpectedContainerT: IntoIterator, + for<'b> <&'a ActualContainerT as IntoIterator>::Item: PartialEq<&'b ExpectedElementT>, { - fn get_missing_items(&self, actual: &ActualContainerT) -> Vec<&ExpectedElementT> { + fn get_missing_items( + &self, + actual: &'a ActualContainerT, + ) -> Vec<&'_ ExpectedElementT> { self.expected.into_iter().filter(|&i| !actual.into_iter().any(|j| j == i)).collect() } - fn get_unexpected_items<'a>(&self, actual: &'a ActualContainerT) -> Vec<&'a ActualElementT> { - actual.into_iter().filter(|&i| !self.expected.into_iter().any(|j| i == j)).collect() + fn get_unexpected_items( + &self, + actual: &'a ActualContainerT, + ) -> Vec<<&'a ActualContainerT as IntoIterator>::Item> { + actual.into_iter().filter(|i| !self.expected.into_iter().any(|j| i == &j)).collect() } } diff --git a/googletest/src/matchers/contains_matcher.rs b/googletest/src/matchers/contains_matcher.rs index 1a27ce04..81a162c8 100644 --- a/googletest/src/matchers/contains_matcher.rs +++ b/googletest/src/matchers/contains_matcher.rs @@ -43,19 +43,23 @@ use std::{fmt::Debug, marker::PhantomData}; /// # should_fail_1().unwrap_err(); /// # should_fail_2().unwrap_err(); /// ``` -pub fn contains(inner: InnerMatcherT) -> ContainsMatcher { - ContainsMatcher { inner, count: None, phantom: Default::default() } +pub fn contains( + inner: InnerMatcherT, +) -> ContainsMatcher { + ContainsMatcher { inner, count: NoCountMatcher, phantom: Default::default() } } /// A matcher which matches a container containing one or more elements a given /// inner [`Matcher`] matches. -pub struct ContainsMatcher { +pub struct ContainsMatcher { inner: InnerMatcherT, - count: Option>>, + count: CountMatcher, phantom: PhantomData, } -impl ContainsMatcher { +pub struct NoCountMatcher; + +impl ContainsMatcher { /// Configures this instance to match containers which contain a number of /// matching items matched by `count`. /// @@ -68,9 +72,14 @@ impl ContainsMatcher { /// /// One can also use `times(eq(0))` to test for the *absence* of an item /// matching the expected value. - pub fn times(mut self, count: impl Matcher + 'static) -> Self { - self.count = Some(Box::new(count)); - self + pub fn times( + self, + count: CountMatcher, + ) -> ContainsMatcher + where + CountMatcher: for<'a> Matcher<'a, ActualT = usize>, + { + ContainsMatcher { inner: self.inner, count, phantom: self.phantom } } } @@ -84,67 +93,95 @@ impl ContainsMatcher { // because val is dropped before matcher but the trait bound requires that // the argument to matches outlive the matcher. It works fine if one defines // val before matcher. -impl, ContainerT: Debug> Matcher - for ContainsMatcher +impl<'a, T: Debug + 'a, InnerMatcherT: Matcher<'a, ActualT = T>, ContainerT: Debug + 'a> Matcher<'a> + for ContainsMatcher where - for<'a> &'a ContainerT: IntoIterator, + &'a ContainerT: IntoIterator, { type ActualT = ContainerT; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { - if let Some(count) = &self.count { - count.matches(&self.count_matches(actual)) - } else { - for v in actual.into_iter() { - if self.inner.matches(v).into() { - return MatcherResult::Match; - } + fn matches(&self, actual: &'a Self::ActualT) -> MatcherResult { + for v in actual.into_iter() { + if self.inner.matches(v).into() { + return MatcherResult::Match; } - MatcherResult::NoMatch } + MatcherResult::NoMatch } - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match(&self, actual: &'a Self::ActualT) -> Description { let count = self.count_matches(actual); - match (count, &self.count) { - (_, Some(_)) => format!("which contains {} matching elements", count).into(), - (0, None) => "which does not contain a matching element".into(), - (_, None) => "which contains a matching element".into(), + match count { + 0 => "which does not contain a matching element".into(), + _ => "which contains a matching element".into(), + } + } + + fn describe(&self, matcher_result: MatcherResult) -> Description { + match matcher_result { + MatcherResult::Match => format!( + "contains at least one element which {}", + self.inner.describe(MatcherResult::Match) + ) + .into(), + MatcherResult::NoMatch => { + format!("contains no element which {}", self.inner.describe(MatcherResult::Match)) + .into() + } } } +} + +// TODO(hovinen): Revisit the trait bounds to see whether this can be made more +// flexible. Namely, the following doesn't compile currently: +// +// let matcher = contains(eq(&42)); +// let val = 42; +// let _ = matcher.matches(&vec![&val]); +// +// because val is dropped before matcher but the trait bound requires that +// the argument to matches outlive the matcher. It works fine if one defines +// val before matcher. +impl<'a, T: Debug + 'a, InnerMatcherT: Matcher<'a, ActualT = T>, ContainerT: Debug + 'a, CountMatcher> + Matcher<'a> for ContainsMatcher +where + &'a ContainerT: IntoIterator, + CountMatcher: for<'b> Matcher<'b, ActualT = usize>, +{ + type ActualT = ContainerT; + + fn matches(&self, actual: &'a Self::ActualT) -> MatcherResult { + self.count.matches(&self.count_matches(actual)) + } + + fn explain_match(&self, actual: &'a Self::ActualT) -> Description { + let count = self.count_matches(actual); + format!("which contains {} matching elements", count).into() + } fn describe(&self, matcher_result: MatcherResult) -> Description { - match (matcher_result, &self.count) { - (MatcherResult::Match, Some(count)) => format!( + match matcher_result { + MatcherResult::Match => format!( "contains n elements which {}\n where n {}", self.inner.describe(MatcherResult::Match), - count.describe(MatcherResult::Match) + self.count.describe(MatcherResult::Match) ) .into(), - (MatcherResult::NoMatch, Some(count)) => format!( + MatcherResult::NoMatch => format!( "doesn't contain n elements which {}\n where n {}", self.inner.describe(MatcherResult::Match), - count.describe(MatcherResult::Match) + self.count.describe(MatcherResult::Match) ) .into(), - (MatcherResult::Match, None) => format!( - "contains at least one element which {}", - self.inner.describe(MatcherResult::Match) - ) - .into(), - (MatcherResult::NoMatch, None) => { - format!("contains no element which {}", self.inner.describe(MatcherResult::Match)) - .into() - } } } } -impl ContainsMatcher { - fn count_matches(&self, actual: &ContainerT) -> usize +impl ContainsMatcher { + fn count_matches<'a, T: Debug + 'a, ContainerT: 'a>(&self, actual: &'a ContainerT) -> usize where - for<'b> &'b ContainerT: IntoIterator, - InnerMatcherT: Matcher, + &'a ContainerT: IntoIterator, + InnerMatcherT: Matcher<'a, ActualT = T>, { let mut count = 0; for v in actual.into_iter() { @@ -236,7 +273,7 @@ mod tests { #[test] fn contains_formats_without_multiplicity_by_default() -> Result<()> { - let matcher: ContainsMatcher, _> = contains(eq(1)); + let matcher: ContainsMatcher, _, _> = contains(eq(1)); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), @@ -246,7 +283,7 @@ mod tests { #[test] fn contains_formats_with_multiplicity_when_specified() -> Result<()> { - let matcher: ContainsMatcher, _> = contains(eq(1)).times(eq(2)); + let matcher: ContainsMatcher, _, _> = contains(eq(1)).times(eq(2)); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), diff --git a/googletest/src/matchers/contains_regex_matcher.rs b/googletest/src/matchers/contains_regex_matcher.rs index 6f681680..eaea4c12 100644 --- a/googletest/src/matchers/contains_regex_matcher.rs +++ b/googletest/src/matchers/contains_regex_matcher.rs @@ -71,7 +71,7 @@ pub struct ContainsRegexMatcher { phantom: PhantomData, } -impl + Debug + ?Sized> Matcher for ContainsRegexMatcher { +impl + Debug + ?Sized> Matcher<'_> for ContainsRegexMatcher { type ActualT = ActualT; fn matches(&self, actual: &ActualT) -> MatcherResult { diff --git a/googletest/src/matchers/disjunction_matcher.rs b/googletest/src/matchers/disjunction_matcher.rs index dd56be27..488e5bc0 100644 --- a/googletest/src/matchers/disjunction_matcher.rs +++ b/googletest/src/matchers/disjunction_matcher.rs @@ -36,20 +36,20 @@ impl DisjunctionMatcher { } } -impl> Matcher for DisjunctionMatcher +impl<'a, M1: Matcher<'a>, M2: Matcher<'a, ActualT = M1::ActualT>> Matcher<'a> for DisjunctionMatcher where M1::ActualT: Debug, { type ActualT = M1::ActualT; - fn matches(&self, actual: &M1::ActualT) -> MatcherResult { + fn matches(&self, actual: &'a M1::ActualT) -> MatcherResult { match (self.m1.matches(actual), self.m2.matches(actual)) { (MatcherResult::NoMatch, MatcherResult::NoMatch) => MatcherResult::NoMatch, _ => MatcherResult::Match, } } - fn explain_match(&self, actual: &M1::ActualT) -> Description { + fn explain_match(&self, actual: &'a M1::ActualT) -> Description { Description::new() .nested(self.m1.explain_match(actual)) .text("and") diff --git a/googletest/src/matchers/display_matcher.rs b/googletest/src/matchers/display_matcher.rs index 1c80f96a..43826fce 100644 --- a/googletest/src/matchers/display_matcher.rs +++ b/googletest/src/matchers/display_matcher.rs @@ -23,19 +23,20 @@ use std::marker::PhantomData; /// let result: impl Display = ...; /// verify_that!(result, displays_as(eq(format!("{}", result))))?; /// ``` -pub fn displays_as>( +pub fn displays_as<'a, T: Debug + Display, InnerMatcher>( inner: InnerMatcher, -) -> impl Matcher { +) -> impl Matcher<'a, ActualT = T> where InnerMatcher: for<'b> Matcher<'b, ActualT = String> { DisplayMatcher:: { inner, phantom: Default::default() } } -struct DisplayMatcher { +struct DisplayMatcher { inner: InnerMatcher, phantom: PhantomData, } -impl> Matcher +impl Matcher<'_> for DisplayMatcher + where InnerMatcher: for<'a> Matcher<'a, ActualT = String> { type ActualT = T; diff --git a/googletest/src/matchers/each_matcher.rs b/googletest/src/matchers/each_matcher.rs index 08a07426..ce13a7f6 100644 --- a/googletest/src/matchers/each_matcher.rs +++ b/googletest/src/matchers/each_matcher.rs @@ -61,12 +61,12 @@ use std::{fmt::Debug, marker::PhantomData}; /// # } /// # should_pass().unwrap(); /// ``` -pub fn each( +pub fn each<'a, ElementT: Debug + 'a, ActualT: Debug + ?Sized + 'a, MatcherT>( inner: MatcherT, -) -> impl Matcher +) -> impl Matcher<'a, ActualT = ActualT> where - for<'a> &'a ActualT: IntoIterator, - MatcherT: Matcher, + &'a ActualT: IntoIterator, + MatcherT: Matcher<'a, ActualT = ElementT>, { EachMatcher { inner, phantom: Default::default() } } @@ -76,14 +76,14 @@ struct EachMatcher { phantom: PhantomData, } -impl Matcher for EachMatcher +impl<'a, ElementT: Debug + 'a, ActualT: Debug + ?Sized + 'a, MatcherT> Matcher<'a> for EachMatcher where - for<'a> &'a ActualT: IntoIterator, - MatcherT: Matcher, + &'a ActualT: IntoIterator, + MatcherT: Matcher<'a, ActualT = ElementT>, { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches(&self, actual: &'a ActualT) -> MatcherResult { for element in actual { if self.inner.matches(element).is_no_match() { return MatcherResult::NoMatch; @@ -92,7 +92,7 @@ where MatcherResult::Match } - fn explain_match(&self, actual: &ActualT) -> Description { + fn explain_match(&self, actual: &'a ActualT) -> Description { let mut non_matching_elements = Vec::new(); for (index, element) in actual.into_iter().enumerate() { if self.inner.matches(element).is_no_match() { diff --git a/googletest/src/matchers/elements_are_matcher.rs b/googletest/src/matchers/elements_are_matcher.rs index 3a4b5b24..a87ae9bd 100644 --- a/googletest/src/matchers/elements_are_matcher.rs +++ b/googletest/src/matchers/elements_are_matcher.rs @@ -101,28 +101,28 @@ pub mod internal { /// /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] - pub struct ElementsAre<'a, ContainerT: ?Sized, T: Debug> { - elements: Vec + 'a>>, + pub struct ElementsAre<'m, 'e, ContainerT: ?Sized, T: Debug> { + elements: Vec + 'm>>, phantom: PhantomData, } - impl<'a, ContainerT: ?Sized, T: Debug> ElementsAre<'a, ContainerT, T> { + impl<'m, 'e, ContainerT: ?Sized, T: Debug> ElementsAre<'m, 'e, ContainerT, T> { /// Factory only intended for use in the macro `elements_are!`. /// /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] - pub fn new(elements: Vec + 'a>>) -> Self { + pub fn new(elements: Vec + 'm>>) -> Self { Self { elements, phantom: Default::default() } } } - impl<'a, T: Debug, ContainerT: Debug + ?Sized> Matcher for ElementsAre<'a, ContainerT, T> + impl<'m, 'e, 'c, T: Debug + 'e, ContainerT: Debug + ?Sized + 'c> Matcher<'c> for ElementsAre<'m, 'e, ContainerT, T> where - for<'b> &'b ContainerT: IntoIterator, + &'c ContainerT: IntoIterator, { type ActualT = ContainerT; - fn matches(&self, actual: &ContainerT) -> MatcherResult { + fn matches(&self, actual: &'c ContainerT) -> MatcherResult { let mut zipped_iterator = zip(actual.into_iter(), self.elements.iter()); for (a, e) in zipped_iterator.by_ref() { if e.matches(a).is_no_match() { @@ -136,7 +136,7 @@ pub mod internal { } } - fn explain_match(&self, actual: &ContainerT) -> Description { + fn explain_match(&self, actual: &'c ContainerT) -> Description { let actual_iterator = actual.into_iter(); let mut zipped_iterator = zip(actual_iterator, self.elements.iter()); let mut mismatches = Vec::new(); diff --git a/googletest/src/matchers/empty_matcher.rs b/googletest/src/matchers/empty_matcher.rs index 11cb6758..80e90d20 100644 --- a/googletest/src/matchers/empty_matcher.rs +++ b/googletest/src/matchers/empty_matcher.rs @@ -50,9 +50,9 @@ use std::{fmt::Debug, marker::PhantomData}; /// # should_pass().unwrap(); /// ``` -pub fn empty() -> impl Matcher +pub fn empty<'a, T: Debug + ?Sized + 'a>() -> impl Matcher<'a, ActualT = T> where - for<'a> &'a T: IntoIterator, + &'a T: IntoIterator, { EmptyMatcher { phantom: Default::default() } } @@ -61,13 +61,13 @@ struct EmptyMatcher { phantom: PhantomData, } -impl Matcher for EmptyMatcher +impl<'a, T: Debug + ?Sized + 'a> Matcher<'a> for EmptyMatcher where - for<'a> &'a T: IntoIterator, + &'a T: IntoIterator, { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches(&self, actual: &'a T) -> MatcherResult { actual.into_iter().next().is_none().into() } diff --git a/googletest/src/matchers/eq_deref_of_matcher.rs b/googletest/src/matchers/eq_deref_of_matcher.rs index 3a6cfced..5029e47a 100644 --- a/googletest/src/matchers/eq_deref_of_matcher.rs +++ b/googletest/src/matchers/eq_deref_of_matcher.rs @@ -65,7 +65,7 @@ pub struct EqDerefOfMatcher { phantom: PhantomData, } -impl Matcher for EqDerefOfMatcher +impl Matcher<'_> for EqDerefOfMatcher where ActualT: Debug + ?Sized, ExpectedRefT: Deref + Debug, diff --git a/googletest/src/matchers/eq_matcher.rs b/googletest/src/matchers/eq_matcher.rs index 26fea831..78a00664 100644 --- a/googletest/src/matchers/eq_matcher.rs +++ b/googletest/src/matchers/eq_matcher.rs @@ -83,7 +83,7 @@ pub struct EqMatcher { phantom: PhantomData, } -impl> Matcher for EqMatcher { +impl> Matcher<'_> for EqMatcher { type ActualT = A; fn matches(&self, actual: &A) -> MatcherResult { diff --git a/googletest/src/matchers/err_matcher.rs b/googletest/src/matchers/err_matcher.rs index 3b10de4f..73ad7417 100644 --- a/googletest/src/matchers/err_matcher.rs +++ b/googletest/src/matchers/err_matcher.rs @@ -38,9 +38,9 @@ use std::{fmt::Debug, marker::PhantomData}; /// # should_fail_1().unwrap_err(); /// # should_fail_2().unwrap_err(); /// ``` -pub fn err( - inner: impl Matcher, -) -> impl Matcher> { +pub fn err<'a, T: Debug, E: Debug>( + inner: impl Matcher<'a, ActualT = E>, +) -> impl Matcher<'a, ActualT = std::result::Result> { ErrMatcher:: { inner, phantom_t: Default::default(), phantom_e: Default::default() } } @@ -50,16 +50,16 @@ struct ErrMatcher { phantom_e: PhantomData, } -impl> Matcher +impl<'a, T: Debug, E: Debug, InnerMatcherT: Matcher<'a, ActualT = E>> Matcher<'a> for ErrMatcher { type ActualT = std::result::Result; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches(&self, actual: &'a Self::ActualT) -> MatcherResult { actual.as_ref().err().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch) } - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match(&self, actual: &'a Self::ActualT) -> Description { match actual { Err(e) => { Description::new().text("which is an error").nested(self.inner.explain_match(e)) @@ -91,8 +91,8 @@ mod tests { #[test] fn err_matches_result_with_err_value() -> Result<()> { - let matcher = err(eq(1)); let value: std::result::Result = Err(1); + let matcher = err(eq(1)); let result = matcher.matches(&value); @@ -101,8 +101,8 @@ mod tests { #[test] fn err_does_not_match_result_with_wrong_err_value() -> Result<()> { - let matcher = err(eq(1)); let value: std::result::Result = Err(0); + let matcher = err(eq(1)); let result = matcher.matches(&value); @@ -111,8 +111,8 @@ mod tests { #[test] fn err_does_not_match_result_with_ok() -> Result<()> { - let matcher = err(eq(1)); let value: std::result::Result = Ok(1); + let matcher = err(eq(1)); let result = matcher.matches(&value); diff --git a/googletest/src/matchers/field_matcher.rs b/googletest/src/matchers/field_matcher.rs index fc37ce66..be3fe185 100644 --- a/googletest/src/matchers/field_matcher.rs +++ b/googletest/src/matchers/field_matcher.rs @@ -152,26 +152,26 @@ pub mod internal { /// /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] - pub fn field_matcher>( - field_accessor: fn(&OuterT) -> Option<&InnerT>, + pub fn field_matcher<'a, OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<'a, ActualT = InnerT>>( + field_accessor: fn(&'a OuterT) -> Option<&'a InnerT>, field_path: &'static str, inner: InnerMatcher, - ) -> impl Matcher { + ) -> impl Matcher<'a, ActualT = OuterT> { FieldMatcher { field_accessor, field_path, inner } } - struct FieldMatcher { - field_accessor: fn(&OuterT) -> Option<&InnerT>, + struct FieldMatcher<'a, OuterT, InnerT, InnerMatcher> { + field_accessor: fn(&'a OuterT) -> Option<&'a InnerT>, field_path: &'static str, inner: InnerMatcher, } - impl> Matcher - for FieldMatcher + impl<'a, OuterT: Debug, InnerT: Debug, InnerMatcher: Matcher<'a, ActualT = InnerT>> Matcher<'a> + for FieldMatcher<'a, OuterT, InnerT, InnerMatcher> { type ActualT = OuterT; - fn matches(&self, actual: &OuterT) -> MatcherResult { + fn matches(&self, actual: &'a OuterT) -> MatcherResult { if let Some(value) = (self.field_accessor)(actual) { self.inner.matches(value) } else { @@ -179,7 +179,7 @@ pub mod internal { } } - fn explain_match(&self, actual: &OuterT) -> Description { + fn explain_match(&self, actual: &'a OuterT) -> Description { if let Some(actual) = (self.field_accessor)(actual) { format!( "which has field `{}`, {}", diff --git a/googletest/src/matchers/ge_matcher.rs b/googletest/src/matchers/ge_matcher.rs index cb2a91ff..76af26ec 100644 --- a/googletest/src/matchers/ge_matcher.rs +++ b/googletest/src/matchers/ge_matcher.rs @@ -74,9 +74,9 @@ use std::{fmt::Debug, marker::PhantomData}; /// /// You can find the standard library `PartialOrd` implementation in /// -pub fn ge, ExpectedT: Debug>( +pub fn ge<'a, ActualT: Debug + PartialOrd, ExpectedT: Debug>( expected: ExpectedT, -) -> impl Matcher { +) -> impl Matcher<'a, ActualT = ActualT> { GeMatcher:: { expected, phantom: Default::default() } } @@ -85,7 +85,7 @@ struct GeMatcher { phantom: PhantomData, } -impl, ExpectedT: Debug> Matcher +impl, ExpectedT: Debug> Matcher<'_> for GeMatcher { type ActualT = ActualT; diff --git a/googletest/src/matchers/gt_matcher.rs b/googletest/src/matchers/gt_matcher.rs index b52c6e3d..120566cf 100644 --- a/googletest/src/matchers/gt_matcher.rs +++ b/googletest/src/matchers/gt_matcher.rs @@ -74,18 +74,18 @@ use std::{fmt::Debug, marker::PhantomData}; /// /// You can find the standard library `PartialOrd` implementation in /// -pub fn gt, ExpectedT: Debug>( +pub fn gt<'a, ActualT: Debug + PartialOrd, ExpectedT: Debug>( expected: ExpectedT, -) -> impl Matcher { +) -> GtMatcher { GtMatcher:: { expected, phantom: Default::default() } } -struct GtMatcher { +pub struct GtMatcher { expected: ExpectedT, phantom: PhantomData, } -impl, ExpectedT: Debug> Matcher +impl, ExpectedT: Debug> Matcher<'_> for GtMatcher { type ActualT = ActualT; diff --git a/googletest/src/matchers/has_entry_matcher.rs b/googletest/src/matchers/has_entry_matcher.rs index 7f9d2add..c0349ac6 100644 --- a/googletest/src/matchers/has_entry_matcher.rs +++ b/googletest/src/matchers/has_entry_matcher.rs @@ -62,10 +62,10 @@ use std::marker::PhantomData; /// However, `has_entry` will offer somewhat better diagnostic messages in the /// case of assertion failure. And it avoid the extra allocation hidden in the /// code above. -pub fn has_entry>( +pub fn has_entry<'a, KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<'a, ActualT = ValueT>>( key: KeyT, inner: MatcherT, -) -> impl Matcher> { +) -> impl Matcher<'a, ActualT = HashMap> { HasEntryMatcher { key, inner, phantom: Default::default() } } @@ -75,12 +75,12 @@ struct HasEntryMatcher { phantom: PhantomData, } -impl> Matcher +impl<'a, KeyT: Debug + Eq + Hash, ValueT: Debug, MatcherT: Matcher<'a, ActualT = ValueT>> Matcher<'a> for HasEntryMatcher { type ActualT = HashMap; - fn matches(&self, actual: &HashMap) -> MatcherResult { + fn matches(&self, actual: &'a HashMap) -> MatcherResult { if let Some(value) = actual.get(&self.key) { self.inner.matches(value) } else { @@ -88,7 +88,7 @@ impl } } - fn explain_match(&self, actual: &HashMap) -> Description { + fn explain_match(&self, actual: &'a HashMap) -> Description { if let Some(value) = actual.get(&self.key) { format!( "which contains key {:?}, but is mapped to value {:#?}, {}", diff --git a/googletest/src/matchers/is_encoded_string_matcher.rs b/googletest/src/matchers/is_encoded_string_matcher.rs index d2fb2596..40702f82 100644 --- a/googletest/src/matchers/is_encoded_string_matcher.rs +++ b/googletest/src/matchers/is_encoded_string_matcher.rs @@ -50,26 +50,26 @@ use std::{fmt::Debug, marker::PhantomData}; /// ``` pub fn is_utf8_string<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT>( inner: InnerMatcherT, -) -> impl Matcher +) -> IsEncodedStringMatcher where - InnerMatcherT: Matcher, + InnerMatcherT: for<'b> Matcher<'b, ActualT = String>, { IsEncodedStringMatcher { inner, phantom: Default::default() } } -struct IsEncodedStringMatcher { +pub struct IsEncodedStringMatcher { inner: InnerMatcherT, phantom: PhantomData, } -impl<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT> Matcher +impl<'a, ActualT: AsRef<[u8]> + Debug + 'a, InnerMatcherT> Matcher<'a> for IsEncodedStringMatcher where - InnerMatcherT: Matcher, + InnerMatcherT: for<'b> Matcher<'b, ActualT = String>, { type ActualT = ActualT; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches(&self, actual: &'a Self::ActualT) -> MatcherResult { String::from_utf8(actual.as_ref().to_vec()) .map(|s| self.inner.matches(&s)) .unwrap_or(MatcherResult::NoMatch) @@ -90,7 +90,7 @@ where } } - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match(&self, actual: &'a Self::ActualT) -> Description { match String::from_utf8(actual.as_ref().to_vec()) { Ok(s) => { format!("which is a UTF-8 encoded string {}", self.inner.explain_match(&s)).into() @@ -152,7 +152,8 @@ mod tests { #[test] fn has_correct_explanation_in_matched_case() -> Result<()> { - let explanation = is_utf8_string(eq("A string")).explain_match(&"A string".as_bytes()); + let actual = "A string".as_bytes(); + let explanation = is_utf8_string(eq("A string")).explain_match(&actual); verify_that!( explanation, @@ -169,8 +170,8 @@ mod tests { #[test] fn has_correct_explanation_when_inner_matcher_does_not_match() -> Result<()> { - let explanation = - is_utf8_string(eq("A string")).explain_match(&"Another string".as_bytes()); + let actual = "Another string".as_bytes(); + let explanation = is_utf8_string(eq("A string")).explain_match(&actual); verify_that!( explanation, diff --git a/googletest/src/matchers/is_matcher.rs b/googletest/src/matchers/is_matcher.rs index 336ce536..e09558f1 100644 --- a/googletest/src/matchers/is_matcher.rs +++ b/googletest/src/matchers/is_matcher.rs @@ -25,25 +25,25 @@ use std::{fmt::Debug, marker::PhantomData}; /// The returned matcher produces a description prefixed by the string /// `description`. This is useful in contexts where the test assertion failure /// output must include the additional description. -pub fn is<'a, ActualT: Debug + 'a, InnerMatcherT: Matcher + 'a>( - description: &'a str, +pub fn is<'d, 'a, ActualT: Debug + 'a + 'd, InnerMatcherT: Matcher<'a, ActualT = ActualT> + 'd>( + description: &'d str, inner: InnerMatcherT, -) -> impl Matcher + 'a { +) -> impl Matcher<'a, ActualT = ActualT> + 'd { IsMatcher { description, inner, phantom: Default::default() } } -struct IsMatcher<'a, ActualT, InnerMatcherT> { - description: &'a str, +struct IsMatcher<'d, ActualT, InnerMatcherT> { + description: &'d str, inner: InnerMatcherT, phantom: PhantomData, } -impl<'a, ActualT: Debug, InnerMatcherT: Matcher> Matcher - for IsMatcher<'a, ActualT, InnerMatcherT> +impl<'d, 'a, ActualT: Debug, InnerMatcherT: Matcher<'a, ActualT = ActualT>> Matcher<'a> + for IsMatcher<'d, ActualT, InnerMatcherT> { type ActualT = ActualT; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches(&self, actual: &'a Self::ActualT) -> MatcherResult { self.inner.matches(actual) } @@ -64,7 +64,7 @@ impl<'a, ActualT: Debug, InnerMatcherT: Matcher> Matcher } } - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match(&self, actual: &'a Self::ActualT) -> Description { self.inner.explain_match(actual) } } diff --git a/googletest/src/matchers/is_nan_matcher.rs b/googletest/src/matchers/is_nan_matcher.rs index 0af4338d..6468ae7b 100644 --- a/googletest/src/matchers/is_nan_matcher.rs +++ b/googletest/src/matchers/is_nan_matcher.rs @@ -20,13 +20,13 @@ use num_traits::float::Float; use std::{fmt::Debug, marker::PhantomData}; /// Matches a floating point value which is NaN. -pub fn is_nan() -> impl Matcher { +pub fn is_nan<'a, T: Float + Debug>() -> impl Matcher<'a, ActualT = T> { IsNanMatcher::(Default::default()) } struct IsNanMatcher(PhantomData); -impl Matcher for IsNanMatcher { +impl Matcher<'_> for IsNanMatcher { type ActualT = T; fn matches(&self, actual: &T) -> MatcherResult { diff --git a/googletest/src/matchers/le_matcher.rs b/googletest/src/matchers/le_matcher.rs index cfc57818..56c8aa2f 100644 --- a/googletest/src/matchers/le_matcher.rs +++ b/googletest/src/matchers/le_matcher.rs @@ -74,9 +74,9 @@ use std::{fmt::Debug, marker::PhantomData}; /// /// You can find the standard library `PartialOrd` implementation in /// -pub fn le, ExpectedT: Debug>( +pub fn le<'a, ActualT: Debug + PartialOrd, ExpectedT: Debug>( expected: ExpectedT, -) -> impl Matcher { +) -> impl Matcher<'a, ActualT = ActualT> { LeMatcher:: { expected, phantom: Default::default() } } @@ -85,7 +85,7 @@ struct LeMatcher { phantom: PhantomData, } -impl, ExpectedT: Debug> Matcher +impl, ExpectedT: Debug> Matcher<'_> for LeMatcher { type ActualT = ActualT; diff --git a/googletest/src/matchers/len_matcher.rs b/googletest/src/matchers/len_matcher.rs index be903c99..0f03a51a 100644 --- a/googletest/src/matchers/len_matcher.rs +++ b/googletest/src/matchers/len_matcher.rs @@ -49,9 +49,10 @@ use std::{fmt::Debug, marker::PhantomData}; /// # } /// # should_pass().unwrap(); /// ``` -pub fn len>(expected: E) -> impl Matcher +pub fn len<'a, T: Debug + ?Sized + 'a, E>(expected: E) -> impl Matcher<'a, ActualT = T> where - for<'a> &'a T: IntoIterator, + &'a T: IntoIterator, + E: for<'b> Matcher<'b, ActualT = usize> { LenMatcher { expected, phantom: Default::default() } } @@ -61,13 +62,14 @@ struct LenMatcher { phantom: PhantomData, } -impl> Matcher for LenMatcher +impl<'a, T: Debug + ?Sized + 'a, E> Matcher<'a> for LenMatcher where - for<'a> &'a T: IntoIterator, + &'a T: IntoIterator, + E: for<'b> Matcher<'b, ActualT = usize> { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches(&self, actual: &'a T) -> MatcherResult { self.expected.matches(&count_elements(actual)) } @@ -83,7 +85,7 @@ where } } - fn explain_match(&self, actual: &T) -> Description { + fn explain_match(&self, actual: &'a T) -> Description { let actual_size = count_elements(actual); format!("which has length {}, {}", actual_size, self.expected.explain_match(&actual_size)) .into() @@ -179,7 +181,7 @@ mod tests { #[test] fn len_matcher_explain_match() -> Result<()> { struct TestMatcher(PhantomData); - impl Matcher for TestMatcher { + impl Matcher<'_> for TestMatcher { type ActualT = T; fn matches(&self, _: &T) -> MatcherResult { diff --git a/googletest/src/matchers/lt_matcher.rs b/googletest/src/matchers/lt_matcher.rs index 08bc563a..c93833b7 100644 --- a/googletest/src/matchers/lt_matcher.rs +++ b/googletest/src/matchers/lt_matcher.rs @@ -74,9 +74,9 @@ use std::{fmt::Debug, marker::PhantomData}; /// /// You can find the standard library `PartialOrd` implementation in /// -pub fn lt, ExpectedT: Debug>( +pub fn lt<'a, ActualT: Debug + PartialOrd, ExpectedT: Debug>( expected: ExpectedT, -) -> impl Matcher { +) -> impl Matcher<'a, ActualT = ActualT> { LtMatcher:: { expected, phantom: Default::default() } } @@ -85,7 +85,7 @@ struct LtMatcher { phantom: PhantomData, } -impl, ExpectedT: Debug> Matcher +impl, ExpectedT: Debug> Matcher<'_> for LtMatcher { type ActualT = ActualT; diff --git a/googletest/src/matchers/matches_pattern.rs b/googletest/src/matchers/matches_pattern.rs index 106a5d70..bcf5dd7c 100644 --- a/googletest/src/matchers/matches_pattern.rs +++ b/googletest/src/matchers/matches_pattern.rs @@ -230,7 +230,7 @@ /// Unfortunately, this matcher does *not* work with methods returning string /// slices: /// -/// ```compile_fail +/// ``` /// # use googletest::prelude::*; /// # #[derive(Debug)] /// pub struct MyStruct { diff --git a/googletest/src/matchers/matches_regex_matcher.rs b/googletest/src/matchers/matches_regex_matcher.rs index 32b053b4..1813cdbe 100644 --- a/googletest/src/matchers/matches_regex_matcher.rs +++ b/googletest/src/matchers/matches_regex_matcher.rs @@ -84,7 +84,7 @@ pub struct MatchesRegexMatcher> { phantom: PhantomData, } -impl Matcher for MatchesRegexMatcher +impl Matcher<'_> for MatchesRegexMatcher where PatternT: Deref, ActualT: AsRef + Debug + ?Sized, diff --git a/googletest/src/matchers/near_matcher.rs b/googletest/src/matchers/near_matcher.rs index ca7cbdf6..2fbe9e30 100644 --- a/googletest/src/matchers/near_matcher.rs +++ b/googletest/src/matchers/near_matcher.rs @@ -166,7 +166,7 @@ impl NearMatcher { } } -impl Matcher for NearMatcher { +impl Matcher<'_> for NearMatcher { type ActualT = T; fn matches(&self, actual: &T) -> MatcherResult { diff --git a/googletest/src/matchers/none_matcher.rs b/googletest/src/matchers/none_matcher.rs index af289327..67b44227 100644 --- a/googletest/src/matchers/none_matcher.rs +++ b/googletest/src/matchers/none_matcher.rs @@ -32,7 +32,7 @@ use std::marker::PhantomData; /// # should_pass().unwrap(); /// # should_fail().unwrap_err(); /// ``` -pub fn none() -> impl Matcher> { +pub fn none<'a, T: Debug>() -> impl Matcher<'a, ActualT = Option> { NoneMatcher:: { phantom: Default::default() } } @@ -40,7 +40,7 @@ struct NoneMatcher { phantom: PhantomData, } -impl Matcher for NoneMatcher { +impl Matcher<'_> for NoneMatcher { type ActualT = Option; fn matches(&self, actual: &Option) -> MatcherResult { diff --git a/googletest/src/matchers/not_matcher.rs b/googletest/src/matchers/not_matcher.rs index f03d4cec..0434b171 100644 --- a/googletest/src/matchers/not_matcher.rs +++ b/googletest/src/matchers/not_matcher.rs @@ -33,28 +33,30 @@ use std::{fmt::Debug, marker::PhantomData}; /// # should_pass().unwrap(); /// # should_fail().unwrap_err(); /// ``` -pub fn not>( +pub fn not<'a, T: Debug, InnerMatcherT: Matcher<'a, ActualT = T>>( inner: InnerMatcherT, -) -> impl Matcher { +) -> NotMatcher { NotMatcher:: { inner, phantom: Default::default() } } -struct NotMatcher { +pub struct NotMatcher { inner: InnerMatcherT, phantom: PhantomData, } -impl> Matcher for NotMatcher { +impl<'a, T: Debug, InnerMatcherT: Matcher<'a, ActualT = T>> Matcher<'a> + for NotMatcher +{ type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches(&self, actual: &'a T) -> MatcherResult { match self.inner.matches(actual) { MatcherResult::Match => MatcherResult::NoMatch, MatcherResult::NoMatch => MatcherResult::Match, } } - fn explain_match(&self, actual: &T) -> Description { + fn explain_match(&self, actual: &'a T) -> Description { self.inner.explain_match(actual) } diff --git a/googletest/src/matchers/ok_matcher.rs b/googletest/src/matchers/ok_matcher.rs index 8425b93a..fdaa4d8f 100644 --- a/googletest/src/matchers/ok_matcher.rs +++ b/googletest/src/matchers/ok_matcher.rs @@ -38,9 +38,9 @@ use std::{fmt::Debug, marker::PhantomData}; /// # should_fail_1().unwrap_err(); /// # should_fail_2().unwrap_err(); /// ``` -pub fn ok( - inner: impl Matcher, -) -> impl Matcher> { +pub fn ok<'a, T: Debug, E: Debug>( + inner: impl Matcher<'a, ActualT = T>, +) -> impl Matcher<'a, ActualT = std::result::Result> { OkMatcher:: { inner, phantom_t: Default::default(), phantom_e: Default::default() } } @@ -50,16 +50,16 @@ struct OkMatcher { phantom_e: PhantomData, } -impl> Matcher +impl<'a, T: Debug, E: Debug, InnerMatcherT: Matcher<'a, ActualT = T>> Matcher<'a> for OkMatcher { type ActualT = std::result::Result; - fn matches(&self, actual: &Self::ActualT) -> MatcherResult { + fn matches(&self, actual: &'a Self::ActualT) -> MatcherResult { actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch) } - fn explain_match(&self, actual: &Self::ActualT) -> Description { + fn explain_match(&self, actual: &'a Self::ActualT) -> Description { match actual { Ok(o) => { Description::new().text("which is a success").nested(self.inner.explain_match(o)) @@ -93,8 +93,8 @@ mod tests { #[test] fn ok_matches_result_with_value() -> Result<()> { - let matcher = ok(eq(1)); let value: std::result::Result = Ok(1); + let matcher = ok(eq(1)); let result = matcher.matches(&value); @@ -103,8 +103,8 @@ mod tests { #[test] fn ok_does_not_match_result_with_wrong_value() -> Result<()> { - let matcher = ok(eq(1)); let value: std::result::Result = Ok(0); + let matcher = ok(eq(1)); let result = matcher.matches(&value); @@ -113,8 +113,8 @@ mod tests { #[test] fn ok_does_not_match_result_with_err() -> Result<()> { - let matcher = ok(eq(1)); let value: std::result::Result = Err(1); + let matcher = ok(eq(1)); let result = matcher.matches(&value); diff --git a/googletest/src/matchers/points_to_matcher.rs b/googletest/src/matchers/points_to_matcher.rs index 2c516d0c..d64459c5 100644 --- a/googletest/src/matchers/points_to_matcher.rs +++ b/googletest/src/matchers/points_to_matcher.rs @@ -32,12 +32,12 @@ use std::ops::Deref; /// # } /// # should_pass().unwrap(); /// ``` -pub fn points_to( +pub fn points_to<'a, ExpectedT, MatcherT, ActualT>( expected: MatcherT, -) -> impl Matcher +) -> impl Matcher<'a, ActualT = ActualT> where - ExpectedT: Debug, - MatcherT: Matcher, + ExpectedT: Debug +'a, + MatcherT: Matcher<'a, ActualT = ExpectedT>, ActualT: Deref + Debug + ?Sized, { PointsToMatcher { expected, phantom: Default::default() } @@ -48,19 +48,19 @@ struct PointsToMatcher { phantom: PhantomData, } -impl Matcher for PointsToMatcher +impl<'a, ExpectedT, MatcherT, ActualT> Matcher<'a> for PointsToMatcher where - ExpectedT: Debug, - MatcherT: Matcher, + ExpectedT: Debug +'a, + MatcherT: Matcher<'a, ActualT = ExpectedT>, ActualT: Deref + Debug + ?Sized, { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches(&self, actual: &'a ActualT) -> MatcherResult { self.expected.matches(actual.deref()) } - fn explain_match(&self, actual: &ActualT) -> Description { + fn explain_match(&self, actual: &'a ActualT) -> Description { self.expected.explain_match(actual.deref()) } diff --git a/googletest/src/matchers/pointwise_matcher.rs b/googletest/src/matchers/pointwise_matcher.rs index 01e70c0d..34341dfd 100644 --- a/googletest/src/matchers/pointwise_matcher.rs +++ b/googletest/src/matchers/pointwise_matcher.rs @@ -171,14 +171,14 @@ pub mod internal { } } - impl, ContainerT: ?Sized + Debug> Matcher - for PointwiseMatcher + impl<'b, T: Debug + 'b, MatcherT: Matcher<'b, ActualT = T>, ContainerT: ?Sized + Debug + 'b> + Matcher<'b> for PointwiseMatcher where - for<'b> &'b ContainerT: IntoIterator, + &'b ContainerT: IntoIterator, { type ActualT = ContainerT; - fn matches(&self, actual: &ContainerT) -> MatcherResult { + fn matches(&self, actual: &'b ContainerT) -> MatcherResult { let mut zipped_iterator = zip(actual.into_iter(), self.matchers.iter()); for (element, matcher) in zipped_iterator.by_ref() { if matcher.matches(element).is_no_match() { @@ -192,7 +192,7 @@ pub mod internal { } } - fn explain_match(&self, actual: &ContainerT) -> Description { + fn explain_match(&self, actual: &'b ContainerT) -> Description { // TODO(b/260819741) This code duplicates elements_are_matcher.rs. Consider // extract as a separate library. (or implement pointwise! with // elements_are) diff --git a/googletest/src/matchers/predicate_matcher.rs b/googletest/src/matchers/predicate_matcher.rs index 5bc067d7..6d11e557 100644 --- a/googletest/src/matchers/predicate_matcher.rs +++ b/googletest/src/matchers/predicate_matcher.rs @@ -124,13 +124,13 @@ where #[doc(hidden)] pub struct NoDescription; -impl Matcher for PredicateMatcher +impl<'a, T: Debug + 'a, P> Matcher<'a> for PredicateMatcher where - for<'a> P: Fn(&'a T) -> bool, + P: Fn(&'a T) -> bool, { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches(&self, actual: &'a T) -> MatcherResult { (self.predicate)(actual).into() } @@ -142,14 +142,14 @@ where } } -impl Matcher +impl<'a, T: Debug + 'a, P, D1: PredicateDescription, D2: PredicateDescription> Matcher<'a> for PredicateMatcher where - for<'a> P: Fn(&'a T) -> bool, + P: Fn(&'a T) -> bool, { type ActualT = T; - fn matches(&self, actual: &T) -> MatcherResult { + fn matches(&self, actual: &'a T) -> MatcherResult { (self.predicate)(actual).into() } @@ -168,7 +168,7 @@ mod tests { use crate::prelude::*; // Simple matcher with a description - fn is_odd() -> impl Matcher { + fn is_odd<'a>() -> impl Matcher<'a, ActualT = i32> { predicate(|x| x % 2 == 1).with_description("is odd", "is even") } @@ -188,7 +188,7 @@ mod tests { } // Simple Matcher without description - fn is_even() -> impl Matcher { + fn is_even<'a>() -> impl Matcher<'a, ActualT = i32> { predicate(|x| x % 2 == 0) } diff --git a/googletest/src/matchers/property_matcher.rs b/googletest/src/matchers/property_matcher.rs index 19b48627..35827714 100644 --- a/googletest/src/matchers/property_matcher.rs +++ b/googletest/src/matchers/property_matcher.rs @@ -142,11 +142,14 @@ pub mod internal { /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] - pub fn property_matcher>( - extractor: impl Fn(&OuterT) -> InnerT, + pub fn property_matcher<'a, OuterT: Debug + 'a, InnerT: Debug, MatcherT>( + extractor: impl Fn(&'a OuterT) -> InnerT, property_desc: &'static str, inner: MatcherT, - ) -> impl Matcher { + ) -> impl Matcher<'a, ActualT = OuterT> + where + MatcherT: for<'b> Matcher<'b, ActualT = InnerT>, + { PropertyMatcher { extractor, property_desc, inner, phantom: Default::default() } } @@ -157,16 +160,17 @@ pub mod internal { phantom: PhantomData, } - impl Matcher for PropertyMatcher + impl<'a, InnerT, OuterT, ExtractorT, MatcherT> Matcher<'a> + for PropertyMatcher where InnerT: Debug, - OuterT: Debug, - ExtractorT: Fn(&OuterT) -> InnerT, - MatcherT: Matcher, + OuterT: Debug + 'a, + ExtractorT: Fn(&'a OuterT) -> InnerT, + MatcherT: for<'b> Matcher<'b, ActualT = InnerT>, { type ActualT = OuterT; - fn matches(&self, actual: &OuterT) -> MatcherResult { + fn matches(&self, actual: &'a OuterT) -> MatcherResult { self.inner.matches(&(self.extractor)(actual)) } @@ -179,7 +183,7 @@ pub mod internal { .into() } - fn explain_match(&self, actual: &OuterT) -> Description { + fn explain_match(&self, actual: &'a OuterT) -> Description { let actual_inner = (self.extractor)(actual); format!( "whose property `{}` is `{:#?}`, {}", @@ -193,31 +197,31 @@ pub mod internal { /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] - pub fn property_ref_matcher( - extractor: fn(&OuterT) -> &InnerT, + pub fn property_ref_matcher<'a, OuterT, InnerT, MatcherT>( + extractor: fn(&'a OuterT) -> &'a InnerT, property_desc: &'static str, inner: MatcherT, - ) -> impl Matcher + ) -> impl Matcher<'a, ActualT = OuterT> where OuterT: Debug, InnerT: Debug + ?Sized, - MatcherT: Matcher, + MatcherT: Matcher<'a, ActualT = InnerT>, { PropertyRefMatcher { extractor, property_desc, inner } } - struct PropertyRefMatcher { - extractor: fn(&OuterT) -> &InnerT, + struct PropertyRefMatcher<'a, InnerT: ?Sized, OuterT, MatcherT> { + extractor: fn(&'a OuterT) -> &'a InnerT, property_desc: &'static str, inner: MatcherT, } - impl> Matcher - for PropertyRefMatcher + impl<'a, InnerT: Debug + ?Sized, OuterT: Debug, MatcherT: Matcher<'a, ActualT = InnerT>> + Matcher<'a> for PropertyRefMatcher<'a, InnerT, OuterT, MatcherT> { type ActualT = OuterT; - fn matches(&self, actual: &OuterT) -> MatcherResult { + fn matches(&self, actual: &'a OuterT) -> MatcherResult { self.inner.matches((self.extractor)(actual)) } @@ -230,7 +234,7 @@ pub mod internal { .into() } - fn explain_match(&self, actual: &OuterT) -> Description { + fn explain_match(&self, actual: &'a OuterT) -> Description { let actual_inner = (self.extractor)(actual); format!( "whose property `{}` is `{:#?}`, {}", diff --git a/googletest/src/matchers/some_matcher.rs b/googletest/src/matchers/some_matcher.rs index 905aa17d..5def2d4d 100644 --- a/googletest/src/matchers/some_matcher.rs +++ b/googletest/src/matchers/some_matcher.rs @@ -38,7 +38,9 @@ use std::{fmt::Debug, marker::PhantomData}; /// # should_fail_1().unwrap_err(); /// # should_fail_2().unwrap_err(); /// ``` -pub fn some(inner: impl Matcher) -> impl Matcher> { +pub fn some<'a, T: Debug>( + inner: impl Matcher<'a, ActualT = T>, +) -> impl Matcher<'a, ActualT = Option> { SomeMatcher { inner, phantom: Default::default() } } @@ -47,14 +49,14 @@ struct SomeMatcher { phantom: PhantomData, } -impl> Matcher for SomeMatcher { +impl<'a, T: Debug, InnerMatcherT: Matcher<'a, ActualT = T>> Matcher<'a> for SomeMatcher { type ActualT = Option; - fn matches(&self, actual: &Option) -> MatcherResult { + fn matches(&self, actual: &'a Option) -> MatcherResult { actual.as_ref().map(|v| self.inner.matches(v)).unwrap_or(MatcherResult::NoMatch) } - fn explain_match(&self, actual: &Option) -> Description { + fn explain_match(&self, actual: &'a Option) -> Description { match (self.matches(actual), actual) { (_, Some(t)) => { Description::new().text("which has a value").nested(self.inner.explain_match(t)) diff --git a/googletest/src/matchers/str_matcher.rs b/googletest/src/matchers/str_matcher.rs index f4f158e5..7aa650f7 100644 --- a/googletest/src/matchers/str_matcher.rs +++ b/googletest/src/matchers/str_matcher.rs @@ -59,7 +59,7 @@ use std::ops::Deref; /// > and expected values when matching strings while /// > [`ignoring_ascii_case`][StrMatcherConfigurator::ignoring_ascii_case] is /// > set. -pub fn contains_substring(expected: T) -> StrMatcher { +pub fn contains_substring(expected: T) -> StrMatcher { StrMatcher { configuration: Configuration { mode: MatchMode::Contains, ..Default::default() }, expected, @@ -99,7 +99,7 @@ pub fn contains_substring(expected: T) -> StrMatcher { /// /// See the [`StrMatcherConfigurator`] extension trait for more options on how /// the string is matched. -pub fn starts_with(expected: T) -> StrMatcher { +pub fn starts_with(expected: T) -> StrMatcher { StrMatcher { configuration: Configuration { mode: MatchMode::StartsWith, ..Default::default() }, expected, @@ -139,7 +139,7 @@ pub fn starts_with(expected: T) -> StrMatcher { /// /// See the [`StrMatcherConfigurator`] extension trait for more options on how /// the string is matched. -pub fn ends_with(expected: T) -> StrMatcher { +pub fn ends_with(expected: T) -> StrMatcher { StrMatcher { configuration: Configuration { mode: MatchMode::EndsWith, ..Default::default() }, expected, @@ -152,7 +152,7 @@ pub fn ends_with(expected: T) -> StrMatcher { /// Matchers which match against string values and, through configuration, /// specialise to [`StrMatcher`] implement this trait. That includes /// [`EqMatcher`] and [`StrMatcher`]. -pub trait StrMatcherConfigurator { +pub trait StrMatcherConfigurator { /// Configures the matcher to ignore any leading whitespace in either the /// actual or the expected value. /// @@ -171,7 +171,7 @@ pub trait StrMatcherConfigurator { /// When all other configuration options are left as the defaults, this is /// equivalent to invoking [`str::trim_start`] on both the expected and /// actual value. - fn ignoring_leading_whitespace(self) -> StrMatcher; + fn ignoring_leading_whitespace(self) -> StrMatcher; /// Configures the matcher to ignore any trailing whitespace in either the /// actual or the expected value. @@ -191,7 +191,7 @@ pub trait StrMatcherConfigurator { /// When all other configuration options are left as the defaults, this is /// equivalent to invoking [`str::trim_end`] on both the expected and /// actual value. - fn ignoring_trailing_whitespace(self) -> StrMatcher; + fn ignoring_trailing_whitespace(self) -> StrMatcher; /// Configures the matcher to ignore both leading and trailing whitespace in /// either the actual or the expected value. @@ -215,7 +215,7 @@ pub trait StrMatcherConfigurator { /// When all other configuration options are left as the defaults, this is /// equivalent to invoking [`str::trim`] on both the expected and actual /// value. - fn ignoring_outer_whitespace(self) -> StrMatcher; + fn ignoring_outer_whitespace(self) -> StrMatcher; /// Configures the matcher to ignore ASCII case when comparing values. /// @@ -237,7 +237,7 @@ pub trait StrMatcherConfigurator { /// /// This is **not guaranteed** to match strings with differing upper/lower /// case characters outside of the codepoints 0-127 covered by ASCII. - fn ignoring_ascii_case(self) -> StrMatcher; + fn ignoring_ascii_case(self) -> StrMatcher; /// Configures the matcher to match only strings which otherwise satisfy the /// conditions a number times matched by the matcher `times`. @@ -272,10 +272,12 @@ pub trait StrMatcherConfigurator { /// This is only meaningful when the matcher was constructed with /// [`contains_substring`]. This method will panic when it is used with any /// other matcher construction. - fn times( + fn times( self, - times: impl Matcher + 'static, - ) -> StrMatcher; + times: NewTimesMatcher, + ) -> StrMatcher + where + NewTimesMatcher: for<'a> Matcher<'a, ActualT = usize>; } /// A matcher which matches equality or containment of a string-like value in a @@ -287,13 +289,13 @@ pub trait StrMatcherConfigurator { /// * [`contains_substring`], /// * [`starts_with`], /// * [`ends_with`]. -pub struct StrMatcher { +pub struct StrMatcher { expected: ExpectedT, - configuration: Configuration, + configuration: Configuration, phantom: PhantomData, } -impl Matcher for StrMatcher +impl Matcher<'_> for StrMatcher where ExpectedT: Deref + Debug, ActualT: AsRef + Debug + ?Sized, @@ -313,10 +315,31 @@ where } } -impl>> - StrMatcherConfigurator for MatcherT +impl Matcher<'_> for StrMatcher +where + ExpectedT: Deref + Debug, + ActualT: AsRef + Debug + ?Sized, + TimesMatcher: for<'a> Matcher<'a, ActualT = usize>, { - fn ignoring_leading_whitespace(self) -> StrMatcher { + type ActualT = ActualT; + + fn matches(&self, actual: &ActualT) -> MatcherResult { + self.configuration.do_strings_match(self.expected.deref(), actual.as_ref()).into() + } + + fn describe(&self, matcher_result: MatcherResult) -> Description { + self.configuration.describe(matcher_result, self.expected.deref()) + } + + fn explain_match(&self, actual: &ActualT) -> Description { + self.configuration.explain_match(self.expected.deref(), actual.as_ref()) + } +} + +impl>> + StrMatcherConfigurator for MatcherT +{ + fn ignoring_leading_whitespace(self) -> StrMatcher { let existing = self.into(); StrMatcher { configuration: existing.configuration.ignoring_leading_whitespace(), @@ -324,7 +347,7 @@ impl>> } } - fn ignoring_trailing_whitespace(self) -> StrMatcher { + fn ignoring_trailing_whitespace(self) -> StrMatcher { let existing = self.into(); StrMatcher { configuration: existing.configuration.ignoring_trailing_whitespace(), @@ -332,41 +355,50 @@ impl>> } } - fn ignoring_outer_whitespace(self) -> StrMatcher { + fn ignoring_outer_whitespace(self) -> StrMatcher { let existing = self.into(); StrMatcher { configuration: existing.configuration.ignoring_outer_whitespace(), ..existing } } - fn ignoring_ascii_case(self) -> StrMatcher { + fn ignoring_ascii_case(self) -> StrMatcher { let existing = self.into(); StrMatcher { configuration: existing.configuration.ignoring_ascii_case(), ..existing } } - fn times( + fn times( self, - times: impl Matcher + 'static, - ) -> StrMatcher { + times: TimesMatcher, + ) -> StrMatcher + where + TimesMatcher: for<'a> Matcher<'a, ActualT = usize>, + { let existing = self.into(); if !matches!(existing.configuration.mode, MatchMode::Contains) { panic!("The times() configurator is only meaningful with contains_substring()."); } - StrMatcher { configuration: existing.configuration.times(times), ..existing } + StrMatcher { + configuration: existing.configuration.times(times), + expected: existing.expected, + phantom: existing.phantom, + } } } -impl> From> for StrMatcher { +impl> From> for StrMatcher { fn from(value: EqMatcher) -> Self { Self::with_default_config(value.expected) } } -impl> From> for StrMatcher { +impl> From> + for StrMatcher +{ fn from(value: EqDerefOfMatcher) -> Self { Self::with_default_config(value.expected) } } -impl StrMatcher { +impl StrMatcher { /// Returns a [`StrMatcher`] with a default configuration to match against /// the given expected value. /// @@ -382,14 +414,16 @@ impl StrMatcher { // parameterised, saving compilation time and binary size on monomorphisation. // // The default value represents exact equality of the strings. -struct Configuration { +struct Configuration { mode: MatchMode, ignore_leading_whitespace: bool, ignore_trailing_whitespace: bool, case_policy: CasePolicy, - times: Option>>, + times: TimesMatcher, } +pub struct NoTimesMatcher; + #[derive(Clone)] enum MatchMode { Equals, @@ -414,7 +448,7 @@ enum CasePolicy { IgnoreAscii, } -impl Configuration { +impl Configuration { // The entry point for all string matching. StrMatcher::matches redirects // immediately to this function. fn do_strings_match(&self, expected: &str, actual: &str) -> bool { @@ -457,14 +491,7 @@ impl Configuration { // Returns whether actual contains expected a number of times matched by the // matcher self.times. Does not take other configuration into account. fn does_containment_match(&self, actual: &str, expected: &str) -> bool { - if let Some(times) = self.times.as_ref() { - // Split returns an iterator over the "boundaries" left and right of the - // substring to be matched, of which there is one more than the number of - // substrings. - matches!(times.matches(&(actual.split(expected).count() - 1)), MatcherResult::Match) - } else { - actual.contains(expected) - } + actual.contains(expected) } // StrMatcher::describe redirects immediately to this function. @@ -480,9 +507,6 @@ impl Configuration { CasePolicy::Respect => {} CasePolicy::IgnoreAscii => addenda.push("ignoring ASCII case".into()), } - if let Some(times) = self.times.as_ref() { - addenda.push(format!("count {}", times.describe(matcher_result)).into()); - } let extra = if !addenda.is_empty() { format!(" ({})", addenda.join(", ")) } else { "".into() }; let match_mode_description = match self.mode { @@ -526,10 +550,6 @@ impl Configuration { return default_explanation; } - if self.times.is_some() { - // TODO - b/283448414 : Support StrMatcher with times. - return default_explanation; - } if matches!(self.case_policy, CasePolicy::IgnoreAscii) { // TODO - b/283448414 : Support StrMatcher with ignore ascii case policy. return default_explanation; @@ -570,19 +590,124 @@ impl Configuration { Self { case_policy: CasePolicy::IgnoreAscii, ..self } } - fn times(self, times: impl Matcher + 'static) -> Self { - Self { times: Some(Box::new(times)), ..self } + fn times(self, times: TimesMatcher) -> Configuration + where + TimesMatcher: for<'a> Matcher<'a, ActualT = usize>, + { + Configuration { + times, + mode: self.mode, + ignore_leading_whitespace: self.ignore_leading_whitespace, + ignore_trailing_whitespace: self.ignore_trailing_whitespace, + case_policy: self.case_policy, + } + } +} + +impl Configuration +where + TimesMatcher: for<'a> Matcher<'a, ActualT = usize>, +{ + // The entry point for all string matching. StrMatcher::matches redirects + // immediately to this function. + fn do_strings_match(&self, expected: &str, actual: &str) -> bool { + let (expected, actual) = + match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) { + (true, true) => (expected.trim(), actual.trim()), + (true, false) => (expected.trim_start(), actual.trim_start()), + (false, true) => (expected.trim_end(), actual.trim_end()), + (false, false) => (expected, actual), + }; + match self.mode { + MatchMode::Equals => match self.case_policy { + CasePolicy::Respect => expected == actual, + CasePolicy::IgnoreAscii => expected.eq_ignore_ascii_case(actual), + }, + MatchMode::Contains => match self.case_policy { + CasePolicy::Respect => self.does_containment_match(actual, expected), + CasePolicy::IgnoreAscii => self.does_containment_match( + actual.to_ascii_lowercase().as_str(), + expected.to_ascii_lowercase().as_str(), + ), + }, + MatchMode::StartsWith => match self.case_policy { + CasePolicy::Respect => actual.starts_with(expected), + CasePolicy::IgnoreAscii => { + actual.len() >= expected.len() + && actual[..expected.len()].eq_ignore_ascii_case(expected) + } + }, + MatchMode::EndsWith => match self.case_policy { + CasePolicy::Respect => actual.ends_with(expected), + CasePolicy::IgnoreAscii => { + actual.len() >= expected.len() + && actual[actual.len() - expected.len()..].eq_ignore_ascii_case(expected) + } + }, + } + } + + // Returns whether actual contains expected a number of times matched by the + // matcher self.times. Does not take other configuration into account. + fn does_containment_match(&self, actual: &str, expected: &str) -> bool { + // Split returns an iterator over the "boundaries" left and right of the + // substring to be matched, of which there is one more than the number of + // substrings. + matches!(self.times.matches(&(actual.split(expected).count() - 1)), MatcherResult::Match) + } + + // StrMatcher::describe redirects immediately to this function. + fn describe(&self, matcher_result: MatcherResult, expected: &str) -> Description { + let mut addenda: Vec> = Vec::with_capacity(3); + match (self.ignore_leading_whitespace, self.ignore_trailing_whitespace) { + (true, true) => addenda.push("ignoring leading and trailing whitespace".into()), + (true, false) => addenda.push("ignoring leading whitespace".into()), + (false, true) => addenda.push("ignoring trailing whitespace".into()), + (false, false) => {} + } + match self.case_policy { + CasePolicy::Respect => {} + CasePolicy::IgnoreAscii => addenda.push("ignoring ASCII case".into()), + } + addenda.push(format!("count {}", self.times.describe(matcher_result)).into()); + let extra = + if !addenda.is_empty() { format!(" ({})", addenda.join(", ")) } else { "".into() }; + let match_mode_description = match self.mode { + MatchMode::Equals => match matcher_result { + MatcherResult::Match => "is equal to", + MatcherResult::NoMatch => "isn't equal to", + }, + MatchMode::Contains => match matcher_result { + MatcherResult::Match => "contains a substring", + MatcherResult::NoMatch => "does not contain a substring", + }, + MatchMode::StartsWith => match matcher_result { + MatcherResult::Match => "starts with prefix", + MatcherResult::NoMatch => "does not start with", + }, + MatchMode::EndsWith => match matcher_result { + MatcherResult::Match => "ends with suffix", + MatcherResult::NoMatch => "does not end with", + }, + }; + format!("{match_mode_description} {expected:?}{extra}").into() + } + + fn explain_match(&self, expected: &str, actual: &str) -> Description { + // TODO - b/283448414 : Support StrMatcher with times. + format!("which {}", self.describe(self.do_strings_match(expected, actual).into(), expected)) + .into() } } -impl Default for Configuration { +impl Default for Configuration { fn default() -> Self { Self { mode: MatchMode::Equals, ignore_leading_whitespace: false, ignore_trailing_whitespace: false, case_policy: CasePolicy::Respect, - times: None, + times: NoTimesMatcher, } } } @@ -811,7 +936,7 @@ mod tests { #[test] fn describes_itself_for_matching_result() -> Result<()> { - let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string"); + let matcher: StrMatcher<&str, _, _> = StrMatcher::with_default_config("A string"); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), displays_as(eq("is equal to \"A string\"")) @@ -820,7 +945,7 @@ mod tests { #[test] fn describes_itself_for_non_matching_result() -> Result<()> { - let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string"); + let matcher: StrMatcher<&str, _, _> = StrMatcher::with_default_config("A string"); verify_that!( Matcher::describe(&matcher, MatcherResult::NoMatch), displays_as(eq("isn't equal to \"A string\"")) @@ -829,7 +954,7 @@ mod tests { #[test] fn describes_itself_for_matching_result_ignoring_leading_whitespace() -> Result<()> { - let matcher: StrMatcher<&str, _> = + let matcher: StrMatcher<&str, _, _> = StrMatcher::with_default_config("A string").ignoring_leading_whitespace(); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), @@ -839,7 +964,7 @@ mod tests { #[test] fn describes_itself_for_non_matching_result_ignoring_leading_whitespace() -> Result<()> { - let matcher: StrMatcher<&str, _> = + let matcher: StrMatcher<&str, _, _> = StrMatcher::with_default_config("A string").ignoring_leading_whitespace(); verify_that!( Matcher::describe(&matcher, MatcherResult::NoMatch), @@ -849,7 +974,7 @@ mod tests { #[test] fn describes_itself_for_matching_result_ignoring_trailing_whitespace() -> Result<()> { - let matcher: StrMatcher<&str, _> = + let matcher: StrMatcher<&str, _, _> = StrMatcher::with_default_config("A string").ignoring_trailing_whitespace(); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), @@ -860,7 +985,7 @@ mod tests { #[test] fn describes_itself_for_matching_result_ignoring_leading_and_trailing_whitespace() -> Result<()> { - let matcher: StrMatcher<&str, _> = + let matcher: StrMatcher<&str, _, _> = StrMatcher::with_default_config("A string").ignoring_outer_whitespace(); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), @@ -870,7 +995,7 @@ mod tests { #[test] fn describes_itself_for_matching_result_ignoring_ascii_case() -> Result<()> { - let matcher: StrMatcher<&str, _> = + let matcher: StrMatcher<&str, _, _> = StrMatcher::with_default_config("A string").ignoring_ascii_case(); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), @@ -881,7 +1006,7 @@ mod tests { #[test] fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace() -> Result<()> { - let matcher: StrMatcher<&str, _> = StrMatcher::with_default_config("A string") + let matcher: StrMatcher<&str, _, _> = StrMatcher::with_default_config("A string") .ignoring_leading_whitespace() .ignoring_ascii_case(); verify_that!( @@ -894,7 +1019,7 @@ mod tests { #[test] fn describes_itself_for_matching_result_in_contains_mode() -> Result<()> { - let matcher: StrMatcher<&str, _> = contains_substring("A string"); + let matcher: StrMatcher<&str, _, _> = contains_substring("A string"); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), displays_as(eq("contains a substring \"A string\"")) @@ -903,7 +1028,7 @@ mod tests { #[test] fn describes_itself_for_non_matching_result_in_contains_mode() -> Result<()> { - let matcher: StrMatcher<&str, _> = contains_substring("A string"); + let matcher: StrMatcher<&str, _, _> = contains_substring("A string"); verify_that!( Matcher::describe(&matcher, MatcherResult::NoMatch), displays_as(eq("does not contain a substring \"A string\"")) @@ -912,7 +1037,7 @@ mod tests { #[test] fn describes_itself_with_count_number() -> Result<()> { - let matcher: StrMatcher<&str, _> = contains_substring("A string").times(gt(2)); + let matcher: StrMatcher<&str, _, _> = contains_substring("A string").times(gt(2)); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), displays_as(eq("contains a substring \"A string\" (count is greater than 2)")) @@ -921,7 +1046,7 @@ mod tests { #[test] fn describes_itself_for_matching_result_in_starts_with_mode() -> Result<()> { - let matcher: StrMatcher<&str, _> = starts_with("A string"); + let matcher: StrMatcher<&str, _, _> = starts_with("A string"); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), displays_as(eq("starts with prefix \"A string\"")) @@ -930,7 +1055,7 @@ mod tests { #[test] fn describes_itself_for_non_matching_result_in_starts_with_mode() -> Result<()> { - let matcher: StrMatcher<&str, _> = starts_with("A string"); + let matcher: StrMatcher<&str, _, _> = starts_with("A string"); verify_that!( Matcher::describe(&matcher, MatcherResult::NoMatch), displays_as(eq("does not start with \"A string\"")) @@ -939,7 +1064,7 @@ mod tests { #[test] fn describes_itself_for_matching_result_in_ends_with_mode() -> Result<()> { - let matcher: StrMatcher<&str, _> = ends_with("A string"); + let matcher: StrMatcher<&str, _, _> = ends_with("A string"); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), displays_as(eq("ends with suffix \"A string\"")) @@ -948,7 +1073,7 @@ mod tests { #[test] fn describes_itself_for_non_matching_result_in_ends_with_mode() -> Result<()> { - let matcher: StrMatcher<&str, _> = ends_with("A string"); + let matcher: StrMatcher<&str, _, _> = ends_with("A string"); verify_that!( Matcher::describe(&matcher, MatcherResult::NoMatch), displays_as(eq("does not end with \"A string\"")) diff --git a/googletest/src/matchers/subset_of_matcher.rs b/googletest/src/matchers/subset_of_matcher.rs index 24c00d80..ebd2e1d0 100644 --- a/googletest/src/matchers/subset_of_matcher.rs +++ b/googletest/src/matchers/subset_of_matcher.rs @@ -83,12 +83,12 @@ use std::{fmt::Debug, marker::PhantomData}; /// runtime proportional to the *product* of the sizes of the actual and /// expected containers as well as the time to check equality of each pair of /// items. It should not be used on especially large containers. -pub fn subset_of( +pub fn subset_of<'a, ElementT: Debug + PartialEq + 'a, ActualT: Debug + ?Sized + 'a, ExpectedT: Debug>( superset: ExpectedT, -) -> impl Matcher +) -> impl Matcher<'a, ActualT = ActualT> where - for<'a> &'a ActualT: IntoIterator, - for<'a> &'a ExpectedT: IntoIterator, + &'a ActualT: IntoIterator, + for<'b> &'b ExpectedT: IntoIterator, { SubsetOfMatcher:: { superset, phantom: Default::default() } } @@ -98,15 +98,15 @@ struct SubsetOfMatcher { phantom: PhantomData, } -impl Matcher +impl<'a, ElementT: Debug + PartialEq + 'a, ActualT: Debug + ?Sized + 'a, ExpectedT: Debug> Matcher<'a> for SubsetOfMatcher where - for<'a> &'a ActualT: IntoIterator, - for<'a> &'a ExpectedT: IntoIterator, + &'a ActualT: IntoIterator, + for<'b> &'b ExpectedT: IntoIterator, { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches(&self, actual: &'a ActualT) -> MatcherResult { for actual_item in actual { if self.expected_is_missing(actual_item) { return MatcherResult::NoMatch; @@ -115,7 +115,7 @@ where MatcherResult::Match } - fn explain_match(&self, actual: &ActualT) -> Description { + fn explain_match(&self, actual: &'a ActualT) -> Description { let unexpected_elements = actual .into_iter() .enumerate() diff --git a/googletest/src/matchers/superset_of_matcher.rs b/googletest/src/matchers/superset_of_matcher.rs index d1e9d72a..2b756d39 100644 --- a/googletest/src/matchers/superset_of_matcher.rs +++ b/googletest/src/matchers/superset_of_matcher.rs @@ -84,12 +84,17 @@ use std::{fmt::Debug, marker::PhantomData}; /// runtime proportional to the *product* of the sizes of the actual and /// expected containers as well as the time to check equality of each pair of /// items. It should not be used on especially large containers. -pub fn superset_of( +pub fn superset_of< + 'a, + ElementT: Debug + PartialEq + 'a, + ActualT: Debug + ?Sized + 'a, + ExpectedT: Debug, +>( subset: ExpectedT, -) -> impl Matcher +) -> impl Matcher<'a, ActualT = ActualT> where - for<'a> &'a ActualT: IntoIterator, - for<'a> &'a ExpectedT: IntoIterator, + &'a ActualT: IntoIterator, + for<'b> &'b ExpectedT: IntoIterator, { SupersetOfMatcher:: { subset, phantom: Default::default() } } @@ -99,15 +104,15 @@ struct SupersetOfMatcher { phantom: PhantomData, } -impl Matcher - for SupersetOfMatcher +impl<'a, ElementT: Debug + PartialEq + 'a, ActualT: Debug + ?Sized + 'a, ExpectedT: Debug> + Matcher<'a> for SupersetOfMatcher where - for<'a> &'a ActualT: IntoIterator, - for<'a> &'a ExpectedT: IntoIterator, + &'a ActualT: IntoIterator, + for<'b> &'b ExpectedT: IntoIterator, { type ActualT = ActualT; - fn matches(&self, actual: &ActualT) -> MatcherResult { + fn matches(&self, actual: &'a ActualT) -> MatcherResult { for expected_item in &self.subset { if actual_is_missing(actual, expected_item) { return MatcherResult::NoMatch; @@ -116,7 +121,7 @@ where MatcherResult::Match } - fn explain_match(&self, actual: &ActualT) -> Description { + fn explain_match(&self, actual: &'a ActualT) -> Description { let missing_items: Vec<_> = self .subset .into_iter() @@ -138,12 +143,12 @@ where } } -fn actual_is_missing( - actual: &ActualT, +fn actual_is_missing<'a, ElementT: PartialEq + 'a, ActualT: ?Sized + 'a>( + actual: &'a ActualT, needle: &ElementT, ) -> bool where - for<'a> &'a ActualT: IntoIterator, + &'a ActualT: IntoIterator, { !actual.into_iter().any(|item| *item == *needle) } diff --git a/googletest/src/matchers/tuple_matcher.rs b/googletest/src/matchers/tuple_matcher.rs index af55cbf6..49b9a6ec 100644 --- a/googletest/src/matchers/tuple_matcher.rs +++ b/googletest/src/matchers/tuple_matcher.rs @@ -29,7 +29,7 @@ pub mod internal { // This implementation is provided for completeness, but is completely trivial. // The only actual value which can be supplied is (), which must match. - impl Matcher for () { + impl Matcher<'_> for () { type ActualT = (); fn matches(&self, _: &Self::ActualT) -> MatcherResult { @@ -50,12 +50,12 @@ pub mod internal { #[doc(hidden)] macro_rules! tuple_matcher_n { ($([$field_number:tt, $matcher_type:ident, $field_type:ident]),*) => { - impl<$($field_type: Debug, $matcher_type: Matcher),*> - Matcher for ($($matcher_type,)*) + impl<'a, $($field_type: Debug, $matcher_type: Matcher<'a, ActualT = $field_type>),*> + Matcher<'a> for ($($matcher_type,)*) { type ActualT = ($($field_type,)*); - fn matches(&self, actual: &($($field_type,)*)) -> MatcherResult { + fn matches(&self, actual: &'a ($($field_type,)*)) -> MatcherResult { $(match self.$field_number.matches(&actual.$field_number) { MatcherResult::Match => {}, MatcherResult::NoMatch => { @@ -65,7 +65,7 @@ pub mod internal { MatcherResult::Match } - fn explain_match(&self, actual: &($($field_type,)*)) -> Description { + fn explain_match(&self, actual: &'a ($($field_type,)*)) -> Description { let mut explanation = Description::new().text("which").nested(self.describe(self.matches(actual))); $(match self.$field_number.matches(&actual.$field_number) { MatcherResult::Match => {}, diff --git a/googletest/src/matchers/unordered_elements_are_matcher.rs b/googletest/src/matchers/unordered_elements_are_matcher.rs index f4585a43..c2d40dee 100644 --- a/googletest/src/matchers/unordered_elements_are_matcher.rs +++ b/googletest/src/matchers/unordered_elements_are_matcher.rs @@ -377,17 +377,17 @@ pub mod internal { /// /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] - pub struct UnorderedElementsAreMatcher<'a, ContainerT: ?Sized, T: Debug, const N: usize> { - elements: [Box + 'a>; N], + pub struct UnorderedElementsAreMatcher<'m, 'e, ContainerT: ?Sized, T: Debug, const N: usize> { + elements: [Box + 'm>; N], requirements: Requirements, phantom: PhantomData, } - impl<'a, ContainerT: ?Sized, T: Debug, const N: usize> - UnorderedElementsAreMatcher<'a, ContainerT, T, N> + impl<'m, 'e, ContainerT: ?Sized, T: Debug, const N: usize> + UnorderedElementsAreMatcher<'m, 'e, ContainerT, T, N> { pub fn new( - elements: [Box + 'a>; N], + elements: [Box + 'm>; N], requirements: Requirements, ) -> Self { Self { elements, requirements, phantom: Default::default() } @@ -403,19 +403,19 @@ pub mod internal { // least one expected element and vice versa. // 3. `UnorderedElementsAreMatcher` verifies that a perfect matching exists // using Ford-Fulkerson. - impl<'a, T: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher - for UnorderedElementsAreMatcher<'a, ContainerT, T, N> + impl<'m, 'e, 'c, T: Debug + 'e, ContainerT: Debug + ?Sized + 'c, const N: usize> Matcher<'c> + for UnorderedElementsAreMatcher<'m, 'e, ContainerT, T, N> where - for<'b> &'b ContainerT: IntoIterator, + &'c ContainerT: IntoIterator, { type ActualT = ContainerT; - fn matches(&self, actual: &ContainerT) -> MatcherResult { + fn matches(&self, actual: &'c ContainerT) -> MatcherResult { let match_matrix = MatchMatrix::generate(actual, &self.elements); match_matrix.is_match_for(self.requirements).into() } - fn explain_match(&self, actual: &ContainerT) -> Description { + fn explain_match(&self, actual: &'c ContainerT) -> Description { if let Some(size_mismatch_explanation) = self.requirements.explain_size_mismatch(actual, N) { @@ -450,49 +450,65 @@ pub mod internal { } } - type KeyValueMatcher<'a, KeyT, ValueT> = - (Box + 'a>, Box + 'a>); + type KeyValueMatcher<'m, 'k, 'v, KeyT, ValueT> = + (Box + 'm>, Box + 'm>); /// This is the analogue to [UnorderedElementsAreMatcher] for maps and /// map-like collections. /// /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] - pub struct UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, const N: usize> + pub struct UnorderedElementsOfMapAreMatcher< + 'm, + 'k, + 'v, + ContainerT, + KeyT, + ValueT, + const N: usize, + > where ContainerT: ?Sized, KeyT: Debug, ValueT: Debug, { - elements: [KeyValueMatcher<'a, KeyT, ValueT>; N], + elements: [KeyValueMatcher<'m, 'k, 'v, KeyT, ValueT>; N], requirements: Requirements, phantom: PhantomData, } - impl<'a, ContainerT, KeyT: Debug, ValueT: Debug, const N: usize> - UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N> + impl<'m, 'k, 'v, ContainerT, KeyT: Debug, ValueT: Debug, const N: usize> + UnorderedElementsOfMapAreMatcher<'m, 'k, 'v, ContainerT, KeyT, ValueT, N> { pub fn new( - elements: [KeyValueMatcher<'a, KeyT, ValueT>; N], + elements: [KeyValueMatcher<'m, 'k, 'v, KeyT, ValueT>; N], requirements: Requirements, ) -> Self { Self { elements, requirements, phantom: Default::default() } } } - impl<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher - for UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N> + impl< + 'm, + 'k, + 'v, + 'c, + KeyT: Debug + 'k, + ValueT: Debug + 'v, + ContainerT: Debug + ?Sized + 'c, + const N: usize, + > Matcher<'c> for UnorderedElementsOfMapAreMatcher<'m, 'k, 'v, ContainerT, KeyT, ValueT, N> where - for<'b> &'b ContainerT: IntoIterator, + &'c ContainerT: IntoIterator, { type ActualT = ContainerT; - fn matches(&self, actual: &ContainerT) -> MatcherResult { + fn matches(&self, actual: &'c ContainerT) -> MatcherResult { let match_matrix = MatchMatrix::generate_for_map(actual, &self.elements); match_matrix.is_match_for(self.requirements).into() } - fn explain_match(&self, actual: &ContainerT) -> Description { + fn explain_match(&self, actual: &'c ContainerT) -> Description { if let Some(size_mismatch_explanation) = self.requirements.explain_size_mismatch(actual, N) { @@ -552,13 +568,13 @@ pub mod internal { } impl Requirements { - fn explain_size_mismatch( + fn explain_size_mismatch<'c, ContainerT: ?Sized>( &self, - actual: &ContainerT, + actual: &'c ContainerT, expected_size: usize, ) -> Option where - for<'b> &'b ContainerT: IntoIterator, + &'c ContainerT: IntoIterator, { let actual_size = count_elements(actual); match self { @@ -601,12 +617,12 @@ pub mod internal { struct MatchMatrix(Vec<[MatcherResult; N]>); impl MatchMatrix { - fn generate<'a, T: Debug + 'a, ContainerT: Debug + ?Sized>( - actual: &ContainerT, - expected: &[Box + 'a>; N], + fn generate<'m, 'c, 'e, T: Debug + 'e, ContainerT: Debug + ?Sized + 'c>( + actual: &'c ContainerT, + expected: &[Box + 'm>; N], ) -> Self where - for<'b> &'b ContainerT: IntoIterator, + &'c ContainerT: IntoIterator, { let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]); for (actual_idx, actual) in actual.into_iter().enumerate() { @@ -617,12 +633,20 @@ pub mod internal { matrix } - fn generate_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>( - actual: &ContainerT, - expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N], + fn generate_for_map< + 'm, + 'k, + 'v, + 'c, + KeyT: Debug + 'k, + ValueT: Debug + 'v, + ContainerT: Debug + ?Sized + 'c, + >( + actual: &'c ContainerT, + expected: &[KeyValueMatcher<'m, 'k, 'v, KeyT, ValueT>; N], ) -> Self where - for<'b> &'b ContainerT: IntoIterator, + &'c ContainerT: IntoIterator, { let mut matrix = MatchMatrix(vec![[MatcherResult::NoMatch; N]; count_elements(actual)]); for (actual_idx, (actual_key, actual_value)) in actual.into_iter().enumerate() { @@ -959,14 +983,14 @@ pub mod internal { (0..N).filter(|expected_idx| !matched_expected.contains(expected_idx)).collect() } - fn get_explanation<'a, T: Debug, ContainerT: Debug + ?Sized>( + fn get_explanation<'a, 'c, 'e, T: Debug + 'e, ContainerT: Debug + ?Sized + 'c>( &self, - actual: &ContainerT, - expected: &[Box + 'a>; N], + actual: &'c ContainerT, + expected: &[Box + 'a>; N], requirements: Requirements, ) -> Option where - for<'b> &'b ContainerT: IntoIterator, + &'c ContainerT: IntoIterator, { let actual: Vec<_> = actual.into_iter().collect(); if self.is_full_match() { @@ -1006,14 +1030,22 @@ pub mod internal { ).into()) } - fn get_explanation_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>( + fn get_explanation_for_map< + 'm, + 'k, + 'v, + 'c, + KeyT: Debug + 'k, + ValueT: Debug + 'v, + ContainerT: Debug + ?Sized + 'c, + >( &self, - actual: &ContainerT, - expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N], + actual: &'c ContainerT, + expected: &[KeyValueMatcher<'m, 'k, 'v, KeyT, ValueT>; N], requirements: Requirements, ) -> Option where - for<'b> &'b ContainerT: IntoIterator, + &'c ContainerT: IntoIterator, { let actual: Vec<_> = actual.into_iter().collect(); if self.is_full_match() { @@ -1106,13 +1138,13 @@ mod tests { // compiler takes care of that, but when the matcher is created separately, // we must create the constitute matchers separately so that they // aren't dropped too early. + let value: HashMap = HashMap::from_iter([(0, 1), (1, 1), (2, 2)]); let matchers = ((anything(), eq(1)), (anything(), eq(2)), (anything(), eq(2))); let matcher: UnorderedElementsOfMapAreMatcher, _, _, 3> = unordered_elements_are![ (matchers.0.0, matchers.0.1), (matchers.1.0, matchers.1.1), (matchers.2.0, matchers.2.1), ]; - let value: HashMap = HashMap::from_iter([(0, 1), (1, 1), (2, 2)]); verify_that!( matcher.explain_match(&value), displays_as(contains_regex( diff --git a/googletest/tests/elements_are_matcher_test.rs b/googletest/tests/elements_are_matcher_test.rs index 4de23144..7277c2e9 100644 --- a/googletest/tests/elements_are_matcher_test.rs +++ b/googletest/tests/elements_are_matcher_test.rs @@ -103,13 +103,14 @@ fn elements_are_produces_correct_failure_message_nested() -> Result<()> { #[test] fn elements_are_explain_match_wrong_size() -> Result<()> { + let matcher = elements_are![eq(1)]; verify_that!( - elements_are![eq(1)].explain_match(&vec![1, 2]), + matcher.explain_match(&vec![1, 2]), displays_as(eq("whose size is 2")) ) } -fn create_matcher() -> impl Matcher> { +fn create_matcher<'a>() -> impl Matcher<'a, ActualT = Vec> { elements_are![eq(1)] } diff --git a/googletest/tests/unordered_elements_are_matcher_test.rs b/googletest/tests/unordered_elements_are_matcher_test.rs index bd614175..ede5dc8b 100644 --- a/googletest/tests/unordered_elements_are_matcher_test.rs +++ b/googletest/tests/unordered_elements_are_matcher_test.rs @@ -171,16 +171,18 @@ fn unordered_elements_are_matches_unordered_with_repetition() -> Result<()> { #[test] fn unordered_elements_are_explains_mismatch_due_to_wrong_size() -> Result<()> { + let matcher = unordered_elements_are![eq(2), eq(3), eq(4)]; verify_that!( - unordered_elements_are![eq(2), eq(3), eq(4)].explain_match(&vec![2, 3]), + matcher.explain_match(&vec![2, 3]), displays_as(eq("which has size 2 (expected 3)")) ) } #[test] fn unordered_elements_are_description_no_full_match() -> Result<()> { + let matcher = unordered_elements_are![eq(1), eq(2), eq(2)]; verify_that!( - unordered_elements_are![eq(1), eq(2), eq(2)].explain_match(&vec![1, 1, 2]), + matcher.explain_match(&vec![1, 1, 2]), displays_as(eq(indoc!( " which does not have a perfect match with the expected elements. The best match found was: @@ -194,21 +196,23 @@ fn unordered_elements_are_description_no_full_match() -> Result<()> { #[test] fn unordered_elements_are_unmatchable_expected_description_mismatch() -> Result<()> { + let matcher = unordered_elements_are![eq(1), eq(2), eq(3)]; verify_that!( - unordered_elements_are![eq(1), eq(2), eq(3)].explain_match(&vec![1, 1, 3]), + matcher.explain_match(&vec![1, 1, 3]), displays_as(eq("which has no element matching the expected element #1")) ) } #[test] fn unordered_elements_are_unmatchable_actual_description_mismatch() -> Result<()> { + let matcher = unordered_elements_are![eq(1), eq(1), eq(3)]; verify_that!( - unordered_elements_are![eq(1), eq(1), eq(3)].explain_match(&vec![1, 2, 3]), + matcher.explain_match(&vec![1, 2, 3]), displays_as(eq("whose element #1 does not match any expected elements")) ) } -fn create_matcher() -> impl Matcher> { +fn create_matcher<'a>() -> impl Matcher<'a, ActualT = Vec> { unordered_elements_are![eq(1)] } @@ -217,7 +221,7 @@ fn unordered_elements_are_works_when_matcher_is_created_in_subroutine() -> Resul verify_that!(vec![1], create_matcher()) } -fn create_matcher_for_map() -> impl Matcher> { +fn create_matcher_for_map<'a>() -> impl Matcher<'a, ActualT = HashMap> { unordered_elements_are![(eq(1), eq(1))] } @@ -275,32 +279,36 @@ fn contains_each_does_not_match_when_matchers_are_unmatched() -> Result<()> { #[test] fn contains_each_explains_mismatch_due_to_wrong_size() -> Result<()> { + let matcher = contains_each![eq(2), eq(3), eq(4)]; verify_that!( - contains_each![eq(2), eq(3), eq(4)].explain_match(&vec![2, 3]), + matcher.explain_match(&vec![2, 3]), displays_as(eq("which has size 2 (expected at least 3)")) ) } #[test] fn contains_each_explains_missing_element_in_mismatch() -> Result<()> { + let matcher = contains_each![eq(2), eq(3), eq(4)]; verify_that!( - contains_each![eq(2), eq(3), eq(4)].explain_match(&vec![1, 2, 3]), + matcher.explain_match(&vec![1, 2, 3]), displays_as(eq("which has no element matching the expected element #2")) ) } #[test] fn contains_each_explains_missing_elements_in_mismatch() -> Result<()> { + let matcher = contains_each![eq(2), eq(3), eq(4), eq(5)]; verify_that!( - contains_each![eq(2), eq(3), eq(4), eq(5)].explain_match(&vec![0, 1, 2, 3]), + matcher.explain_match(&vec![0, 1, 2, 3]), displays_as(eq("which has no elements matching the expected elements #2, #3")) ) } #[test] fn contains_each_explains_mismatch_due_to_no_graph_matching_found() -> Result<()> { + let matcher = contains_each![ge(2), ge(2)]; verify_that!( - contains_each![ge(2), ge(2)].explain_match(&vec![1, 2]), + matcher.explain_match(&vec![1, 2]), displays_as(eq(indoc!( " which does not have a superset match with the expected elements. The best match found was: @@ -367,32 +375,36 @@ fn is_contained_in_does_not_match_when_elements_are_unmatched() -> Result<()> { #[test] fn is_contained_in_explains_mismatch_due_to_wrong_size() -> Result<()> { + let matcher = is_contained_in![eq(2), eq(3)]; verify_that!( - is_contained_in![eq(2), eq(3)].explain_match(&vec![2, 3, 4]), + matcher.explain_match(&vec![2, 3, 4]), displays_as(eq("which has size 3 (expected at most 2)")) ) } #[test] fn is_contained_in_explains_missing_element_in_mismatch() -> Result<()> { + let matcher = is_contained_in![eq(2), eq(3), eq(4)]; verify_that!( - is_contained_in![eq(2), eq(3), eq(4)].explain_match(&vec![1, 2, 3]), + matcher.explain_match(&vec![1, 2, 3]), displays_as(eq("whose element #0 does not match any expected elements")) ) } #[test] fn is_contained_in_explains_missing_elements_in_mismatch() -> Result<()> { + let matcher = is_contained_in![eq(2), eq(3), eq(4), eq(5)]; verify_that!( - is_contained_in![eq(2), eq(3), eq(4), eq(5)].explain_match(&vec![0, 1, 2, 3]), + matcher.explain_match(&vec![0, 1, 2, 3]), displays_as(eq("whose elements #0, #1 do not match any expected elements")) ) } #[test] fn is_contained_in_explains_mismatch_due_to_no_graph_matching_found() -> Result<()> { + let matcher = is_contained_in![ge(1), ge(3)]; verify_that!( - is_contained_in![ge(1), ge(3)].explain_match(&vec![1, 2]), + matcher.explain_match(&vec![1, 2]), displays_as(eq(indoc!( " which does not have a subset match with the expected elements. The best match found was: