Skip to content

Commit d6f4cc5

Browse files
hovinenbcopybara-github
authored andcommitted
Support borrowed data inside matchers.
With the previous changes to use `Box` to own matchers in `AllMatcher`, `ElementsAreMatcher`, `UnorderedElementsAreMatcher` and `UnorderedElementsOfMapAreMatcher`, these matchers lost the ability to contain borrowed data with a lifetime other than `'static`. This is because `Box` implicitly assumes that its inner type has lifetime `'static`. In particular, one could not nest `eq_deref_of` inside these matchers. That should not be necessary as long as the `Box` instances are dropped before the references they contain become invalid. Such a limitation is therefore surprising. This change introduces explicit lifetimes for the content of the `Box` instances. Doing so allows the borrow checker to apply the lifetime of the contained references to prove that they outlive the `Box` instances. This change also introduces integration tests which demonstrate that this works. PiperOrigin-RevId: 532422248
1 parent 84907e4 commit d6f4cc5

File tree

6 files changed

+88
-59
lines changed

6 files changed

+88
-59
lines changed

googletest/src/matchers/all_matcher.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,20 @@ pub mod internal {
7373
///
7474
/// For internal use only. API stablility is not guaranteed!
7575
#[doc(hidden)]
76-
pub struct AllMatcher<T: Debug + ?Sized, const N: usize> {
77-
components: [Box<dyn Matcher<ActualT = T>>; N],
76+
pub struct AllMatcher<'a, T: Debug + ?Sized, const N: usize> {
77+
components: [Box<dyn Matcher<ActualT = T> + 'a>; N],
7878
}
7979

80-
impl<T: Debug + ?Sized, const N: usize> AllMatcher<T, N> {
80+
impl<'a, T: Debug + ?Sized, const N: usize> AllMatcher<'a, T, N> {
8181
/// Constructs an [`AllMatcher`] with the given component matchers.
8282
///
8383
/// Intended for use only by the [`all`] macro.
84-
pub fn new(components: [Box<dyn Matcher<ActualT = T>>; N]) -> Self {
84+
pub fn new(components: [Box<dyn Matcher<ActualT = T> + 'a>; N]) -> Self {
8585
Self { components }
8686
}
8787
}
8888

89-
impl<T: Debug + ?Sized, const N: usize> Matcher for AllMatcher<T, N> {
89+
impl<'a, T: Debug + ?Sized, const N: usize> Matcher for AllMatcher<'a, T, N> {
9090
type ActualT = T;
9191

9292
fn matches(&self, actual: &Self::ActualT) -> MatcherResult {

googletest/src/matchers/elements_are_matcher.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,24 +72,24 @@ pub mod internal {
7272
///
7373
/// **For internal use only. API stablility is not guaranteed!**
7474
#[doc(hidden)]
75-
pub struct ElementsAre<ContainerT: ?Sized, T: Debug> {
76-
elements: Vec<Box<dyn Matcher<ActualT = T>>>,
75+
pub struct ElementsAre<'a, ContainerT: ?Sized, T: Debug> {
76+
elements: Vec<Box<dyn Matcher<ActualT = T> + 'a>>,
7777
phantom: PhantomData<ContainerT>,
7878
}
7979

80-
impl<ContainerT: ?Sized, T: Debug> ElementsAre<ContainerT, T> {
80+
impl<'a, ContainerT: ?Sized, T: Debug> ElementsAre<'a, ContainerT, T> {
8181
/// Factory only intended for use in the macro `elements_are!`.
8282
///
8383
/// **For internal use only. API stablility is not guaranteed!**
8484
#[doc(hidden)]
85-
pub fn new(elements: Vec<Box<dyn Matcher<ActualT = T>>>) -> Self {
85+
pub fn new(elements: Vec<Box<dyn Matcher<ActualT = T> + 'a>>) -> Self {
8686
Self { elements, phantom: Default::default() }
8787
}
8888
}
8989

90-
impl<T: Debug, ContainerT: Debug + ?Sized> Matcher for ElementsAre<ContainerT, T>
90+
impl<'a, T: Debug, ContainerT: Debug + ?Sized> Matcher for ElementsAre<'a, ContainerT, T>
9191
where
92-
for<'a> &'a ContainerT: IntoIterator<Item = &'a T>,
92+
for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
9393
{
9494
type ActualT = ContainerT;
9595

googletest/src/matchers/unordered_elements_are_matcher.rs

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -341,15 +341,17 @@ pub mod internal {
341341
///
342342
/// **For internal use only. API stablility is not guaranteed!**
343343
#[doc(hidden)]
344-
pub struct UnorderedElementsAreMatcher<ContainerT: ?Sized, T: Debug, const N: usize> {
345-
elements: [Box<dyn Matcher<ActualT = T>>; N],
344+
pub struct UnorderedElementsAreMatcher<'a, ContainerT: ?Sized, T: Debug, const N: usize> {
345+
elements: [Box<dyn Matcher<ActualT = T> + 'a>; N],
346346
requirements: Requirements,
347347
phantom: PhantomData<ContainerT>,
348348
}
349349

350-
impl<ContainerT: ?Sized, T: Debug, const N: usize> UnorderedElementsAreMatcher<ContainerT, T, N> {
350+
impl<'a, ContainerT: ?Sized, T: Debug, const N: usize>
351+
UnorderedElementsAreMatcher<'a, ContainerT, T, N>
352+
{
351353
pub fn new(
352-
elements: [Box<dyn Matcher<ActualT = T>>; N],
354+
elements: [Box<dyn Matcher<ActualT = T> + 'a>; N],
353355
requirements: Requirements,
354356
) -> Self {
355357
Self { elements, requirements, phantom: Default::default() }
@@ -365,10 +367,10 @@ pub mod internal {
365367
// least one expected element and vice versa.
366368
// 3. `UnorderedElementsAreMatcher` verifies that a perfect matching exists
367369
// using Ford-Fulkerson.
368-
impl<T: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher
369-
for UnorderedElementsAreMatcher<ContainerT, T, N>
370+
impl<'a, T: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher
371+
for UnorderedElementsAreMatcher<'a, ContainerT, T, N>
370372
where
371-
for<'a> &'a ContainerT: IntoIterator<Item = &'a T>,
373+
for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
372374
{
373375
type ActualT = ContainerT;
374376

@@ -411,40 +413,40 @@ pub mod internal {
411413
}
412414
}
413415

414-
type KeyValueMatcher<KeyT, ValueT> =
415-
(Box<dyn Matcher<ActualT = KeyT>>, Box<dyn Matcher<ActualT = ValueT>>);
416+
type KeyValueMatcher<'a, KeyT, ValueT> =
417+
(Box<dyn Matcher<ActualT = KeyT> + 'a>, Box<dyn Matcher<ActualT = ValueT> + 'a>);
416418

417419
/// This is the analogue to [UnorderedElementsAreMatcher] for maps and
418420
/// map-like collections.
419421
///
420422
/// **For internal use only. API stablility is not guaranteed!**
421423
#[doc(hidden)]
422-
pub struct UnorderedElementsOfMapAreMatcher<ContainerT, KeyT, ValueT, const N: usize>
424+
pub struct UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, const N: usize>
423425
where
424426
ContainerT: ?Sized,
425427
KeyT: Debug,
426428
ValueT: Debug,
427429
{
428-
elements: [KeyValueMatcher<KeyT, ValueT>; N],
430+
elements: [KeyValueMatcher<'a, KeyT, ValueT>; N],
429431
requirements: Requirements,
430432
phantom: PhantomData<ContainerT>,
431433
}
432434

433-
impl<ContainerT, KeyT: Debug, ValueT: Debug, const N: usize>
434-
UnorderedElementsOfMapAreMatcher<ContainerT, KeyT, ValueT, N>
435+
impl<'a, ContainerT, KeyT: Debug, ValueT: Debug, const N: usize>
436+
UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N>
435437
{
436438
pub fn new(
437-
elements: [KeyValueMatcher<KeyT, ValueT>; N],
439+
elements: [KeyValueMatcher<'a, KeyT, ValueT>; N],
438440
requirements: Requirements,
439441
) -> Self {
440442
Self { elements, requirements, phantom: Default::default() }
441443
}
442444
}
443445

444-
impl<KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher
445-
for UnorderedElementsOfMapAreMatcher<ContainerT, KeyT, ValueT, N>
446+
impl<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize> Matcher
447+
for UnorderedElementsOfMapAreMatcher<'a, ContainerT, KeyT, ValueT, N>
446448
where
447-
for<'a> &'a ContainerT: IntoIterator<Item = (&'a KeyT, &'a ValueT)>,
449+
for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
448450
{
449451
type ActualT = ContainerT;
450452

@@ -561,12 +563,12 @@ pub mod internal {
561563
struct MatchMatrix<const N: usize>(Vec<[MatcherResult; N]>);
562564

563565
impl<const N: usize> MatchMatrix<N> {
564-
fn generate<T: Debug, ContainerT: Debug + ?Sized>(
566+
fn generate<'a, T: Debug + 'a, ContainerT: Debug + ?Sized>(
565567
actual: &ContainerT,
566-
expected: &[Box<dyn Matcher<ActualT = T>>; N],
568+
expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N],
567569
) -> Self
568570
where
569-
for<'a> &'a ContainerT: IntoIterator<Item = &'a T>,
571+
for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
570572
{
571573
let mut matrix =
572574
MatchMatrix(vec![[MatcherResult::DoesNotMatch; N]; count_elements(actual)]);
@@ -578,12 +580,12 @@ pub mod internal {
578580
matrix
579581
}
580582

581-
fn generate_for_map<KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>(
583+
fn generate_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>(
582584
actual: &ContainerT,
583-
expected: &[KeyValueMatcher<KeyT, ValueT>; N],
585+
expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N],
584586
) -> Self
585587
where
586-
for<'a> &'a ContainerT: IntoIterator<Item = (&'a KeyT, &'a ValueT)>,
588+
for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
587589
{
588590
let mut matrix =
589591
MatchMatrix(vec![[MatcherResult::DoesNotMatch; N]; count_elements(actual)]);
@@ -921,14 +923,14 @@ pub mod internal {
921923
(0..N).filter(|expected_idx| !matched_expected.contains(expected_idx)).collect()
922924
}
923925

924-
fn get_explanation<T: Debug, ContainerT: Debug + ?Sized>(
926+
fn get_explanation<'a, T: Debug, ContainerT: Debug + ?Sized>(
925927
&self,
926928
actual: &ContainerT,
927-
expected: &[Box<dyn Matcher<ActualT = T>>; N],
929+
expected: &[Box<dyn Matcher<ActualT = T> + 'a>; N],
928930
requirements: Requirements,
929931
) -> Option<String>
930932
where
931-
for<'a> &'a ContainerT: IntoIterator<Item = &'a T>,
933+
for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
932934
{
933935
let actual: Vec<_> = actual.into_iter().collect();
934936
if self.is_full_match() {
@@ -968,14 +970,14 @@ pub mod internal {
968970
))
969971
}
970972

971-
fn get_explanation_for_map<KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>(
973+
fn get_explanation_for_map<'a, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized>(
972974
&self,
973975
actual: &ContainerT,
974-
expected: &[KeyValueMatcher<KeyT, ValueT>; N],
976+
expected: &[KeyValueMatcher<'a, KeyT, ValueT>; N],
975977
requirements: Requirements,
976978
) -> Option<String>
977979
where
978-
for<'a> &'a ContainerT: IntoIterator<Item = (&'a KeyT, &'a ValueT)>,
980+
for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
979981
{
980982
let actual: Vec<_> = actual.into_iter().collect();
981983
if self.is_full_match() {

googletest/tests/all_matcher_test.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ fn supports_trailing_comma() -> Result<()> {
4949
)
5050
}
5151

52+
#[test]
53+
fn admits_matchers_without_static_lifetime() -> Result<()> {
54+
#[derive(Debug, PartialEq)]
55+
struct AStruct(i32);
56+
let expected_value = AStruct(123);
57+
verify_that!(AStruct(123), all![eq_deref_of(&expected_value)])
58+
}
59+
5260
#[test]
5361
fn mismatch_description_two_failed_matchers() -> Result<()> {
5462
verify_that!(

googletest/tests/elements_are_matcher_test.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ fn elements_are_returns_no_match_when_expected_and_actual_sizes_differ() -> Resu
4646
verify_that!(value, not(elements_are![eq(1), eq(2), eq(3)]))
4747
}
4848

49+
#[test]
50+
fn elements_are_admits_matchers_without_static_lifetime() -> Result<()> {
51+
#[derive(Debug, PartialEq)]
52+
struct AStruct(i32);
53+
let expected_value = AStruct(123);
54+
verify_that!(vec![AStruct(123)], elements_are![eq_deref_of(&expected_value)])
55+
}
56+
4957
#[test]
5058
fn elements_are_produces_correct_failure_message() -> Result<()> {
5159
let result = verify_that!(vec![1, 4, 3], elements_are![eq(1), eq(2), eq(3)]);

googletest/tests/unordered_elements_are_matcher_test.rs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ fn unordered_elements_are_matches_slice() -> Result<()> {
4444

4545
#[test]
4646
fn unordered_elements_are_matches_hash_map() -> Result<()> {
47-
let value: HashMap<u32, &'static str> =
48-
HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
47+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
4948
verify_that!(
5049
value,
5150
unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
@@ -54,8 +53,7 @@ fn unordered_elements_are_matches_hash_map() -> Result<()> {
5453

5554
#[test]
5655
fn unordered_elements_are_matches_hash_map_with_trailing_comma() -> Result<()> {
57-
let value: HashMap<u32, &'static str> =
58-
HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
56+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
5957
verify_that!(
6058
value,
6159
unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")),]
@@ -64,8 +62,7 @@ fn unordered_elements_are_matches_hash_map_with_trailing_comma() -> Result<()> {
6462

6563
#[test]
6664
fn unordered_elements_are_does_not_match_hash_map_with_wrong_key() -> Result<()> {
67-
let value: HashMap<u32, &'static str> =
68-
HashMap::from_iter([(1, "One"), (2, "Two"), (4, "Three")]);
65+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (4, "Three")]);
6966
verify_that!(
7067
value,
7168
not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
@@ -74,8 +71,7 @@ fn unordered_elements_are_does_not_match_hash_map_with_wrong_key() -> Result<()>
7471

7572
#[test]
7673
fn unordered_elements_are_does_not_match_hash_map_with_wrong_value() -> Result<()> {
77-
let value: HashMap<u32, &'static str> =
78-
HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Four")]);
74+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Four")]);
7975
verify_that!(
8076
value,
8177
not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
@@ -84,7 +80,7 @@ fn unordered_elements_are_does_not_match_hash_map_with_wrong_value() -> Result<(
8480

8581
#[test]
8682
fn unordered_elements_are_does_not_match_hash_map_missing_element() -> Result<()> {
87-
let value: HashMap<u32, &'static str> = HashMap::from_iter([(1, "One"), (2, "Two")]);
83+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two")]);
8884
verify_that!(
8985
value,
9086
not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
@@ -93,15 +89,13 @@ fn unordered_elements_are_does_not_match_hash_map_missing_element() -> Result<()
9389

9490
#[test]
9591
fn unordered_elements_are_does_not_match_hash_map_with_extra_element() -> Result<()> {
96-
let value: HashMap<u32, &'static str> =
97-
HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
92+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
9893
verify_that!(value, not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One"))]))
9994
}
10095

10196
#[test]
10297
fn unordered_elements_are_does_not_match_hash_map_with_mismatched_key_and_value() -> Result<()> {
103-
let value: HashMap<u32, &'static str> =
104-
HashMap::from_iter([(1, "One"), (2, "Three"), (3, "Two")]);
98+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Three"), (3, "Two")]);
10599
verify_that!(
106100
value,
107101
not(unordered_elements_are![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))])
@@ -120,6 +114,25 @@ fn unordered_elements_are_matches_size() -> Result<()> {
120114
verify_that!(value, not(unordered_elements_are![eq(1), eq(2), eq(3)]))
121115
}
122116

117+
#[test]
118+
fn unordered_elements_are_admits_matchers_without_static_lifetime() -> Result<()> {
119+
#[derive(Debug, PartialEq)]
120+
struct AStruct(i32);
121+
let expected_value = AStruct(123);
122+
verify_that!(vec![AStruct(123)], unordered_elements_are![eq_deref_of(&expected_value)])
123+
}
124+
125+
#[test]
126+
fn unordered_elements_are_with_map_admits_matchers_without_static_lifetime() -> Result<()> {
127+
#[derive(Debug, PartialEq)]
128+
struct AStruct(i32);
129+
let expected_value = AStruct(123);
130+
verify_that!(
131+
HashMap::from([(1, AStruct(123))]),
132+
unordered_elements_are![(eq(1), eq_deref_of(&expected_value))]
133+
)
134+
}
135+
123136
#[test]
124137
fn unordered_elements_are_description_mismatch() -> Result<()> {
125138
let result = verify_that!(vec![1, 4, 3], unordered_elements_are![eq(1), eq(2), eq(3)]);
@@ -219,15 +232,13 @@ fn contains_each_supports_trailing_comma() -> Result<()> {
219232

220233
#[test]
221234
fn contains_each_matches_hash_map() -> Result<()> {
222-
let value: HashMap<u32, &'static str> =
223-
HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
235+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
224236
verify_that!(value, contains_each![(eq(2), eq("Two")), (eq(1), eq("One"))])
225237
}
226238

227239
#[test]
228240
fn contains_each_matches_hash_map_with_trailing_comma() -> Result<()> {
229-
let value: HashMap<u32, &'static str> =
230-
HashMap::from_iter([(1, "One"), (2, "Two"), (3, "Three")]);
241+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two"), (3, "Three")]);
231242
verify_that!(value, contains_each![(eq(2), eq("Two")), (eq(1), eq("One")),])
232243
}
233244

@@ -317,7 +328,7 @@ fn is_contained_supports_trailing_comma() -> Result<()> {
317328

318329
#[test]
319330
fn is_contained_in_matches_hash_map() -> Result<()> {
320-
let value: HashMap<u32, &'static str> = HashMap::from_iter([(1, "One"), (2, "Two")]);
331+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two")]);
321332
verify_that!(
322333
value,
323334
is_contained_in![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three"))]
@@ -326,7 +337,7 @@ fn is_contained_in_matches_hash_map() -> Result<()> {
326337

327338
#[test]
328339
fn is_contained_in_matches_hash_map_with_trailing_comma() -> Result<()> {
329-
let value: HashMap<u32, &'static str> = HashMap::from_iter([(1, "One"), (2, "Two")]);
340+
let value: HashMap<u32, &'static str> = HashMap::from([(1, "One"), (2, "Two")]);
330341
verify_that!(
331342
value,
332343
is_contained_in![(eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")),]

0 commit comments

Comments
 (0)