Repository: clap-rs/clap
Pull request #4572 (2022-12-22 18:18:17)

perf(parser): Reduce lookups for conflicts 
We already need to lookup every present-arg for conflicts, so we might
as well cache it ahead of time.  This let's us move some operations to
be immutable so we can more easily cache other lookups.

For me, this gave a 70% speed improvement for #4516 with mixed results
on normal benchmarks


Patch:
diff --git a/clap_bench/benches/04_new_help.rs b/clap_bench/benches/04_new_help.rs
index dfb2157e..3398b58e 100644
--- a/clap_bench/benches/04_new_help.rs
+++ b/clap_bench/benches/04_new_help.rs
@@ -199,7 +199,7 @@ pub fn example10(c: &mut Criterion) {
 }
 
 pub fn example4_template(c: &mut Criterion) {
-    let mut cmd = app_example4().help_template("{name} {version}\n{author}\n{about}\n\nUSAGE:\n    {usage}\n\nOPTIONS:\n{options}\n\nARGS:\n{args}\n");
+    let mut cmd = app_example4().help_template("{bin} {version}\n{author}\n{about}\n\nUSAGE:\n    {usage}\n\nOPTIONS:\n{options}\n\nARGS:\n{args}\n");
     c.bench_function("example4_template", |b| b.iter(|| build_help(&mut cmd)));
 }
 
diff --git a/clap_bench/benches/05_ripgrep.rs b/clap_bench/benches/05_ripgrep.rs
index 00662ea5..efca09c8 100644
--- a/clap_bench/benches/05_ripgrep.rs
+++ b/clap_bench/benches/05_ripgrep.rs
@@ -256,7 +256,7 @@ const USAGE: &str = "
     rg [OPTIONS] --type-list";
 
 const TEMPLATE: &str = "\
-{name} {version}
+{bin} {version}
 {author}
 {about}
 
diff --git a/clap_mangen/src/lib.rs b/clap_mangen/src/lib.rs
index 66dda1e0..d529325f 100644
--- a/clap_mangen/src/lib.rs
+++ b/clap_mangen/src/lib.rs
@@ -231,7 +231,7 @@ impl Man {
     }
 
     fn _render_version_section(&self, roff: &mut Roff) {
-        let version = roman(&render::version(&self.cmd));
+        let version = roman(render::version(&self.cmd));
         roff.control("SH", ["VERSION"]);
         roff.text([version]);
     }
diff --git a/clap_mangen/src/render.rs b/clap_mangen/src/render.rs
index 9c400ffe..ab9c9ec7 100644
--- a/clap_mangen/src/render.rs
+++ b/clap_mangen/src/render.rs
@@ -13,7 +13,7 @@ pub(crate) fn about(roff: &mut Roff, cmd: &clap::Command) {
         Some(about) => format!("{} - {}", cmd.get_name(), about),
         None => cmd.get_name().to_string(),
     };
-    roff.text([roman(&s)]);
+    roff.text([roman(s)]);
 }
 
 pub(crate) fn description(roff: &mut Roff, cmd: &clap::Command) {
@@ -36,19 +36,19 @@ pub(crate) fn synopsis(roff: &mut Roff, cmd: &clap::Command) {
         match (opt.get_short(), opt.get_long()) {
             (Some(short), Some(long)) => {
                 line.push(roman(lhs));
-                line.push(bold(&format!("-{}", short)));
+                line.push(bold(format!("-{}", short)));
                 line.push(roman("|"));
-                line.push(bold(&format!("--{}", long)));
+                line.push(bold(format!("--{}", long)));
                 line.push(roman(rhs));
             }
             (Some(short), None) => {
                 line.push(roman(lhs));
-                line.push(bold(&format!("-{} ", short)));
+                line.push(bold(format!("-{} ", short)));
                 line.push(roman(rhs));
             }
             (None, Some(long)) => {
                 line.push(roman(lhs));
-                line.push(bold(&format!("--{}", long)));
+                line.push(bold(format!("--{}", long)));
                 line.push(roman(rhs));
             }
             (None, None) => continue,
@@ -101,12 +101,12 @@ pub(crate) fn options(roff: &mut Roff, cmd: &clap::Command) {
 
         if let Some(value) = &opt.get_value_names() {
             header.push(roman("="));
-            header.push(italic(&value.join(" ")));
+            header.push(italic(value.join(" ")));
         }
 
         if let Some(defs) = option_default_values(opt) {
             header.push(roman(" "));
-            header.push(roman(&defs));
+            header.push(roman(defs));
         }
 
         let mut body = vec![];
@@ -168,13 +168,13 @@ pub(crate) fn options(roff: &mut Roff, cmd: &clap::Command) {
         header.push(roman(rhs));
 
         if let Some(defs) = option_default_values(pos) {
-            header.push(roman(&format!(" {}", defs)));
+            header.push(roman(format!(" {}", defs)));
         }
 
         let mut body = vec![];
         let mut arg_help_written = false;
         if let Some(help) = option_help(pos) {
-            body.push(roman(&help.to_string()));
+            body.push(roman(help.to_string()));
             arg_help_written = true;
         }
 
@@ -229,7 +229,7 @@ pub(crate) fn subcommands(roff: &mut Roff, cmd: &clap::Command, section: &str) {
             sub.get_name(),
             section
         );
-        roff.text([roman(&name)]);
+        roff.text([roman(name)]);
 
         if let Some(about) = sub.get_about().or_else(|| sub.get_long_about()) {
             for line in about.to_string().lines() {
@@ -273,11 +273,11 @@ fn markers(required: bool) -> (&'static str, &'static str) {
 }
 
 fn short_option(opt: char) -> Inline {
-    bold(&format!("-{}", opt))
+    bold(format!("-{}", opt))
 }
 
 fn long_option(opt: &str) -> Inline {
-    bold(&format!("--{}", opt))
+    bold(format!("--{}", opt))
 }
 
 fn option_help(opt: &clap::Arg) -> Option<&clap::builder::StyledStr> {
diff --git a/examples/repl.rs b/examples/repl.rs
index e18fbbd6..ef265911 100644
--- a/examples/repl.rs
+++ b/examples/repl.rs
@@ -29,7 +29,7 @@ fn main() -> Result<(), String> {
 fn respond(line: &str) -> Result<bool, String> {
     let args = shlex::split(line).ok_or("error: Invalid quoting")?;
     let matches = cli()
-        .try_get_matches_from(&args)
+        .try_get_matches_from(args)
         .map_err(|e| e.to_string())?;
     match matches.subcommand() {
         Some(("ping", _matches)) => {
diff --git a/src/builder/command.rs b/src/builder/command.rs
index 1a424993..b235a950 100644
--- a/src/builder/command.rs
+++ b/src/builder/command.rs
@@ -487,7 +487,7 @@ impl Command {
     /// [`Command::try_get_matches_from_mut`]: Command::try_get_matches_from_mut()
     #[inline]
     pub fn get_matches(self) -> ArgMatches {
-        self.get_matches_from(&mut env::args_os())
+        self.get_matches_from(env::args_os())
     }
 
     /// Parse [`env::args_os`], exiting on failure.
@@ -545,7 +545,7 @@ impl Command {
     #[inline]
     pub fn try_get_matches(self) -> ClapResult<ArgMatches> {
         // Start the parsing
-        self.try_get_matches_from(&mut env::args_os())
+        self.try_get_matches_from(env::args_os())
     }
 
     /// Parse the specified arguments, exiting on failure.
@@ -1744,7 +1744,7 @@ impl Command {
     /// Valid tags are:
     ///
     ///   * `{name}`                - Display name for the (sub-)command.
-    ///   * `{bin}`                 - Binary name.(deprecated)
+    ///   * `{bin}`                 - Binary name.
     ///   * `{version}`             - Version number.
     ///   * `{author}`              - Author information.
     ///   * `{author-with-newline}` - Author followed by `\n`.
@@ -1772,7 +1772,7 @@ impl Command {
     /// # use clap::Command;
     /// Command::new("myprog")
     ///     .version("1.0")
-    ///     .help_template("{name} ({version}) - {usage}")
+    ///     .help_template("{bin} ({version}) - {usage}")
     /// # ;
     /// ```
     ///
diff --git a/src/builder/debug_asserts.rs b/src/builder/debug_asserts.rs
index bfebbb4c..dff70e30 100644
--- a/src/builder/debug_asserts.rs
+++ b/src/builder/debug_asserts.rs
@@ -359,13 +359,6 @@ pub(crate) fn assert_app(cmd: &Command) {
             cmd.get_name(),
             "`{unified}` template variable was removed in clap3, use `{options}` instead"
         );
-        #[cfg(feature = "unstable-v5")]
-        assert!(
-            !help_template.to_string().contains("{bin}"),
-            "Command {}: {}",
-            cmd.get_name(),
-            "`{bin}` template variable was removed in clap5, use `{name}` instead"
-        )
     }
 
     cmd._panic_on_missing_help(cmd.is_help_expected_set());
diff --git a/src/output/help_template.rs b/src/output/help_template.rs
index 6fe9db1b..3db80da7 100644
--- a/src/output/help_template.rs
+++ b/src/output/help_template.rs
@@ -137,7 +137,6 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
                     "name" => {
                         self.write_display_name();
                     }
-                    #[cfg(not(feature = "unstable-v5"))]
                     "bin" => {
                         self.write_bin_name();
                     }
diff --git a/src/parser/arg_matcher.rs b/src/parser/arg_matcher.rs
index 93bac7e1..c48b1085 100644
--- a/src/parser/arg_matcher.rs
+++ b/src/parser/arg_matcher.rs
@@ -113,6 +113,10 @@ impl ArgMatcher {
         self.matches.args.keys()
     }
 
+    pub(crate) fn args(&self) -> crate::util::flat_map::Iter<'_, Id, MatchedArg> {
+        self.matches.args.iter()
+    }
+
     pub(crate) fn entry(&mut self, arg: Id) -> crate::util::Entry<Id, MatchedArg> {
         self.matches.args.entry(arg)
     }
diff --git a/src/parser/matches/arg_matches.rs b/src/parser/matches/arg_matches.rs
index 000ee77d..36d2ca93 100644
--- a/src/parser/matches/arg_matches.rs
+++ b/src/parser/matches/arg_matches.rs
@@ -216,44 +216,6 @@ 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
@@ -300,60 +262,6 @@ 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.
@@ -430,45 +338,6 @@ 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.
@@ -584,11 +453,6 @@ 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 {
@@ -1103,30 +967,12 @@ impl ArgMatches {
         let values = arg.vals_flatten();
         let values = ValuesRef {
             // enforced by `try_get_arg_t`
-            iter: values.map(unwrap_downcast_ref),
+            iter: values.map(|v| v.downcast_ref::<T>().expect(INTERNAL_ERROR_MSG)),
             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)) {
@@ -1142,25 +988,6 @@ 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,
@@ -1170,7 +997,7 @@ impl ArgMatches {
             Some(values) => Ok(values
                 .into_vals_flatten()
                 // enforced by `try_get_arg_t`
-                .map(unwrap_downcast_into)
+                .map(|v| v.downcast_into::<T>().expect(INTERNAL_ERROR_MSG))
                 .next()),
             None => Ok(None),
         }
@@ -1189,31 +1016,12 @@ impl ArgMatches {
         let values = arg.into_vals_flatten();
         let values = Values {
             // enforced by `try_get_arg_t`
-            iter: values.map(unwrap_downcast_into),
+            iter: values.map(|v| v.downcast_into::<T>().expect(INTERNAL_ERROR_MSG)),
             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));
@@ -1569,15 +1377,14 @@ impl Default for RawValues<'_> {
 // commit: be5e1fa3c26e351761b33010ddbdaf5f05dbcc33
 // license: MIT - Copyright (c) 2015 The Rust Project Developers
 
-#[derive(Clone, Debug)]
-#[deprecated(since = "4.1.0", note = "Use Occurrences instead")]
+#[derive(Clone)]
+#[allow(missing_debug_implementations)]
 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>;
 
@@ -1589,18 +1396,15 @@ 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] = [];
@@ -1611,206 +1415,6 @@ 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
@@ -1880,16 +1484,6 @@ 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 188f82e6..6a80625f 100644
--- a/src/parser/matches/matched_arg.rs
+++ b/src/parser/matches/matched_arg.rs
@@ -80,11 +80,6 @@ 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()
     }
@@ -93,11 +88,6 @@ 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()
     }
diff --git a/src/parser/validator.rs b/src/parser/validator.rs
index 0d97c2f6..088259ff 100644
--- a/src/parser/validator.rs
+++ b/src/parser/validator.rs
@@ -1,6 +1,6 @@
 // Internal
 use crate::builder::StyledStr;
-use crate::builder::{Arg, ArgPredicate, Command, PossibleValue};
+use crate::builder::{Arg, ArgGroup, ArgPredicate, Command, PossibleValue};
 use crate::error::{Error, Result as ClapResult};
 use crate::output::Usage;
 use crate::parser::{ArgMatcher, ParseState};
@@ -27,7 +27,7 @@ impl<'cmd> Validator<'cmd> {
         matcher: &mut ArgMatcher,
     ) -> ClapResult<()> {
         debug!("Validator::validate");
-        let mut conflicts = Conflicts::new();
+        let conflicts = Conflicts::with_args(self.cmd, matcher);
         let has_subcmd = matcher.subcommand_name().is_some();
 
         if let ParseState::Opt(a) = parse_state {
@@ -54,8 +54,8 @@ impl<'cmd> Validator<'cmd> {
 
         if !has_subcmd && self.cmd.is_arg_required_else_help_set() {
             let num_user_values = matcher
-                .arg_ids()
-                .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
+                .args()
+                .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
                 .count();
             if num_user_values == 0 {
                 let message = self.cmd.write_help_err(false);
@@ -80,9 +80,9 @@ impl<'cmd> Validator<'cmd> {
             ));
         }
 
-        ok!(self.validate_conflicts(matcher, &mut conflicts));
+        ok!(self.validate_conflicts(matcher, &conflicts));
         if !(self.cmd.is_subcommand_negates_reqs_set() && has_subcmd) {
-            ok!(self.validate_required(matcher, &mut conflicts));
+            ok!(self.validate_required(matcher, &conflicts));
         }
 
         Ok(())
@@ -91,19 +91,19 @@ impl<'cmd> Validator<'cmd> {
     fn validate_conflicts(
         &mut self,
         matcher: &ArgMatcher,
-        conflicts: &mut Conflicts,
+        conflicts: &Conflicts,
     ) -> ClapResult<()> {
         debug!("Validator::validate_conflicts");
 
         ok!(self.validate_exclusive(matcher));
 
-        for arg_id in matcher
-            .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
-            .filter(|arg_id| self.cmd.find(arg_id).is_some())
+        for (arg_id, _) in matcher
+            .args()
+            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
+            .filter(|(arg_id, _)| self.cmd.find(arg_id).is_some())
         {
             debug!("Validator::validate_conflicts::iter: id={:?}", arg_id);
-            let conflicts = conflicts.gather_conflicts(self.cmd, matcher, arg_id);
+            let conflicts = conflicts.gather_conflicts(self.cmd, arg_id);
             ok!(self.build_conflict_err(arg_id, &conflicts, matcher));
         }
 
@@ -113,9 +113,9 @@ impl<'cmd> Validator<'cmd> {
     fn validate_exclusive(&self, matcher: &ArgMatcher) -> ClapResult<()> {
         debug!("Validator::validate_exclusive");
         let args_count = matcher
-            .arg_ids()
-            .filter(|arg_id| {
-                matcher.check_explicit(arg_id, &crate::builder::ArgPredicate::IsPresent)
+            .args()
+            .filter(|(arg_id, matched)| {
+                matched.check_explicit(&crate::builder::ArgPredicate::IsPresent)
                     // Avoid including our own groups by checking none of them.  If a group is present, the
                     // args for the group will be.
                     && self.cmd.find(arg_id).is_some()
@@ -127,14 +127,12 @@ impl<'cmd> Validator<'cmd> {
         }
 
         matcher
-            .arg_ids()
-            .filter(|arg_id| {
-                matcher.check_explicit(arg_id, &crate::builder::ArgPredicate::IsPresent)
-            })
-            .filter_map(|name| {
-                debug!("Validator::validate_exclusive:iter:{:?}", name);
+            .args()
+            .filter(|(_, matched)| matched.check_explicit(&crate::builder::ArgPredicate::IsPresent))
+            .filter_map(|(id, _)| {
+                debug!("Validator::validate_exclusive:iter:{:?}", id);
                 self.cmd
-                    .find(name)
+                    .find(id)
                     // Find `arg`s which are exclusive but also appear with other args.
                     .filter(|&arg| arg.is_exclusive_set() && args_count > 1)
             })
@@ -196,8 +194,9 @@ impl<'cmd> Validator<'cmd> {
         conflicting_keys: &[Id],
     ) -> Option<StyledStr> {
         let used_filtered: Vec<Id> = matcher
-            .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
+            .args()
+            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
+            .map(|(n, _)| n)
             .filter(|n| {
                 // Filter out the args we don't want to specify.
                 self.cmd.find(n).map_or(false, |a| !a.is_hide_set())
@@ -220,14 +219,14 @@ impl<'cmd> Validator<'cmd> {
 
     fn gather_requires(&mut self, matcher: &ArgMatcher) {
         debug!("Validator::gather_requires");
-        for name in matcher
-            .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
+        for (name, matched) in matcher
+            .args()
+            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
         {
             debug!("Validator::gather_requires:iter:{:?}", name);
             if let Some(arg) = self.cmd.find(name) {
                 let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
-                    let required = matcher.check_explicit(arg.get_id(), val);
+                    let required = matched.check_explicit(val);
                     required.then(|| req_arg.clone())
                 };
 
@@ -243,11 +242,7 @@ impl<'cmd> Validator<'cmd> {
         }
     }
 
-    fn validate_required(
-        &mut self,
-        matcher: &ArgMatcher,
-        conflicts: &mut Conflicts,
-    ) -> ClapResult<()> {
+    fn validate_required(&mut self, matcher: &ArgMatcher, conflicts: &Conflicts) -> ClapResult<()> {
         debug!("Validator::validate_required: required={:?}", self.required);
         self.gather_requires(matcher);
 
@@ -255,9 +250,9 @@ impl<'cmd> Validator<'cmd> {
         let mut highest_index = 0;
 
         let is_exclusive_present = matcher
-            .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
-            .any(|id| {
+            .args()
+            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
+            .any(|(id, _)| {
                 self.cmd
                     .find(id)
                     .map(|arg| arg.is_exclusive_set())
@@ -276,7 +271,7 @@ impl<'cmd> Validator<'cmd> {
             debug!("Validator::validate_required:iter:aog={:?}", arg_or_group);
             if let Some(arg) = self.cmd.find(arg_or_group) {
                 debug!("Validator::validate_required:iter: This is an arg");
-                if !is_exclusive_present && !self.is_missing_required_ok(arg, matcher, conflicts) {
+                if !is_exclusive_present && !self.is_missing_required_ok(arg, conflicts) {
                     debug!(
                         "Validator::validate_required:iter: Missing {:?}",
                         arg.get_id()
@@ -374,14 +369,9 @@ impl<'cmd> Validator<'cmd> {
         Ok(())
     }
 
-    fn is_missing_required_ok(
-        &self,
-        a: &Arg,
-        matcher: &ArgMatcher,
-        conflicts: &mut Conflicts,
-    ) -> bool {
+    fn is_missing_required_ok(&self, a: &Arg, conflicts: &Conflicts) -> bool {
         debug!("Validator::is_missing_required_ok: {}", a.get_id());
-        let conflicts = conflicts.gather_conflicts(self.cmd, matcher, a.get_id());
+        let conflicts = conflicts.gather_conflicts(self.cmd, a.get_id());
         !conflicts.is_empty()
     }
 
@@ -441,8 +431,9 @@ impl<'cmd> Validator<'cmd> {
         );
 
         let used: Vec<Id> = matcher
-            .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
+            .args()
+            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
+            .map(|(n, _)| n)
             .filter(|n| {
                 // Filter out the args we don't want to specify.
                 self.cmd.find(n).map_or(false, |a| !a.is_hide_set())
@@ -465,71 +456,92 @@ struct Conflicts {
 }
 
 impl Conflicts {
-    fn new() -> Self {
-        Self::default()
+    fn with_args(cmd: &Command, matcher: &ArgMatcher) -> Self {
+        let mut potential = FlatMap::new();
+        potential.extend_unchecked(
+            matcher
+                .args()
+                .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
+                .map(|(id, _)| {
+                    let conf = gather_direct_conflicts(cmd, id);
+                    (id.clone(), conf)
+                }),
+        );
+        Self { potential }
     }
 
-    fn gather_conflicts(&mut self, cmd: &Command, matcher: &ArgMatcher, arg_id: &Id) -> Vec<Id> {
+    fn gather_conflicts(&self, cmd: &Command, arg_id: &Id) -> Vec<Id> {
         debug!("Conflicts::gather_conflicts: arg={:?}", arg_id);
         let mut conflicts = Vec::new();
-        for other_arg_id in matcher
-            .arg_ids()
-            .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent))
-        {
+
+        let arg_id_conflicts_storage;
+        let arg_id_conflicts = if let Some(arg_id_conflicts) = self.get_direct_conflicts(arg_id) {
+            arg_id_conflicts
+        } else {
+            // `is_missing_required_ok` is a case where we check not-present args for conflicts
+            arg_id_conflicts_storage = gather_direct_conflicts(cmd, arg_id);
+            &arg_id_conflicts_storage
+        };
+        for (other_arg_id, other_arg_id_conflicts) in self.potential.iter() {
             if arg_id == other_arg_id {
                 continue;
             }
 
-            if self
-                .gather_direct_conflicts(cmd, arg_id)
-                .contains(other_arg_id)
-            {
+            if arg_id_conflicts.contains(other_arg_id) {
                 conflicts.push(other_arg_id.clone());
             }
-            if self
-                .gather_direct_conflicts(cmd, other_arg_id)
-                .contains(arg_id)
-            {
+            if other_arg_id_conflicts.contains(arg_id) {
                 conflicts.push(other_arg_id.clone());
             }
         }
+
         debug!("Conflicts::gather_conflicts: conflicts={:?}", conflicts);
         conflicts
     }
 
-    fn gather_direct_conflicts(&mut self, cmd: &Command, arg_id: &Id) -> &[Id] {
-        self.potential.entry(arg_id.clone()).or_insert_with(|| {
-            let conf = if let Some(arg) = cmd.find(arg_id) {
-                let mut conf = arg.blacklist.clone();
-                for group_id in cmd.groups_for_arg(arg_id) {
-                    let group = cmd.find_group(&group_id).expect(INTERNAL_ERROR_MSG);
-                    conf.extend(group.conflicts.iter().cloned());
-                    if !group.multiple {
-                        for member_id in &group.args {
-                            if member_id != arg_id {
-                                conf.push(member_id.clone());
-                            }
-                        }
-                    }
-                }
+    fn get_direct_conflicts(&self, arg_id: &Id) -> Option<&[Id]> {
+        self.potential.get(arg_id).map(Vec::as_slice)
+    }
+}
 
-                // Overrides are implicitly conflicts
-                conf.extend(arg.overrides.iter().cloned());
+fn gather_direct_conflicts(cmd: &Command, id: &Id) -> Vec<Id> {
+    let conf = if let Some(arg) = cmd.find(id) {
+        gather_arg_direct_conflicts(cmd, arg)
+    } else if let Some(group) = cmd.find_group(id) {
+        gather_group_direct_conflicts(group)
+    } else {
+        debug_assert!(false, "id={:?} is unknown", id);
+        Vec::new()
+    };
+    debug!(
+        "Conflicts::gather_direct_conflicts id={:?}, conflicts={:?}",
+        id, conf
+    );
+    conf
+}
 
-                conf
-            } else if let Some(group) = cmd.find_group(arg_id) {
-                group.conflicts.clone()
-            } else {
-                debug_assert!(false, "id={:?} is unknown", arg_id);
-                Vec::new()
-            };
-            debug!(
-                "Conflicts::gather_direct_conflicts id={:?}, conflicts={:?}",
-                arg_id, conf
-            );
-            conf
-        })
+fn gather_arg_direct_conflicts(cmd: &Command, arg: &Arg) -> Vec<Id> {
+    let mut conf = arg.blacklist.clone();
+    for group_id in cmd.groups_for_arg(arg.get_id()) {
+        let group = cmd.find_group(&group_id).expect(INTERNAL_ERROR_MSG);
+        conf.extend(group.conflicts.iter().cloned());
+        if !group.multiple {
+            for member_id in &group.args {
+                if member_id != arg.get_id() {
+                    conf.push(member_id.clone());
+                }
+            }
+        }
     }
+
+    // Overrides are implicitly conflicts
+    conf.extend(arg.overrides.iter().cloned());
+
+    conf
+}
+
+fn gather_group_direct_conflicts(group: &ArgGroup) -> Vec<Id> {
+    group.conflicts.clone()
 }
 
 pub(crate) fn get_possible_values_cli(a: &Arg) -> Vec<PossibleValue> {
diff --git a/src/util/mod.rs b/src/util/mod.rs
index ed38fbc5..e6a8f70e 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -1,7 +1,7 @@
 #![allow(clippy::single_component_path_imports)]
 
-mod flat_map;
-mod flat_set;
+pub(crate) mod flat_map;
+pub(crate) mod flat_set;
 mod graph;
 mod id;
 mod str_to_bool;


Test patch:
diff --git a/tests/builder/occurrences.rs b/tests/builder/grouped_values.rs
similarity index 88%
rename from tests/builder/occurrences.rs
rename to tests/builder/grouped_values.rs
index 56716cb7..726eac92 100644
--- a/tests/builder/occurrences.rs
+++ b/tests/builder/grouped_values.rs
@@ -1,13 +1,6 @@
 #![cfg(feature = "unstable-grouped")]
 
-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()
-}
+use clap::{Arg, ArgAction, Command};
 
 #[test]
 fn grouped_value_works() {
@@ -29,7 +22,7 @@ fn grouped_value_works() {
             "en_US:my option 2",
         ])
         .unwrap();
-    let grouped_vals = occurrences_as_vec_vec(&m, "option");
+    let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect();
     assert_eq!(
         grouped_vals,
         vec![
@@ -57,7 +50,7 @@ fn issue_1026() {
             "target3", "file8",
         ])
         .unwrap();
-    let grouped_vals = occurrences_as_vec_vec(&m, "target");
+    let grouped_vals: Vec<_> = m.grouped_values_of("target").unwrap().collect();
     assert_eq!(
         grouped_vals,
         vec![
@@ -87,7 +80,7 @@ fn grouped_value_long_flag_delimiter() {
             "alice,bob",
         ])
         .unwrap();
-    let grouped_vals = occurrences_as_vec_vec(&m, "option");
+    let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect();
     assert_eq!(
         grouped_vals,
         vec![
@@ -111,7 +104,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 = occurrences_as_vec_vec(&m, "option");
+    let grouped_vals: Vec<_> = m.grouped_values_of("option").unwrap().collect();
     assert_eq!(
         grouped_vals,
         vec![vec!["foo"], vec!["val1", "val2", "val3"], vec!["bar"]]
@@ -131,7 +124,7 @@ fn grouped_value_positional_arg() {
             "myprog", "val1", "val2", "val3", "val4", "val5", "val6",
         ])
         .unwrap();
-    let grouped_vals = occurrences_as_vec_vec(&m, "pos");
+    let grouped_vals: Vec<_> = m.grouped_values_of("pos").unwrap().collect();
     assert_eq!(
         grouped_vals,
         vec![vec!["val1", "val2", "val3", "val4", "val5", "val6"]]
@@ -152,7 +145,7 @@ fn grouped_value_multiple_positional_arg() {
             "myprog", "val1", "val2", "val3", "val4", "val5", "val6",
         ])
         .unwrap();
-    let grouped_vals = occurrences_as_vec_vec(&m, "pos2");
+    let grouped_vals: Vec<_> = m.grouped_values_of("pos2").unwrap().collect();
     assert_eq!(
         grouped_vals,
         vec![vec!["val2", "val3", "val4", "val5", "val6"]]
@@ -174,7 +167,7 @@ fn grouped_value_multiple_positional_arg_last_multiple() {
             "myprog", "val1", "--", "val2", "val3", "val4", "val5", "val6",
         ])
         .unwrap();
-    let grouped_vals = occurrences_as_vec_vec(&m, "pos2");
+    let grouped_vals: Vec<_> = m.grouped_values_of("pos2").unwrap().collect();
     assert_eq!(
         grouped_vals,
         vec![vec!["val2", "val3", "val4", "val5", "val6"]]
@@ -197,10 +190,10 @@ fn grouped_interleaved_positional_values() {
         .try_get_matches_from(["foo", "1", "2", "-f", "a", "3", "-f", "b", "4"])
         .unwrap();
 
-    let pos = occurrences_as_vec_vec(&m, "pos");
+    let pos: Vec<_> = m.grouped_values_of("pos").unwrap().collect();
     assert_eq!(pos, vec![vec!["1", "2"], vec!["3"], vec!["4"]]);
 
-    let flag = occurrences_as_vec_vec(&m, "flag");
+    let flag: Vec<_> = m.grouped_values_of("flag").unwrap().collect();
     assert_eq!(flag, vec![vec!["a"], vec!["b"]]);
 }
 
@@ -220,10 +213,10 @@ fn grouped_interleaved_positional_occurrences() {
         .try_get_matches_from(["foo", "1", "2", "-f", "a", "3", "-f", "b", "4"])
         .unwrap();
 
-    let pos = occurrences_as_vec_vec(&m, "pos");
+    let pos: Vec<_> = m.grouped_values_of("pos").unwrap().collect();
     assert_eq!(pos, vec![vec!["1", "2"], vec!["3"], vec!["4"]]);
 
-    let flag = occurrences_as_vec_vec(&m, "flag");
+    let flag: Vec<_> = m.grouped_values_of("flag").unwrap().collect();
     assert_eq!(flag, vec![vec!["a"], vec!["b"]]);
 }
 
diff --git a/tests/builder/help.rs b/tests/builder/help.rs
index dbd8084a..c448f711 100644
--- a/tests/builder/help.rs
+++ b/tests/builder/help.rs
@@ -1042,7 +1042,6 @@ Options:
   -V, --version  Print version information
 ";
 
-    #[cfg(not(feature = "unstable-v5"))]
     let cmd = Command::new("ripgrep")
         .version("0.5")
         .override_usage(
@@ -1058,26 +1057,6 @@ Options:
 
 Usage: {usage}
 
-Options:
-{options}",
-        );
-
-    #[cfg(feature = "unstable-v5")]
-    let cmd = Command::new("ripgrep")
-        .version("0.5")
-        .override_usage(
-            "\
-       rg [OPTIONS] <pattern> [<path> ...]
-       rg [OPTIONS] [-e PATTERN | -f FILE ]... [<path> ...]
-       rg [OPTIONS] --files [<path> ...]
-       rg [OPTIONS] --type-list",
-        )
-        .help_template(
-            "\
-{name} {version}
-
-Usage: {usage}
-
 Options:
 {options}",
         );
diff --git a/tests/builder/main.rs b/tests/builder/main.rs
index 22fdd5f0..858f2875 100644
--- a/tests/builder/main.rs
+++ b/tests/builder/main.rs
@@ -24,6 +24,7 @@ mod error;
 mod flag_subcommands;
 mod flags;
 mod global_args;
+mod grouped_values;
 mod groups;
 mod help;
 mod help_env;
@@ -32,7 +33,6 @@ 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/template_help.rs b/tests/builder/template_help.rs
index 4d305a24..3df5ab46 100644
--- a/tests/builder/template_help.rs
+++ b/tests/builder/template_help.rs
@@ -2,7 +2,6 @@ use super::utils;
 
 use clap::{arg, Command};
 
-#[cfg(not(feature = "unstable-v5"))]
 static EXAMPLE1_TMPL_S: &str = "{bin} {version}
 {author}
 {about}
@@ -11,16 +10,6 @@ Usage: {usage}
 
 {all-args}";
 
-#[cfg(feature = "unstable-v5")]
-static EXAMPLE1_TMPL_S: &str = "{name} {version}
-{author}
-{about}
-
-Usage: {usage}
-
-{all-args}";
-
-#[cfg(not(feature = "unstable-v5"))]
 static EXAMPLE1_TMPS_F: &str = "{bin} {version}
 {author}
 {about}
@@ -34,20 +23,6 @@ Arguments:
 Commands:
 {subcommands}";
 
-#[cfg(feature = "unstable-v5")]
-static EXAMPLE1_TMPS_F: &str = "{name} {version}
-{author}
-{about}
-
-Usage: {usage}
-
-Options:
-{options}
-Arguments:
-{positionals}
-Commands:
-{subcommands}";
-
 static CUSTOM_TEMPL_HELP: &str = "MyApp 1.0
 Kevin K. <kbknapp@gmail.com>
 Does awesome things
@@ -130,20 +105,11 @@ fn template_unknowntag() {
 
 #[test]
 fn template_author_version() {
-    #[cfg(not(feature = "unstable-v5"))]
     let cmd = Command::new("MyApp")
         .version("1.0")
         .author("Kevin K. <kbknapp@gmail.com>")
         .about("Does awesome things")
         .help_template("{author}\n{version}\n{about}\n{bin}");
-
-    #[cfg(feature = "unstable-v5")]
-    let cmd = Command::new("MyApp")
-        .version("1.0")
-        .author("Kevin K. <kbknapp@gmail.com>")
-        .about("Does awesome things")
-        .help_template("{author}\n{version}\n{about}\n{name}");
-
     utils::assert_output(
         cmd,
         "MyApp --help",