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