Repository: clap-rs/clap
Pull request #4544 (2022-12-09 08:42:31)

Update grouped_values_of to use typed API and add support in derive
A step towards #2924

tmccombs: hmm, it looks like rust 1.66 changed the Debug format for PhantomData, which is causing the test for value_parser::value_parser to fail. I'm not sure what to do about that, since updating the test would cause it to fail on older versions of rust.
tmccombs: > Rebase and tests should get past that problem

It looks like master is currently [failing](https://github.com/clap-rs/clap/actions/runs/3718113536/jobs/6306082546) in the same way.
epage: Oops, forgot to push

Patch:
diff --git a/src/parser/matches/arg_matches.rs b/src/parser/matches/arg_matches.rs
index 36d2ca93..000ee77d 100644
--- a/src/parser/matches/arg_matches.rs
+++ b/src/parser/matches/arg_matches.rs
@@ -216,6 +216,44 @@ impl ArgMatches {
         MatchesError::unwrap(id, self.try_get_many(id))
     }
 
+    /// Iterate over the values passed to each occurrence of an option.
+    ///
+    /// Each item is itself an iterator containing the arguments passed to a single occurrence
+    /// of the option.
+    ///
+    /// If the option doesn't support multiple occurrences, or there was only a single occurrence,
+    /// the iterator will only contain a single item.
+    ///
+    /// Returns `None` if the option wasn't present.
+    ///
+    /// # Panics
+    ///
+    /// If the argument definition and access mismatch. To handle this case programmatically, see
+    /// [`ArgMatches::try_get_occurrences`].
+    ///
+    /// # Examples
+    /// ```rust
+    /// # use clap::{Command,Arg, ArgAction, value_parser};
+    /// let m = Command::new("myprog")
+    ///     .arg(Arg::new("x")
+    ///         .short('x')
+    ///         .num_args(2)
+    ///         .action(ArgAction::Append)
+    ///         .value_parser(value_parser!(String)))
+    ///     .get_matches_from(vec![
+    ///         "myprog", "-x", "a", "b", "-x", "c", "d"]);
+    /// let vals: Vec<Vec<&String>> = m.get_occurrences("x").unwrap().map(Iterator::collect).collect();
+    /// assert_eq!(vals, [["a", "b"], ["c", "d"]]);
+    /// ```
+    #[cfg_attr(debug_assertions, track_caller)]
+    #[cfg(feature = "unstable-grouped")]
+    pub fn get_occurrences<T: Any + Clone + Send + Sync + 'static>(
+        &self,
+        id: &str,
+    ) -> Option<OccurrencesRef<T>> {
+        MatchesError::unwrap(id, self.try_get_occurrences(id))
+    }
+
     /// Iterate over the original argument values.
     ///
     /// An `OsStr` on Unix-like systems is any series of bytes, regardless of whether or not they
@@ -262,6 +300,60 @@ impl ArgMatches {
         MatchesError::unwrap(id, self.try_get_raw(id))
     }
 
+    /// Iterate over the original values for each occurrence of an option.
+    ///
+    /// Similar to [`ArgMatches::get_occurrences`] but returns raw values.
+    ///
+    /// An `OsStr` on Unix-like systems is any series of bytes, regardless of whether or not they
+    /// contain valid UTF-8. Since [`String`]s in Rust are guaranteed to be valid UTF-8, a valid
+    /// filename on a Unix system as an argument value may contain invalid UTF-8.
+    ///
+    /// Returns `None` if the option wasn't present.
+    ///
+    /// # Panic
+    ///
+    /// If the argument definition and access mismatch.  To handle this case programmatically, see
+    /// [`ArgMatches::try_get_raw_occurrences`].
+    ///
+    /// # Examples
+    ///
+    #[cfg_attr(not(unix), doc = " ```ignore")]
+    #[cfg_attr(unix, doc = " ```")]
+    /// # use clap::{Command, arg, value_parser, ArgAction, Arg};
+    /// # use std::ffi::{OsStr,OsString};
+    /// # use std::os::unix::ffi::{OsStrExt,OsStringExt};
+    /// use std::path::PathBuf;
+    ///
+    /// let m = Command::new("myprog")
+    ///     .arg(Arg::new("x")
+    ///         .short('x')
+    ///         .num_args(2)
+    ///         .action(ArgAction::Append)
+    ///         .value_parser(value_parser!(PathBuf)))
+    ///     .get_matches_from(vec![OsString::from("myprog"),
+    ///                             OsString::from("-x"),
+    ///                             OsString::from("a"), OsString::from("b"),
+    ///                             OsString::from("-x"),
+    ///                             OsString::from("c"),
+    ///                             // "{0xe9}!"
+    ///                             OsString::from_vec(vec![0xe9, b'!'])]);
+    /// let mut itr = m.get_raw_occurrences("x")
+    ///     .expect("`-x`is required")
+    ///     .map(Iterator::collect::<Vec<_>>);
+    /// assert_eq!(itr.next(), Some(vec![OsStr::new("a"), OsStr::new("b")]));
+    /// assert_eq!(itr.next(), Some(vec![OsStr::new("c"), OsStr::from_bytes(&[0xe9, b'!'])]));
+    /// assert_eq!(itr.next(), None);
+    /// ```
+    /// [`Iterator`]: std::iter::Iterator
+    /// [`OsStr`]: std::ffi::OsStr
+    /// [values]: OsValues
+    /// [`String`]: std::string::String
+    #[cfg(feature = "unstable-grouped")]
+    #[cfg_attr(debug_assertions, track_caller)]
+    pub fn get_raw_occurrences(&self, id: &str) -> Option<RawOccurrences<'_>> {
+        MatchesError::unwrap(id, self.try_get_raw_occurrences(id))
+    }
+
     /// Returns the value of a specific option or positional argument.
     ///
     /// i.e. an argument that [takes an additional value][crate::Arg::num_args] at runtime.
@@ -338,6 +430,45 @@ impl ArgMatches {
         MatchesError::unwrap(id, self.try_remove_many(id))
     }
 
+    /// Return values for each occurrence of an option.
+    ///
+    /// Each item is itself an iterator containing the arguments passed to a single occurrence of
+    /// the option.
+    ///
+    /// If the option doesn't support multiple occurrences, or there was only a single occurrence,
+    /// the iterator will only contain a single item.
+    ///
+    /// Returns `None` if the option wasn't present.
+    ///
+    /// # Panic
+    ///
+    /// If the argument definition and access mismatch.  To handle this case programmatically, see
+    /// [`ArgMatches::try_remove_occurrences`].
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// # use clap::{Command, Arg, value_parser, ArgAction};
+    /// let mut m = Command::new("myprog")
+    ///     .arg(Arg::new("x")
+    ///         .short('x')
+    ///         .num_args(2)
+    ///         .action(ArgAction::Append)
+    ///         .value_parser(value_parser!(String)))
+    ///     .get_matches_from(vec![
+    ///         "myprog", "-x", "a", "b", "-x", "c", "d"]);
+    /// let vals: Vec<Vec<String>> = m.remove_occurrences("x").unwrap().map(Iterator::collect).collect();
+    /// assert_eq!(vals, [["a", "b"], ["c", "d"]]);
+    /// ```
+    #[cfg(feature = "unstable-grouped")]
+    #[cfg_attr(debug_assertions, track_caller)]
+    pub fn remove_occurrences<T: Any + Clone + Send + Sync + 'static>(
+        &mut self,
+        id: &str,
+    ) -> Option<Occurrences<T>> {
+        MatchesError::unwrap(id, self.try_remove_occurrences(id))
+    }
+
     /// Check if values are present for the argument or group id
     ///
     /// *NOTE:* This will always return `true` if [`default_value`] has been set.
@@ -453,6 +584,11 @@ impl ArgMatches {
     /// [`Iterator`]: std::iter::Iterator
     #[cfg(feature = "unstable-grouped")]
     #[cfg_attr(debug_assertions, track_caller)]
+    #[deprecated(
+        since = "4.1.0",
+        note = "Use get_occurrences or remove_occurrences instead"
+    )]
+    #[allow(deprecated)]
     pub fn grouped_values_of(&self, id: &str) -> Option<GroupedValues> {
         let arg = some!(self.get_arg(id));
         let v = GroupedValues {
@@ -967,12 +1103,30 @@ impl ArgMatches {
         let values = arg.vals_flatten();
         let values = ValuesRef {
             // enforced by `try_get_arg_t`
-            iter: values.map(|v| v.downcast_ref::<T>().expect(INTERNAL_ERROR_MSG)),
+            iter: values.map(unwrap_downcast_ref),
             len,
         };
         Ok(Some(values))
     }
 
+    /// Non-panicking version of [`ArgMatches::get_occurrences`]
+    #[cfg(feature = "unstable-grouped")]
+    pub fn try_get_occurrences<T: Any + Clone + Send + Sync + 'static>(
+        &self,
+        id: &str,
+    ) -> Result<Option<OccurrencesRef<T>>, MatchesError> {
+        let arg = match ok!(self.try_get_arg_t::<T>(id)) {
+            Some(arg) => arg,
+            None => return Ok(None),
+        };
+        let values = arg.vals();
+        Ok(Some(OccurrencesRef {
+            iter: values.map(|g| OccurrenceValuesRef {
+                iter: g.iter().map(unwrap_downcast_ref),
+            }),
+        }))
+    }
+
     /// Non-panicking version of [`ArgMatches::get_raw`]
     pub fn try_get_raw(&self, id: &str) -> Result<Option<RawValues<'_>>, MatchesError> {
         let arg = match ok!(self.try_get_arg(id)) {
@@ -988,6 +1142,25 @@ impl ArgMatches {
         Ok(Some(values))
     }
 
+    /// Non-panicking version of [`ArgMatches::get_raw_occurrences`]
+    #[cfg(feature = "unstable-grouped")]
+    pub fn try_get_raw_occurrences(
+        &self,
+        id: &str,
+    ) -> Result<Option<RawOccurrences<'_>>, MatchesError> {
+        let arg = match ok!(self.try_get_arg(id)) {
+            Some(arg) => arg,
+            None => return Ok(None),
+        };
+        let values = arg.raw_vals();
+        let occurrences = RawOccurrences {
+            iter: values.map(|g| RawOccurrenceValues {
+                iter: g.iter().map(OsString::as_os_str),
+            }),
+        };
+        Ok(Some(occurrences))
+    }
+
     /// Non-panicking version of [`ArgMatches::remove_one`]
     pub fn try_remove_one<T: Any + Clone + Send + Sync + 'static>(
         &mut self,
@@ -997,7 +1170,7 @@ impl ArgMatches {
             Some(values) => Ok(values
                 .into_vals_flatten()
                 // enforced by `try_get_arg_t`
-                .map(|v| v.downcast_into::<T>().expect(INTERNAL_ERROR_MSG))
+                .map(unwrap_downcast_into)
                 .next()),
             None => Ok(None),
         }
@@ -1016,12 +1189,31 @@ impl ArgMatches {
         let values = arg.into_vals_flatten();
         let values = Values {
             // enforced by `try_get_arg_t`
-            iter: values.map(|v| v.downcast_into::<T>().expect(INTERNAL_ERROR_MSG)),
+            iter: values.map(unwrap_downcast_into),
             len,
         };
         Ok(Some(values))
     }
 
+    /// Non-panicking version of [`ArgMatches::remove_occurrences`]
+    #[cfg(feature = "unstable-grouped")]
+    pub fn try_remove_occurrences<T: Any + Clone + Send + Sync + 'static>(
+        &mut self,
+        id: &str,
+    ) -> Result<Option<Occurrences<T>>, MatchesError> {
+        let arg = match ok!(self.try_remove_arg_t::<T>(id)) {
+            Some(arg) => arg,
+            None => return Ok(None),
+        };
+        let values = arg.into_vals();
+        let occurrences = Occurrences {
+            iter: values.into_iter().map(|g| OccurrenceValues {
+                iter: g.into_iter().map(unwrap_downcast_into),
+            }),
+        };
+        Ok(Some(occurrences))
+    }
+
     /// Non-panicking version of [`ArgMatches::contains_id`]
     pub fn try_contains_id(&self, id: &str) -> Result<bool, MatchesError> {
         ok!(self.verify_arg(id));
@@ -1377,14 +1569,15 @@ impl Default for RawValues<'_> {
 // commit: be5e1fa3c26e351761b33010ddbdaf5f05dbcc33
 // license: MIT - Copyright (c) 2015 The Rust Project Developers
 
-#[derive(Clone)]
-#[allow(missing_debug_implementations)]
+#[derive(Clone, Debug)]
+#[deprecated(since = "4.1.0", note = "Use Occurrences instead")]
 pub struct GroupedValues<'a> {
     #[allow(clippy::type_complexity)]
     iter: Map<Iter<'a, Vec<AnyValue>>, fn(&Vec<AnyValue>) -> Vec<&str>>,
     len: usize,
 }
 
+#[allow(deprecated)]
 impl<'a> Iterator for GroupedValues<'a> {
     type Item = Vec<&'a str>;
 
@@ -1396,15 +1589,18 @@ impl<'a> Iterator for GroupedValues<'a> {
     }
 }
 
+#[allow(deprecated)]
 impl<'a> DoubleEndedIterator for GroupedValues<'a> {
     fn next_back(&mut self) -> Option<Self::Item> {
         self.iter.next_back()
     }
 }
 
+#[allow(deprecated)]
 impl<'a> ExactSizeIterator for GroupedValues<'a> {}
 
 /// Creates an empty iterator. Used for `unwrap_or_default()`.
+#[allow(deprecated)]
 impl<'a> Default for GroupedValues<'a> {
     fn default() -> Self {
         static EMPTY: [Vec<AnyValue>; 0] = [];
@@ -1415,6 +1611,206 @@ impl<'a> Default for GroupedValues<'a> {
     }
 }
 
+#[derive(Clone, Debug)]
+pub struct Occurrences<T> {
+    #[allow(clippy::type_complexity)]
+    iter: Map<std::vec::IntoIter<Vec<AnyValue>>, fn(Vec<AnyValue>) -> OccurrenceValues<T>>,
+}
+
+impl<T> Iterator for Occurrences<T> {
+    type Item = OccurrenceValues<T>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.iter.next()
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+impl<T> DoubleEndedIterator for Occurrences<T> {
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.iter.next_back()
+    }
+}
+
+impl<T> ExactSizeIterator for Occurrences<T> {}
+
+impl<T> Default for Occurrences<T> {
+    fn default() -> Self {
+        let empty: Vec<Vec<AnyValue>> = Default::default();
+        Occurrences {
+            iter: empty.into_iter().map(|_| unreachable!()),
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct OccurrenceValues<T> {
+    #[allow(clippy::type_complexity)]
+    iter: Map<std::vec::IntoIter<AnyValue>, fn(AnyValue) -> T>,
+}
+
+impl<T> Iterator for OccurrenceValues<T> {
+    type Item = T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.iter.next()
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+impl<T> DoubleEndedIterator for OccurrenceValues<T> {
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.iter.next_back()
+    }
+}
+
+impl<T> ExactSizeIterator for OccurrenceValues<T> {}
+
+#[derive(Clone, Debug)]
+pub struct OccurrencesRef<'a, T> {
+    #[allow(clippy::type_complexity)]
+    iter: Map<Iter<'a, Vec<AnyValue>>, fn(&Vec<AnyValue>) -> OccurrenceValuesRef<'_, T>>,
+}
+
+impl<'a, T> Iterator for OccurrencesRef<'a, T>
+where
+    Self: 'a,
+{
+    type Item = OccurrenceValuesRef<'a, T>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.iter.next()
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+impl<'a, T> DoubleEndedIterator for OccurrencesRef<'a, T>
+where
+    Self: 'a,
+{
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.iter.next_back()
+    }
+}
+
+impl<'a, T> ExactSizeIterator for OccurrencesRef<'a, T> where Self: 'a {}
+impl<'a, T> Default for OccurrencesRef<'a, T> {
+    fn default() -> Self {
+        static EMPTY: [Vec<AnyValue>; 0] = [];
+        OccurrencesRef {
+            iter: EMPTY[..].iter().map(|_| unreachable!()),
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct OccurrenceValuesRef<'a, T> {
+    #[allow(clippy::type_complexity)]
+    iter: Map<Iter<'a, AnyValue>, fn(&AnyValue) -> &T>,
+}
+
+impl<'a, T> Iterator for OccurrenceValuesRef<'a, T>
+where
+    Self: 'a,
+{
+    type Item = &'a T;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.iter.next()
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+impl<'a, T> DoubleEndedIterator for OccurrenceValuesRef<'a, T>
+where
+    Self: 'a,
+{
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.iter.next_back()
+    }
+}
+
+impl<'a, T> ExactSizeIterator for OccurrenceValuesRef<'a, T> where Self: 'a {}
+
+#[derive(Clone, Debug)]
+pub struct RawOccurrences<'a> {
+    #[allow(clippy::type_complexity)]
+    iter: Map<Iter<'a, Vec<OsString>>, fn(&Vec<OsString>) -> RawOccurrenceValues<'_>>,
+}
+
+impl<'a> Iterator for RawOccurrences<'a> {
+    type Item = RawOccurrenceValues<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.iter.next()
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+impl<'a> DoubleEndedIterator for RawOccurrences<'a> {
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.iter.next_back()
+    }
+}
+
+impl<'a> ExactSizeIterator for RawOccurrences<'a> {}
+
+impl<'a> Default for RawOccurrences<'a> {
+    fn default() -> Self {
+        static EMPTY: [Vec<OsString>; 0] = [];
+        RawOccurrences {
+            iter: EMPTY[..].iter().map(|_| unreachable!()),
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct RawOccurrenceValues<'a> {
+    #[allow(clippy::type_complexity)]
+    iter: Map<Iter<'a, OsString>, fn(&OsString) -> &OsStr>,
+}
+
+impl<'a> Iterator for RawOccurrenceValues<'a>
+where
+    Self: 'a,
+{
+    type Item = &'a OsStr;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.iter.next()
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+}
+
+impl<'a> DoubleEndedIterator for RawOccurrenceValues<'a>
+where
+    Self: 'a,
+{
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.iter.next_back()
+    }
+}
+
+impl<'a> ExactSizeIterator for RawOccurrenceValues<'a> {}
+
 /// Iterate over indices for where an argument appeared when parsing, via [`ArgMatches::indices_of`]
 ///
 /// # Examples
@@ -1484,6 +1880,16 @@ fn unwrap_string(value: &AnyValue) -> &str {
     }
 }
 
+#[track_caller]
+fn unwrap_downcast_ref<T: Any + Clone + Send + Sync + 'static>(value: &AnyValue) -> &T {
+    value.downcast_ref().expect(INTERNAL_ERROR_MSG)
+}
+
+#[track_caller]
+fn unwrap_downcast_into<T: Any + Clone + Send + Sync + 'static>(value: AnyValue) -> T {
+    value.downcast_into().expect(INTERNAL_ERROR_MSG)
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/src/parser/matches/matched_arg.rs b/src/parser/matches/matched_arg.rs
index 6a80625f..188f82e6 100644
--- a/src/parser/matches/matched_arg.rs
+++ b/src/parser/matches/matched_arg.rs
@@ -80,6 +80,11 @@ impl MatchedArg {
         self.vals.iter()
     }
 
+    #[cfg(feature = "unstable-grouped")]
+    pub(crate) fn into_vals(self) -> Vec<Vec<AnyValue>> {
+        self.vals
+    }
+
     pub(crate) fn vals_flatten(&self) -> Flatten<Iter<Vec<AnyValue>>> {
         self.vals.iter().flatten()
     }
@@ -88,6 +93,11 @@ impl MatchedArg {
         self.vals.into_iter().flatten()
     }
 
+    #[cfg(feature = "unstable-grouped")]
+    pub(crate) fn raw_vals(&self) -> Iter<Vec<OsString>> {
+        self.raw_vals.iter()
+    }
+
     pub(crate) fn raw_vals_flatten(&self) -> Flatten<Iter<Vec<OsString>>> {
         self.raw_vals.iter().flatten()
     }


Test patch:
diff --git a/tests/builder/main.rs b/tests/builder/main.rs
index 858f2875..22fdd5f0 100644
--- a/tests/builder/main.rs
+++ b/tests/builder/main.rs
@@ -24,7 +24,6 @@ mod error;
 mod flag_subcommands;
 mod flags;
 mod global_args;
-mod grouped_values;
 mod groups;
 mod help;
 mod help_env;
@@ -33,6 +32,7 @@ mod ignore_errors;
 mod indices;
 mod multiple_occurrences;
 mod multiple_values;
+mod occurrences;
 mod opts;
 mod positionals;
 mod posix_compatible;
diff --git a/tests/builder/grouped_values.rs b/tests/builder/occurrences.rs
similarity index 88%
rename from tests/builder/grouped_values.rs
rename to tests/builder/occurrences.rs
index 726eac92..56716cb7 100644
--- a/tests/builder/grouped_values.rs
+++ b/tests/builder/occurrences.rs
@@ -1,6 +1,13 @@
 #![cfg(feature = "unstable-grouped")]
 
-use clap::{Arg, ArgAction, Command};
+use clap::{Arg, ArgAction, ArgMatches, Command};
+
+fn occurrences_as_vec_vec<'a>(m: &'a ArgMatches, name: &str) -> Vec<Vec<&'a String>> {
+    m.get_occurrences(name)
+        .unwrap()
+        .map(Iterator::collect)
+        .collect()
+}
 
 #[test]
 fn grouped_value_works() {
@@ -22,7 +29,7 @@ fn grouped_value_works() {
             "en_US:my option 2",
         ])
         .unwrap();
-    let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect();
+    let grouped_vals = occurrences_as_vec_vec(&m, "option");
     assert_eq!(
         grouped_vals,
         vec![
@@ -50,7 +57,7 @@ fn issue_1026() {
             "target3", "file8",
         ])
         .unwrap();
-    let grouped_vals: Vec<_> = m.grouped_values_of("target").unwrap().collect();
+    let grouped_vals = occurrences_as_vec_vec(&m, "target");
     assert_eq!(
         grouped_vals,
         vec![
@@ -80,7 +87,7 @@ fn grouped_value_long_flag_delimiter() {
             "alice,bob",
         ])
         .unwrap();
-    let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect();
+    let grouped_vals = occurrences_as_vec_vec(&m, "option");
     assert_eq!(
         grouped_vals,
         vec![
@@ -104,7 +111,7 @@ fn grouped_value_short_flag_delimiter() {
         )
         .try_get_matches_from(vec!["myapp", "-o=foo", "-o=val1,val2,val3", "-o=bar"])
         .unwrap();
-    let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect();
+    let grouped_vals = occurrences_as_vec_vec(&m, "option");
     assert_eq!(
         grouped_vals,
         vec![vec!["foo"], vec!["val1", "val2", "val3"], vec!["bar"]]
@@ -124,7 +131,7 @@ fn grouped_value_positional_arg() {
             "myprog", "val1", "val2", "val3", "val4", "val5", "val6",
         ])
         .unwrap();
-    let grouped_vals: Vec<_> = m.grouped_values_of("pos").unwrap().collect();
+    let grouped_vals = occurrences_as_vec_vec(&m, "pos");
     assert_eq!(
         grouped_vals,
         vec![vec!["val1", "val2", "val3", "val4", "val5", "val6"]]
@@ -145,7 +152,7 @@ fn grouped_value_multiple_positional_arg() {
             "myprog", "val1", "val2", "val3", "val4", "val5", "val6",
         ])
         .unwrap();
-    let grouped_vals: Vec<_> = m.grouped_values_of("pos2").unwrap().collect();
+    let grouped_vals = occurrences_as_vec_vec(&m, "pos2");
     assert_eq!(
         grouped_vals,
         vec![vec!["val2", "val3", "val4", "val5", "val6"]]
@@ -167,7 +174,7 @@ fn grouped_value_multiple_positional_arg_last_multiple() {
             "myprog", "val1", "--", "val2", "val3", "val4", "val5", "val6",
         ])
         .unwrap();
-    let grouped_vals: Vec<_> = m.grouped_values_of("pos2").unwrap().collect();
+    let grouped_vals = occurrences_as_vec_vec(&m, "pos2");
     assert_eq!(
         grouped_vals,
         vec![vec!["val2", "val3", "val4", "val5", "val6"]]
@@ -190,10 +197,10 @@ fn grouped_interleaved_positional_values() {
         .try_get_matches_from(["foo", "1", "2", "-f", "a", "3", "-f", "b", "4"])
         .unwrap();
 
-    let pos: Vec<_> = m.grouped_values_of("pos").unwrap().collect();
+    let pos = occurrences_as_vec_vec(&m, "pos");
     assert_eq!(pos, vec![vec!["1", "2"], vec!["3"], vec!["4"]]);
 
-    let flag: Vec<_> = m.grouped_values_of("flag").unwrap().collect();
+    let flag = occurrences_as_vec_vec(&m, "flag");
     assert_eq!(flag, vec![vec!["a"], vec!["b"]]);
 }
 
@@ -213,10 +220,10 @@ fn grouped_interleaved_positional_occurrences() {
         .try_get_matches_from(["foo", "1", "2", "-f", "a", "3", "-f", "b", "4"])
         .unwrap();
 
-    let pos: Vec<_> = m.grouped_values_of("pos").unwrap().collect();
+    let pos = occurrences_as_vec_vec(&m, "pos");
     assert_eq!(pos, vec![vec!["1", "2"], vec!["3"], vec!["4"]]);
 
-    let flag: Vec<_> = m.grouped_values_of("flag").unwrap().collect();
+    let flag = occurrences_as_vec_vec(&m, "flag");
     assert_eq!(flag, vec![vec!["a"], vec!["b"]]);
 }