Skip to main content

test_that/matchers/containers/container_contains/
unordered_matcher.rs

1// Copyright 2022 Google LLC
2// Copyright 2026 Bradford Hovinen <bradford@hovinen.me>
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16// There are no visible documentation elements in this module; the declarative
17// macro is documented in the matchers module.
18#![doc(hidden)]
19
20/// Module for use only by the macros in this module.
21///
22/// **For internal use only. API stability is not guaranteed!**
23#[doc(hidden)]
24pub mod __internal {
25    use crate::{
26        description::Description,
27        matcher::{Describable, Matcher, MatcherResult},
28        matcher_support::count_elements::count_elements,
29        matchers::__internal::ContainerContainsOrderedMatcher,
30        matchers::containers::{
31            OwnedItems, RefItems,
32            container_contains::{PairBorrow, Requirements},
33        },
34    };
35    use alloc::{boxed::Box, collections::BTreeSet, vec::Vec};
36    use core::{borrow::Borrow, fmt::Debug, marker::PhantomData};
37
38    /// Matches elements of an iterable collection with a sequence of matchers,
39    /// in any order.
40    ///
41    /// This struct is meant to be used only through the
42    /// `contains_exactly![...]` macro.
43    pub struct ContainerContainsUnorderedMatcher<'matchers, ContainerT: ?Sized, T: Debug, ModeT> {
44        elements: Vec<Box<dyn Matcher<T> + 'matchers>>,
45        requirements: Requirements,
46        _phantom: PhantomData<(*const ContainerT, ModeT)>,
47    }
48
49    impl<'matchers, ContainerT: ?Sized, T: Debug, ModeT>
50        ContainerContainsUnorderedMatcher<'matchers, ContainerT, T, ModeT>
51    {
52        /// Constructs a [ContainerContainsUnorderedMatcher] with the given
53        /// matchers.
54        ///
55        /// This is not intended to be invoked directly, but rather through the
56        /// macros [contains_exactly], [contains_each] and
57        /// [is_contained_in].
58        #[doc(hidden)]
59        pub fn new(
60            elements: Vec<Box<dyn Matcher<T> + 'matchers>>,
61            requirements: Requirements,
62        ) -> Self {
63            Self { elements, requirements, _phantom: PhantomData }
64        }
65
66        /// Asserts that the matchers must match the elements of the collection
67        /// in the same order as they appear when iterating.
68        pub fn in_order(self) -> ContainerContainsOrderedMatcher<'matchers, ContainerT, T, ModeT> {
69            ContainerContainsOrderedMatcher::new(self.elements, self.requirements)
70        }
71
72        fn matches_with_iter<ItemT: Borrow<T>>(
73            &self,
74            n: usize,
75            actual: impl Iterator<Item = ItemT>,
76        ) -> MatcherResult {
77            let match_matrix = MatchMatrix::generate(actual, n, &self.elements);
78            match_matrix.is_match_for(self.requirements).into()
79        }
80
81        // This performs the checks in three different steps. This is useful for
82        // performance but also to produce an actionable error message.
83        // 1. Verifies that both collections have the same size (via
84        //    size_mismatch param)
85        // 2. Verifies that each actual element matches at least one expected
86        //    element and vice versa.
87        // 3. Verifies that a perfect matching exists using Ford-Fulkerson.
88        fn explain_match_with_iters<ItemT1: Borrow<T>, ItemT2: Borrow<T>>(
89            &self,
90            size_mismatch: Option<Description>,
91            n: usize,
92            iter_for_generate: impl Iterator<Item = ItemT1>,
93            iter_for_explain: impl Iterator<Item = ItemT2>,
94        ) -> Description {
95            if let Some(explanation) = size_mismatch {
96                return explanation;
97            }
98            let match_matrix = MatchMatrix::generate(iter_for_generate, n, &self.elements);
99            if let Some(unmatchable) = match_matrix.explain_unmatchable(self.requirements) {
100                return unmatchable;
101            }
102            let best_match = match_matrix.find_best_match();
103            best_match
104                .get_explanation(iter_for_explain, &self.elements, self.requirements)
105                .unwrap_or("whose elements all match".into())
106        }
107    }
108
109    impl<'matchers, T: Debug, ContainerT: Debug + ?Sized> Matcher<ContainerT>
110        for ContainerContainsUnorderedMatcher<'matchers, ContainerT, T, RefItems>
111    where
112        for<'b> &'b ContainerT: IntoIterator<Item = &'b T>,
113    {
114        fn matches(&self, actual: &ContainerT) -> MatcherResult {
115            self.matches_with_iter(count_elements(actual), actual.into_iter())
116        }
117
118        fn explain_match(&self, actual: &ContainerT) -> Description {
119            self.explain_match_with_iters(
120                self.requirements.explain_size_mismatch(actual, self.elements.len()),
121                count_elements(actual),
122                actual.into_iter(),
123                actual.into_iter(),
124            )
125        }
126    }
127
128    impl<'matchers, T: Debug, ContainerT: Debug + ?Sized> Matcher<ContainerT>
129        for ContainerContainsUnorderedMatcher<'matchers, ContainerT, T, OwnedItems>
130    where
131        for<'b> &'b ContainerT: IntoIterator<Item = T>,
132    {
133        fn matches(&self, actual: &ContainerT) -> MatcherResult {
134            self.matches_with_iter(count_elements(actual), actual.into_iter())
135        }
136
137        fn explain_match(&self, actual: &ContainerT) -> Description {
138            self.explain_match_with_iters(
139                self.requirements.explain_size_mismatch(actual, self.elements.len()),
140                count_elements(actual),
141                actual.into_iter(),
142                actual.into_iter(),
143            )
144        }
145    }
146
147    impl<'matchers, T: Debug, ContainerT: ?Sized, ModeT> Describable
148        for ContainerContainsUnorderedMatcher<'matchers, ContainerT, T, ModeT>
149    {
150        fn describe(&self, matcher_result: MatcherResult) -> Description {
151            format!(
152                "{} elements matching in any order:\n{}",
153                if matcher_result.into() { "contains" } else { "doesn't contain" },
154                self.elements
155                    .iter()
156                    .map(|matcher| matcher.describe(MatcherResult::Match))
157                    .collect::<Description>()
158                    .enumerate()
159                    .indent()
160            )
161            .into()
162        }
163    }
164
165    type KeyValueMatcher<'a, KeyT, ValueT> =
166        (Box<dyn Matcher<KeyT> + 'a>, Box<dyn Matcher<ValueT> + 'a>);
167
168    /// This is the analogue to [ContainerContainsUnorderedMatcher] for maps and
169    /// map-like collections.
170    ///
171    /// **For internal use only. API stability is not guaranteed!**
172    #[doc(hidden)]
173    pub struct MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, ModeT, const N: usize>
174    where
175        ContainerT: ?Sized,
176        KeyT: Debug,
177        ValueT: Debug,
178    {
179        elements: [KeyValueMatcher<'matchers, KeyT, ValueT>; N],
180        requirements: Requirements,
181        phantom: PhantomData<(ModeT, ContainerT)>,
182    }
183
184    impl<'matchers, ContainerT: ?Sized, KeyT: Debug, ValueT: Debug, ModeT, const N: usize>
185        MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, ModeT, N>
186    {
187        pub fn new(
188            elements: [KeyValueMatcher<'matchers, KeyT, ValueT>; N],
189            requirements: Requirements,
190        ) -> Self {
191            Self { elements, requirements, phantom: PhantomData }
192        }
193
194        fn matches_with_iter<ItemT: PairBorrow<KeyT, ValueT>>(
195            &self,
196            n: usize,
197            actual: impl Iterator<Item = ItemT>,
198        ) -> MatcherResult {
199            let match_matrix = MatchMatrix::generate_for_map(actual, n, &self.elements);
200            match_matrix.is_match_for(self.requirements).into()
201        }
202
203        fn explain_match_with_iters<
204            ItemT1: PairBorrow<KeyT, ValueT>,
205            ItemT2: PairBorrow<KeyT, ValueT>,
206        >(
207            &self,
208            size_mismatch: Option<Description>,
209            n: usize,
210            iter_for_generate: impl Iterator<Item = ItemT1>,
211            iter_for_explain: impl Iterator<Item = ItemT2>,
212        ) -> Description {
213            if let Some(explanation) = size_mismatch {
214                return explanation;
215            }
216            let match_matrix = MatchMatrix::generate_for_map(iter_for_generate, n, &self.elements);
217            if let Some(unmatchable) = match_matrix.explain_unmatchable(self.requirements) {
218                return unmatchable;
219            }
220            let best_match = match_matrix.find_best_match();
221            best_match
222                .get_explanation_for_map(iter_for_explain, &self.elements, self.requirements)
223                .unwrap_or("whose elements all match".into())
224        }
225    }
226
227    impl<'matchers, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize>
228        Matcher<ContainerT> for MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, RefItems, N>
229    where
230        for<'b> &'b ContainerT: IntoIterator<Item = (&'b KeyT, &'b ValueT)>,
231    {
232        fn matches(&self, actual: &ContainerT) -> MatcherResult {
233            self.matches_with_iter(count_elements(actual), actual.into_iter())
234        }
235
236        fn explain_match(&self, actual: &ContainerT) -> Description {
237            self.explain_match_with_iters(
238                self.requirements.explain_size_mismatch(actual, N),
239                count_elements(actual),
240                actual.into_iter(),
241                actual.into_iter(),
242            )
243        }
244    }
245
246    impl<'matchers, KeyT: Debug, ValueT: Debug, ContainerT: Debug + ?Sized, const N: usize>
247        Matcher<ContainerT>
248        for MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, OwnedItems, N>
249    where
250        for<'b> &'b ContainerT: IntoIterator<Item = (KeyT, ValueT)>,
251    {
252        fn matches(&self, actual: &ContainerT) -> MatcherResult {
253            self.matches_with_iter(count_elements(actual), actual.into_iter())
254        }
255
256        fn explain_match(&self, actual: &ContainerT) -> Description {
257            self.explain_match_with_iters(
258                self.requirements.explain_size_mismatch(actual, N),
259                count_elements(actual),
260                actual.into_iter(),
261                actual.into_iter(),
262            )
263        }
264    }
265
266    impl<'matchers, KeyT: Debug, ValueT: Debug, ContainerT: ?Sized, ModeT, const N: usize>
267        Describable for MapContainsMatcher<'matchers, ContainerT, KeyT, ValueT, ModeT, N>
268    {
269        fn describe(&self, matcher_result: MatcherResult) -> Description {
270            format!(
271                "{} elements matching in any order:\n{}",
272                if matcher_result.into() { "contains" } else { "doesn't contain" },
273                self.elements
274                    .iter()
275                    .map(|(key_matcher, value_matcher)| format!(
276                        "{} => {}",
277                        key_matcher.describe(MatcherResult::Match),
278                        value_matcher.describe(MatcherResult::Match)
279                    ))
280                    .collect::<Description>()
281                    .indent()
282            )
283            .into()
284        }
285    }
286
287    /// The bipartite matching graph between actual and expected elements.
288    pub(crate) struct MatchMatrix(Vec<Vec<MatcherResult>>, usize);
289
290    impl MatchMatrix {
291        fn generate<'matchers, ActualT: Debug + ?Sized + 'matchers, ItemT: Borrow<ActualT>>(
292            actual_items: impl Iterator<Item = ItemT>,
293            n_actual: usize,
294            expected: &[Box<dyn Matcher<ActualT> + 'matchers>],
295        ) -> Self {
296            let mut matrix = MatchMatrix(
297                vec![vec![MatcherResult::NoMatch; expected.len()]; n_actual],
298                expected.len(),
299            );
300            for (actual_idx, actual_item) in actual_items.enumerate() {
301                for (expected_idx, expected) in expected.iter().enumerate() {
302                    matrix.0[actual_idx][expected_idx] = expected.matches(actual_item.borrow());
303                }
304            }
305            matrix
306        }
307
308        pub(crate) fn generate_for_fixed_matcher<
309            'matchers,
310            MatcherT: Matcher<ActualT> + 'matchers,
311            ActualT: Debug + ?Sized + 'matchers,
312            ItemT: Borrow<ActualT>,
313        >(
314            actual_items: impl Iterator<Item = ItemT>,
315            n_actual: usize,
316            expected: &[MatcherT],
317        ) -> Self {
318            let mut matrix = MatchMatrix(
319                vec![vec![MatcherResult::NoMatch; expected.len()]; n_actual],
320                expected.len(),
321            );
322            for (actual_idx, actual_item) in actual_items.enumerate() {
323                for (expected_idx, expected) in expected.iter().enumerate() {
324                    matrix.0[actual_idx][expected_idx] = expected.matches(actual_item.borrow());
325                }
326            }
327            matrix
328        }
329
330        fn generate_for_map<
331            'matchers,
332            KeyT: Debug,
333            ValueT: Debug,
334            ItemT: PairBorrow<KeyT, ValueT>,
335        >(
336            actual_items: impl Iterator<Item = ItemT>,
337            n_actual: usize,
338            expected: &[KeyValueMatcher<'matchers, KeyT, ValueT>],
339        ) -> Self {
340            let mut matrix = MatchMatrix(
341                vec![vec![MatcherResult::NoMatch; expected.len()]; n_actual],
342                expected.len(),
343            );
344            for (actual_idx, actual_item) in actual_items.enumerate() {
345                for (expected_idx, (expected_key, expected_value)) in expected.iter().enumerate() {
346                    matrix.0[actual_idx][expected_idx] =
347                        (expected_key.matches(actual_item.borrow_key()).into()
348                            && expected_value.matches(actual_item.borrow_value()).into())
349                        .into();
350                }
351            }
352            matrix
353        }
354
355        pub(crate) fn is_match_for(&self, requirements: Requirements) -> bool {
356            match requirements {
357                Requirements::PerfectMatch => {
358                    !self.find_unmatchable_elements().has_unmatchable_elements()
359                        && self.find_best_match().is_full_match()
360                }
361                Requirements::Superset => {
362                    !self.find_unmatched_expected().has_unmatchable_elements()
363                        && self.find_best_match().is_superset_match()
364                }
365                Requirements::Subset => {
366                    !self.find_unmatched_actual().has_unmatchable_elements()
367                        && self.find_best_match().is_subset_match()
368                }
369            }
370        }
371
372        fn explain_unmatchable(&self, requirements: Requirements) -> Option<Description> {
373            let unmatchable_elements = match requirements {
374                Requirements::PerfectMatch => self.find_unmatchable_elements(),
375                Requirements::Superset => self.find_unmatched_expected(),
376                Requirements::Subset => self.find_unmatched_actual(),
377            };
378            unmatchable_elements.get_explanation()
379        }
380
381        // Verifies that each actual matches at least one expected and that
382        // each expected matches at least one actual.
383        // This is a necessary condition but not sufficient. But it is faster
384        // than `find_best_match()`.
385        fn find_unmatchable_elements(&self) -> UnmatchableElements {
386            let unmatchable_actual =
387                self.0.iter().map(|row| row.iter().all(|&e| e.is_no_match())).collect();
388            let mut unmatchable_expected = vec![false; self.1];
389            for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
390                *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
391            }
392            UnmatchableElements { unmatchable_actual, unmatchable_expected }
393        }
394
395        fn find_unmatched_expected(&self) -> UnmatchableElements {
396            let mut unmatchable_expected = vec![false; self.1];
397            for (col_idx, expected) in unmatchable_expected.iter_mut().enumerate() {
398                *expected = self.0.iter().map(|row| row[col_idx]).all(|e| e.is_no_match());
399            }
400            UnmatchableElements {
401                unmatchable_actual: vec![false; self.0.len()],
402                unmatchable_expected,
403            }
404        }
405
406        fn find_unmatched_actual(&self) -> UnmatchableElements {
407            let unmatchable_actual =
408                self.0.iter().map(|row| row.iter().all(|e| e.is_no_match())).collect();
409            UnmatchableElements {
410                unmatchable_actual,
411                unmatchable_expected: vec![false; self.0.len()],
412            }
413        }
414
415        // Verifies that a full match exists.
416        //
417        // Uses the well-known Ford-Fulkerson max flow method to find a maximum
418        // bipartite matching. Flow is considered to be from actual to expected.
419        // There is an implicit source node that is connected to all of the
420        // actual nodes, and an implicit sink node that is connected to
421        // all of the expected nodes. All edges have unit capacity.
422        //
423        // Neither the flow graph nor the residual flow graph are represented
424        // explicitly. Instead, they are implied by the information in `self.0`
425        // and the local `actual_match : [Option<usize>; N]` whose
426        // elements are initialized to `None`. This represents the
427        // initial state of the algorithm, where the flow graph is
428        // empty, and the residual flow graph has the following edges:
429        //   - An edge from source to each actual element node
430        //   - An edge from each expected element node to sink
431        //   - An edge from each actual element node to each expected element
432        //     node, if the actual element matches the expected element, i.e.
433        //     `matches!(self.0[actual_id][expected_id], Matches)`
434        //
435        // When the `try_augment(...)` method adds a flow, it sets
436        // `actual_match[l] = Some(r)` for some nodes l and r. This
437        // induces the following changes:
438        //   - The edges (source, l), (l, r), and (r, sink) are added to the
439        //     flow graph.
440        //   - The same three edges are removed from the residual flow graph.
441        //   - The reverse edges (l, source), (r, l), and (sink, r) are added to
442        //     the residual flow graph, which is a directional graph
443        //     representing unused flow capacity.
444        //
445        // When the method augments a flow (changing `actual_match[l]` from
446        // `Some(r1)` to `Some(r2)`), this can be thought of as
447        // "undoing" the above steps with respect to r1 and "redoing"
448        // them with respect to r2.
449        //
450        // It bears repeating that the flow graph and residual flow graph are
451        // never represented explicitly, but can be derived by looking at the
452        // information in 'self.0' and in `actual_match`.
453        //
454        // As an optimization, there is a second local `expected_match:
455        // [Option<usize>; N]` which does not provide any new
456        // information. Instead, it enables more efficient queries about
457        // edges entering or leaving the expected elements nodes of the
458        // flow or residual flow graphs. The following invariants
459        // are maintained:
460        //
461        // actual_match[a] == None or expected_match[actual_match[a].unwrap()]
462        // == Some(a)
463        // expected_match[r] == None or actual_match[expected_match[e].unwrap()]
464        // == Some(e)
465        //
466        // | [ source ]                                                              |
467        // |   |||                                                                   |
468        // |   |||                                                                   |
469        // |   ||\-> actual_match[0]=Some(1) -\   expected_match[0]=None    ---\     |
470        // |   ||                             |                                |     |
471        // |   |\--> actual_match[1]=None     \-> expected_match[1]=Some(0) --\|     |
472        // |   |                                                              ||     |
473        // |   \---> actual_match[2]=Some(2)  --> expected_match[2]=Some(2) -\||     |
474        // |                                                                 |||     |
475        // |         elements                     matchers                   vvv     |
476        // |                                                               [ sink ]  |
477        //
478        // See Also:
479        //   [1] Cormen, et al (2001). "Section 26.2: The Ford-Fulkerson
480        //       method". "Introduction to Algorithms (Second ed.)", pp.
481        //       651-664.
482        //   [2] "Ford-Fulkerson algorithm", Wikipedia,
483        //       'http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm'
484        fn find_best_match(&self) -> BestMatch {
485            let mut actual_match = vec![None; self.0.len()];
486            let mut expected_match: Vec<Option<usize>> = vec![None; self.1];
487            // Searches the residual flow graph for a path from each actual node
488            // to the sink in the residual flow graph, and if one is found, add
489            // this path to the graph. It's okay to search through the actual
490            // nodes once. The edge from the implicit source node to each
491            // previously-visited actual node will have flow if that actual node
492            // has any path to the sink whatsoever. Subsequent augmentations can
493            // only add flow to the network, and cannot take away that previous
494            // flow unit from the source. Since the source-to-actual edge can
495            // only carry one flow unit (or, each actual element can be matched
496            // to only one expected element), there is no need to visit the
497            // actual nodes more than once looking for augmented paths. The flow
498            // is known to be possible or impossible by looking at the node
499            // once.
500            for actual_idx in 0..self.0.len() {
501                assert!(actual_match[actual_idx].is_none());
502                let mut seen = vec![false; self.1];
503                self.try_augment(actual_idx, &mut seen, &mut actual_match, &mut expected_match);
504            }
505            BestMatch(actual_match, self.1)
506        }
507
508        // Perform a depth-first search from actual node `actual_idx` to the
509        // sink by searching for an unassigned expected node. If a path
510        // is found, flow is added to the network by linking the actual
511        // and expected vector elements corresponding each segment of
512        // the path. Returns true if a path to sink was found, which
513        // means that a unit of flow was added to the network. The
514        // 'seen' array elements correspond to expected nodes and are
515        // marked to eliminate cycles from the search.
516        //
517        // Actual nodes will only be explored at most once because they
518        // are accessible from at most one expected node in the residual flow
519        // graph.
520        //
521        // Note that `actual_match[actual_idx]` is the only element of
522        // `actual_match` that `try_augment(...)` will potentially
523        // transition from `None` to `Some(...)`. Any other
524        // `actual_match` element holding `None` before `try_augment(...
525        // )` will be holding it when `try_augment(...)` returns.
526        //
527        fn try_augment(
528            &self,
529            actual_idx: usize,
530            seen: &mut [bool],
531            actual_match: &mut [Option<usize>],
532            expected_match: &mut [Option<usize>],
533        ) -> bool {
534            for expected_idx in 0..expected_match.len() {
535                if seen[expected_idx] {
536                    continue;
537                }
538                if self.0[actual_idx][expected_idx].is_no_match() {
539                    continue;
540                }
541                // There is an edge between `actual_idx` and `expected_idx`.
542                seen[expected_idx] = true;
543                // Next a search is performed to determine whether
544                // this edge is a dead end or leads to the sink.
545                //
546                // `expected_match[expected_idx].is_none()` means that there is
547                // residual flow from expected node at index expected_idx to the
548                // sink, so we can use that to finish this flow path and return
549                // success.
550                //
551                // Otherwise, we look for a residual flow starting from
552                // `expected_match[expected_idx].unwrap()` by calling
553                // ourselves recursively to see if this ultimately leads to
554                // sink.
555                if expected_match[expected_idx].is_none()
556                    || self.try_augment(
557                        expected_match[expected_idx].unwrap(),
558                        seen,
559                        actual_match,
560                        expected_match,
561                    )
562                {
563                    // We found a residual flow from source to sink. We thus
564                    // need to add the new edge to the current flow. Note: this
565                    // also remove the potential flow that existed by
566                    // overwriting the value in the `expected_match` and
567                    // `actual_match`.
568                    expected_match[expected_idx] = Some(actual_idx);
569                    actual_match[actual_idx] = Some(expected_idx);
570                    return true;
571                }
572            }
573            false
574        }
575    }
576
577    /// The list of elements that do not match any element in the corresponding
578    /// set.
579    /// These lists are represented as fixed-size bit sets to avoid
580    /// allocation.
581    /// TODO(bjacotg) Use BitArr!(for N) once generic_const_exprs is stable.
582    struct UnmatchableElements {
583        unmatchable_actual: Vec<bool>,
584        unmatchable_expected: Vec<bool>,
585    }
586
587    impl UnmatchableElements {
588        fn has_unmatchable_elements(&self) -> bool {
589            self.unmatchable_actual.iter().any(|b| *b)
590                || self.unmatchable_expected.iter().any(|b| *b)
591        }
592
593        fn get_explanation(&self) -> Option<Description> {
594            let unmatchable_actual = self.unmatchable_actual();
595            let actual_idx = unmatchable_actual
596                .iter()
597                .map(|idx| format!("#{}", idx))
598                .collect::<Vec<_>>()
599                .join(", ");
600            let unmatchable_expected = self.unmatchable_expected();
601            let expected_idx = unmatchable_expected
602                .iter()
603                .map(|idx| format!("#{}", idx))
604                .collect::<Vec<_>>()
605                .join(", ");
606            match (unmatchable_actual.len(), unmatchable_expected.len()) {
607                (0, 0) => None,
608                (1, 0) => {
609                    Some(format!("whose element {actual_idx} does not match any expected elements").into())
610                }
611                (_, 0) => {
612                    Some(format!("whose elements {actual_idx} do not match any expected elements",).into())
613                }
614                (0, 1) => Some(format!(
615                    "which has no element matching the expected element {expected_idx}"
616                ).into()),
617                (0, _) => Some(format!(
618                    "which has no elements matching the expected elements {expected_idx}"
619                ).into()),
620                (1, 1) => Some(format!(
621                    "whose element {actual_idx} does not match any expected elements and no elements match the expected element {expected_idx}"
622                ).into()),
623                (_, 1) => Some(format!(
624                    "whose elements {actual_idx} do not match any expected elements and no elements match the expected element {expected_idx}"
625                ).into()),
626                (1, _) => Some(format!(
627                    "whose element {actual_idx} does not match any expected elements and no elements match the expected elements {expected_idx}"
628                ).into()),
629                (_, _) => Some(format!(
630                    "whose elements {actual_idx} do not match any expected elements and no elements match the expected elements {expected_idx}"
631                ).into()),
632            }
633        }
634
635        fn unmatchable_actual(&self) -> Vec<usize> {
636            self.unmatchable_actual
637                .iter()
638                .enumerate()
639                .filter_map(|(idx, b)| if *b { Some(idx) } else { None })
640                .collect()
641        }
642
643        fn unmatchable_expected(&self) -> Vec<usize> {
644            self.unmatchable_expected
645                .iter()
646                .enumerate()
647                .filter_map(|(idx, b)| if *b { Some(idx) } else { None })
648                .collect()
649        }
650    }
651
652    /// The representation of a match between actual and expected.
653    /// The value at idx represents which expected the actual at idx is
654    /// matched to. For example, `BestMatch([Some(0), None, Some(1)])`
655    /// means:
656    ///  * The 0th element in actual matches the 0th element in expected.
657    ///  * The 1st element in actual does not match.
658    ///  * The 2nd element in actual matches the 1st element in expected.
659    struct BestMatch(Vec<Option<usize>>, usize);
660
661    impl BestMatch {
662        fn is_full_match(&self) -> bool {
663            self.0.iter().all(|o| o.is_some())
664        }
665
666        fn is_subset_match(&self) -> bool {
667            self.is_full_match()
668        }
669
670        fn is_superset_match(&self) -> bool {
671            self.get_unmatched_expected().is_empty()
672        }
673
674        fn get_matches(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
675            self.0.iter().enumerate().filter_map(|(actual_idx, maybe_expected_idx)| {
676                maybe_expected_idx.map(|expected_idx| (actual_idx, expected_idx))
677            })
678        }
679
680        fn get_unmatched_actual(&self) -> impl Iterator<Item = usize> + '_ {
681            self.0
682                .iter()
683                .enumerate()
684                .filter(|&(_, o)| o.is_none())
685                .map(|(actual_idx, _)| actual_idx)
686        }
687
688        fn get_unmatched_expected(&self) -> Vec<usize> {
689            let matched_expected: BTreeSet<_> = self.0.iter().flatten().collect();
690            (0..self.1).filter(|expected_idx| !matched_expected.contains(expected_idx)).collect()
691        }
692
693        fn get_explanation<'matchers, T: Debug, ItemT: Borrow<T>>(
694            &self,
695            actual_items: impl Iterator<Item = ItemT>,
696            expected: &[Box<dyn Matcher<T> + 'matchers>],
697            requirements: Requirements,
698        ) -> Option<Description> {
699            let actual: Vec<ItemT> = actual_items.collect();
700            if self.is_full_match() {
701                return None;
702            }
703
704            let matches = self.get_matches().map(|(actual_idx, expected_idx)| {
705                format!(
706                    "Actual element {:?} at index {actual_idx} matched expected element `{}` at index {expected_idx}.",
707                    actual[actual_idx].borrow(),
708                    expected[expected_idx].describe(MatcherResult::Match),
709                )
710            });
711
712            let unmatched_actual = self.get_unmatched_actual().map(|actual_idx| {
713                format!(
714                    "Actual element {:#?} at index {actual_idx} did not match any remaining expected element.",
715                    actual[actual_idx].borrow()
716                )
717            });
718
719            let unmatched_expected =
720                self.get_unmatched_expected().into_iter().map(|expected_idx| {
721                    format!(
722                        "Expected element `{}` at index {expected_idx} did not match any remaining actual element.",
723                        expected[expected_idx].describe(MatcherResult::Match)
724                    )
725                });
726
727            let best_match = matches
728                .chain(unmatched_actual)
729                .chain(unmatched_expected)
730                .collect::<Description>()
731                .indent();
732            Some(
733                format!(
734                    "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
735                )
736                .into(),
737            )
738        }
739
740        fn get_explanation_for_map<
741            'matchers,
742            KeyT: Debug,
743            ValueT: Debug,
744            ItemT: PairBorrow<KeyT, ValueT>,
745        >(
746            &self,
747            actual_items: impl Iterator<Item = ItemT>,
748            expected: &[KeyValueMatcher<'matchers, KeyT, ValueT>],
749            requirements: Requirements,
750        ) -> Option<Description> {
751            let actual: Vec<ItemT> = actual_items.collect();
752            if self.is_full_match() {
753                return None;
754            }
755
756            let matches = self.get_matches().map(|(actual_idx, expected_idx)| {
757                format!(
758                    "Actual element {:?} => {:?} at index {actual_idx} matched expected element `{}` => `{}` at index {expected_idx}.",
759                    actual[actual_idx].borrow_key(),
760                    actual[actual_idx].borrow_value(),
761                    expected[expected_idx].0.describe(MatcherResult::Match),
762                    expected[expected_idx].1.describe(MatcherResult::Match),
763                )
764            });
765
766            let unmatched_actual = self.get_unmatched_actual().map(|actual_idx| {
767                format!(
768                    "Actual element {:#?} => {:#?} at index {actual_idx} did not match any remaining expected element.",
769                    actual[actual_idx].borrow_key(),
770                    actual[actual_idx].borrow_value(),
771                )
772            });
773
774            let unmatched_expected =
775                self.get_unmatched_expected().into_iter().map(|expected_idx| {
776                    format!(
777                        "Expected element `{}` => `{}` at index {expected_idx} did not match any remaining actual element.",
778                        expected[expected_idx].0.describe(MatcherResult::Match),
779                        expected[expected_idx].1.describe(MatcherResult::Match),
780                    )
781                });
782
783            let best_match = matches
784                .chain(unmatched_actual)
785                .chain(unmatched_expected)
786                .collect::<Description>()
787                .indent();
788            Some(
789                format!(
790                    "which does not have a {requirements} match with the expected elements. The best match found was:\n{best_match}"
791                )
792                .into(),
793            )
794        }
795    }
796}
797
798#[cfg(all(test, feature = "std"))]
799mod tests {
800    use super::__internal::MapContainsMatcher;
801    use crate::matcher::Matcher;
802    use crate::matchers::containers::RefItems;
803    use crate::prelude::*;
804    use indoc::indoc;
805    use std::collections::HashMap;
806
807    #[test]
808    fn has_correct_description_for_map() -> TestResult<()> {
809        // ContainerContainsUnorderedMatcher maintains references to the
810        // matchers, so the constituent matchers must live longer.
811        // Inside a verify_that! macro, the compiler takes care of that,
812        // but when the matcher is created separately, we must create
813        // the constitute matchers separately so that they aren't
814        // dropped too early.
815        let matchers = ((eq(2), eq("Two")), (eq(1), eq("One")), (eq(3), eq("Three")));
816        let result = verify_that!(
817            HashMap::from([(1, "one")]),
818            contains_exactly![
819                matchers.0.0 => matchers.0.1,
820                matchers.1.0 => matchers.1.1,
821                matchers.2.0 => matchers.2.1
822            ]
823        );
824        verify_that!(
825            result,
826            err(displays_as(contains_substring(indoc!(
827                "
828                contains elements matching in any order:
829                  is equal to 2 => is equal to \"Two\"
830                  is equal to 1 => is equal to \"One\"
831                  is equal to 3 => is equal to \"Three\""
832            ))))
833        )
834    }
835
836    #[cfg(feature = "regex")]
837    #[test]
838    fn contains_exactly_description_no_full_match_with_map() -> TestResult<()> {
839        // ContainerContainsUnorderedMatcher maintains references to the
840        // matchers, so the constituent matchers must live longer.
841        // Inside a verify_that! macro, the compiler takes care of that,
842        // but when the matcher is created separately, we must create
843        // the constitute matchers separately so that they aren't
844        // dropped too early.
845        let matchers = ((anything(), eq(1)), (anything(), eq(2)), (anything(), eq(2)));
846        let matcher: MapContainsMatcher<HashMap<u32, u32>, _, _, RefItems, 3> = contains_exactly![
847            matchers.0.0 => matchers.0.1,
848            matchers.1.0 => matchers.1.1,
849            matchers.2.0 => matchers.2.1,
850        ];
851        let value: HashMap<u32, u32> = HashMap::from_iter([(0, 1), (1, 1), (2, 2)]);
852        verify_that!(
853            matcher.explain_match(&value),
854                displays_as(
855                    contains_regex(
856                        "Actual element 2 => 2 at index [0-2] matched expected element `is anything` => `is equal to 2` at index [0-2]."
857                    )
858                )
859                .and(
860                    displays_as(
861                        contains_regex(
862                            "Actual element [0-1] => [0-1] at index [0-2] did not match any remaining expected element."
863                        )
864                    )
865                )
866                .and(
867                    displays_as(
868                        contains_substring(
869                            "Expected element `is anything` => `is equal to 2` at index 2 did not match any remaining actual element."
870                        )
871                    )
872                )
873        )
874    }
875}