perf(parser): Don't allocate on every call

This commit is contained in:
Ed Page 2022-05-16 13:11:35 -05:00
parent 36236957e4
commit c7bd8c891d
3 changed files with 68 additions and 23 deletions

View file

@ -139,6 +139,7 @@ impl ArgMatcher {
id, source
);
let ma = self.entry(id).or_insert(MatchedArg::new_arg(arg));
debug_assert_eq!(ma.type_id(), Some(arg.get_value_parser().type_id()));
ma.set_source(source);
}
@ -148,6 +149,7 @@ impl ArgMatcher {
id, source
);
let ma = self.entry(id).or_insert(MatchedArg::new_group());
debug_assert_eq!(ma.type_id(), None);
ma.set_source(source);
}
@ -155,6 +157,7 @@ impl ArgMatcher {
let id = &arg.id;
debug!("ArgMatcher::start_occurrence_of_arg: id={:?}", id);
let ma = self.entry(id).or_insert(MatchedArg::new_arg(arg));
debug_assert_eq!(ma.type_id(), Some(arg.get_value_parser().type_id()));
ma.set_source(ValueSource::CommandLine);
ma.inc_occurrences();
}
@ -162,6 +165,7 @@ impl ArgMatcher {
pub(crate) fn start_occurrence_of_group(&mut self, id: &Id) {
debug!("ArgMatcher::start_occurrence_of_group: id={:?}", id);
let ma = self.entry(id).or_insert(MatchedArg::new_group());
debug_assert_eq!(ma.type_id(), None);
ma.set_source(ValueSource::CommandLine);
ma.inc_occurrences();
}
@ -170,6 +174,14 @@ impl ArgMatcher {
let id = &Id::empty_hash();
debug!("ArgMatcher::start_occurrence_of_external: id={:?}", id,);
let ma = self.entry(id).or_insert(MatchedArg::new_external(cmd));
debug_assert_eq!(
ma.type_id(),
Some(
cmd.get_external_subcommand_value_parser()
.expect(INTERNAL_ERROR_MSG)
.type_id()
)
);
ma.set_source(ValueSource::CommandLine);
ma.inc_occurrences();
}

View file

@ -17,6 +17,7 @@ use crate::parser::MatchesError;
use crate::parser::ValueSource;
use crate::util::{Id, Key};
use crate::Error;
use crate::INTERNAL_ERROR_MSG;
/// Container for parse results.
///
@ -115,19 +116,17 @@ impl ArgMatches {
/// [`occurrences_of`]: crate::ArgMatches::occurrences_of()
pub fn get_one<T: 'static>(&self, name: &str) -> Result<Option<&T>, MatchesError> {
let id = Id::from(name);
let value = match self.try_get_arg(&id)?.and_then(|a| a.first()) {
let arg = self.try_get_arg_t::<T>(&id)?;
let value = match arg.and_then(|a| a.first()) {
Some(value) => value,
None => {
return Ok(None);
}
};
value
Ok(value
.downcast_ref::<T>()
.map(Some)
.ok_or_else(|| MatchesError::Downcast {
actual: value.type_id(),
expected: AnyValueId::of::<T>(),
})
.expect(INTERNAL_ERROR_MSG)) // enforced by `try_get_arg_t`
}
/// Iterate over [values] of a specific option or positional argument.
@ -164,22 +163,15 @@ impl ArgMatches {
name: &str,
) -> Result<Option<impl Iterator<Item = &T>>, MatchesError> {
let id = Id::from(name);
let values = match self.try_get_arg(&id)? {
Some(values) => values.vals_flatten(),
None => {
return Ok(None);
}
};
// HACK: Track the type id and report errors even when there are no values
let values: Result<Vec<&T>, MatchesError> = values
.map(|v| {
v.downcast_ref::<T>().ok_or_else(|| MatchesError::Downcast {
actual: v.type_id(),
expected: AnyValueId::of::<T>(),
})
})
.collect();
Ok(Some(values?.into_iter()))
match self.try_get_arg_t::<T>(&id)? {
Some(values) => Ok(Some(
values
.vals_flatten()
// enforced by `try_get_arg_t`
.map(|v| v.downcast_ref::<T>().expect(INTERNAL_ERROR_MSG)),
)),
None => Ok(None),
}
}
/// Iterate over the original argument values.
@ -1288,6 +1280,31 @@ impl ArgMatches {
Ok(self.args.get(arg))
}
#[inline]
fn try_get_arg_t<T: 'static>(&self, arg: &Id) -> Result<Option<&MatchedArg>, MatchesError> {
let expected = AnyValueId::of::<T>();
let arg = match self.try_get_arg(arg)? {
Some(arg) => arg,
None => {
return Ok(None);
}
};
let actual = arg
.type_id()
.or_else(|| {
arg.vals_flatten()
.map(|v| v.type_id())
.find(|actual| *actual != expected)
})
.unwrap_or(expected);
if expected == actual {
Ok(Some(arg))
} else {
Err(MatchesError::Downcast { actual, expected })
}
}
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
fn get_arg(&self, arg: &Id) -> Option<&MatchedArg> {

View file

@ -7,6 +7,7 @@ use std::{
use crate::builder::ArgPredicate;
use crate::parser::AnyValue;
use crate::parser::AnyValueId;
use crate::parser::ValueSource;
use crate::util::eq_ignore_case;
use crate::INTERNAL_ERROR_MSG;
@ -16,6 +17,7 @@ pub(crate) struct MatchedArg {
occurs: u64,
source: Option<ValueSource>,
indices: Vec<usize>,
type_id: Option<AnyValueId>,
vals: Vec<Vec<AnyValue>>,
raw_vals: Vec<Vec<OsString>>,
ignore_case: bool,
@ -28,6 +30,7 @@ impl MatchedArg {
occurs: 0,
source: None,
indices: Vec::new(),
type_id: Some(arg.get_value_parser().type_id()),
vals: Vec::new(),
raw_vals: Vec::new(),
ignore_case,
@ -40,18 +43,24 @@ impl MatchedArg {
occurs: 0,
source: None,
indices: Vec::new(),
type_id: None,
vals: Vec::new(),
raw_vals: Vec::new(),
ignore_case,
}
}
pub(crate) fn new_external(_cmd: &crate::Command) -> Self {
pub(crate) fn new_external(cmd: &crate::Command) -> Self {
let ignore_case = false;
Self {
occurs: 0,
source: None,
indices: Vec::new(),
type_id: Some(
cmd.get_external_subcommand_value_parser()
.expect(INTERNAL_ERROR_MSG)
.type_id(),
),
vals: Vec::new(),
raw_vals: Vec::new(),
ignore_case,
@ -171,6 +180,10 @@ impl MatchedArg {
self.source = Some(source)
}
}
pub(crate) fn type_id(&self) -> Option<AnyValueId> {
self.type_id
}
}
impl PartialEq for MatchedArg {
@ -179,6 +192,7 @@ impl PartialEq for MatchedArg {
occurs: self_occurs,
source: self_source,
indices: self_indices,
type_id: self_type_id,
vals: _,
raw_vals: self_raw_vals,
ignore_case: self_ignore_case,
@ -187,6 +201,7 @@ impl PartialEq for MatchedArg {
occurs: other_occurs,
source: other_source,
indices: other_indices,
type_id: other_type_id,
vals: _,
raw_vals: other_raw_vals,
ignore_case: other_ignore_case,
@ -194,6 +209,7 @@ impl PartialEq for MatchedArg {
self_occurs == other_occurs
&& self_source == other_source
&& self_indices == other_indices
&& self_type_id == other_type_id
&& self_raw_vals == other_raw_vals
&& self_ignore_case == other_ignore_case
}