mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 14:22:34 +00:00
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
This commit is contained in:
parent
d2d022248b
commit
4a34b9dd43
3 changed files with 84 additions and 68 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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,7 +91,7 @@ impl<'cmd> Validator<'cmd> {
|
|||
fn validate_conflicts(
|
||||
&mut self,
|
||||
matcher: &ArgMatcher,
|
||||
conflicts: &mut Conflicts,
|
||||
conflicts: &Conflicts,
|
||||
) -> ClapResult<()> {
|
||||
debug!("Validator::validate_conflicts");
|
||||
|
||||
|
@ -103,7 +103,7 @@ impl<'cmd> Validator<'cmd> {
|
|||
.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));
|
||||
}
|
||||
|
||||
|
@ -243,11 +243,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);
|
||||
|
||||
|
@ -276,7 +272,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 +370,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()
|
||||
}
|
||||
|
||||
|
@ -465,73 +456,94 @@ 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides are implicitly conflicts
|
||||
conf.extend(arg.overrides.iter().cloned());
|
||||
|
||||
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 get_direct_conflicts(&self, arg_id: &Id) -> Option<&[Id]> {
|
||||
self.potential.get(arg_id).map(Vec::as_slice)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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> {
|
||||
if !a.is_takes_value_set() {
|
||||
vec![]
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue