diff --git a/Cargo.toml b/Cargo.toml index 41df1ac10fa..f4f3f97f134 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -168,7 +168,8 @@ string = ["clap_builder/string"] # Allow runtime generated strings # In-work features unstable-v5 = ["clap_builder/unstable-v5", "clap_derive?/unstable-v5", "deprecated"] -unstable-styles = ["clap_builder/unstable-styles"] +unstable-ext = [] +unstable-styles = ["clap_builder/unstable-styles"] # deprecated [lib] bench = false diff --git a/clap_builder/Cargo.toml b/clap_builder/Cargo.toml index 5a2c9184009..5ae18d9ca37 100644 --- a/clap_builder/Cargo.toml +++ b/clap_builder/Cargo.toml @@ -52,7 +52,8 @@ string = [] # Allow runtime generated strings # In-work features unstable-v5 = ["deprecated"] -unstable-styles = ["color"] +unstable-ext = [] +unstable-styles = ["color"] # deprecated [lib] bench = false diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs index a40d0cca419..6768d17d814 100644 --- a/clap_builder/src/builder/arg.rs +++ b/clap_builder/src/builder/arg.rs @@ -11,6 +11,9 @@ use std::{ // Internal use super::{ArgFlags, ArgSettings}; +#[cfg(feature = "unstable-ext")] +use crate::builder::ext::Extension; +use crate::builder::ext::Extensions; use crate::builder::ArgPredicate; use crate::builder::IntoResettable; use crate::builder::OsStr; @@ -85,7 +88,7 @@ pub struct Arg { pub(crate) terminator: Option, pub(crate) index: Option, pub(crate) help_heading: Option>, - pub(crate) value_hint: Option, + pub(crate) ext: Extensions, } /// # Basic API @@ -869,6 +872,18 @@ impl Arg { self.settings.unset(setting); self } + + /// Extend [`Arg`] with [`ArgExt`] data + #[cfg(feature = "unstable-ext")] + pub fn add(&mut self, tagged: T) -> bool { + self.ext.set(tagged) + } + + /// Remove an [`ArgExt`] + #[cfg(feature = "unstable-ext")] + pub fn remove(&mut self) -> Option { + self.ext.remove::() + } } /// # Value Handling @@ -1291,7 +1306,15 @@ impl Arg { /// ``` #[must_use] pub fn value_hint(mut self, value_hint: impl IntoResettable) -> Self { - self.value_hint = value_hint.into_resettable().into_option(); + // HACK: we should use `Self::add` and `Self::remove` to type-check that `ArgExt` is used + match value_hint.into_resettable().into_option() { + Some(value_hint) => { + self.ext.set(value_hint); + } + None => { + self.ext.remove::(); + } + } self } @@ -4022,7 +4045,8 @@ impl Arg { /// Get the value hint of this argument pub fn get_value_hint(&self) -> ValueHint { - self.value_hint.unwrap_or_else(|| { + // HACK: we should use `Self::add` and `Self::remove` to type-check that `ArgExt` is used + self.ext.get::().copied().unwrap_or_else(|| { if self.is_takes_value_set() { let type_id = self.get_value_parser().type_id(); if type_id == AnyValueId::of::() { @@ -4210,6 +4234,12 @@ impl Arg { pub fn is_ignore_case_set(&self) -> bool { self.is_set(ArgSettings::IgnoreCase) } + + /// Access an [`ArgExt`] + #[cfg(feature = "unstable-ext")] + pub fn get(&self) -> Option<&T> { + self.ext.get::() + } } /// # Internally used only @@ -4484,8 +4514,8 @@ impl fmt::Debug for Arg { .field("terminator", &self.terminator) .field("index", &self.index) .field("help_heading", &self.help_heading) - .field("value_hint", &self.value_hint) - .field("default_missing_vals", &self.default_missing_vals); + .field("default_missing_vals", &self.default_missing_vals) + .field("ext", &self.ext); #[cfg(feature = "env")] { @@ -4496,6 +4526,10 @@ impl fmt::Debug for Arg { } } +/// User-provided data that can be attached to an [`Arg`] +#[cfg(feature = "unstable-ext")] +pub trait ArgExt: Extension {} + // Flags #[cfg(test)] mod test { diff --git a/clap_builder/src/builder/ext.rs b/clap_builder/src/builder/ext.rs index cf8eab9a9bd..974b61cb215 100644 --- a/clap_builder/src/builder/ext.rs +++ b/clap_builder/src/builder/ext.rs @@ -1,43 +1,36 @@ +use crate::util::AnyValue; use crate::util::AnyValueId; use crate::util::FlatMap; #[derive(Default, Clone, Debug)] pub(crate) struct Extensions { - extensions: FlatMap, + extensions: FlatMap, } impl Extensions { #[allow(dead_code)] pub(crate) fn get(&self) -> Option<&T> { let id = AnyValueId::of::(); - self.extensions.get(&id).map(|e| e.as_ref::()) + self.extensions.get(&id).map(|e| { + e.downcast_ref::() + .expect("`Extensions` tracks values by type") + }) } #[allow(dead_code)] - pub(crate) fn get_mut(&mut self) -> Option<&mut T> { - let id = AnyValueId::of::(); - self.extensions.get_mut(&id).map(|e| e.as_mut::()) - } - - #[allow(dead_code)] - pub(crate) fn get_or_insert_default(&mut self) -> &mut T { - let id = AnyValueId::of::(); - self.extensions - .entry(id) - .or_insert_with(|| BoxedExtension::new(T::default())) - .as_mut::() - } - - #[allow(dead_code)] - pub(crate) fn set>(&mut self, tagged: T) -> bool { - let BoxedEntry { id, value } = tagged.into(); + pub(crate) fn set(&mut self, tagged: T) -> bool { + let value = AnyValue::new(tagged); + let id = value.type_id(); self.extensions.insert(id, value).is_some() } #[allow(dead_code)] - pub(crate) fn remove(&mut self) -> Option> { + pub(crate) fn remove(&mut self) -> Option { let id = AnyValueId::of::(); - self.extensions.remove(&id).map(BoxedExtension::into_inner) + self.extensions.remove(&id).map(|e| { + e.downcast_into::() + .expect("`Extensions` tracks values by type") + }) } pub(crate) fn update(&mut self, other: &Self) { @@ -47,162 +40,7 @@ impl Extensions { } } -/// Supports conversion to `Any`. Traits to be extended by `impl_downcast!` must extend `Extension`. -pub(crate) trait Extension: std::fmt::Debug + Send + Sync + 'static { - /// Clone `&Box` (where `Trait: Extension`) to `Box`. - /// - /// `Box` can /// then be further `downcast` into - // `Box` where `ConcreteType` implements `Trait`. - fn clone_extension(&self) -> Box; - /// Convert `&Trait` (where `Trait: Extension`) to `&Any`. - /// - /// This is needed since Rust cannot /// generate `&Any`'s vtable from - /// `&Trait`'s. - fn as_any(&self) -> &dyn std::any::Any; - /// Convert `&mut Trait` (where `Trait: Extension`) to `&Any`. - /// - /// This is needed since Rust cannot /// generate `&mut Any`'s vtable from - /// `&mut Trait`'s. - fn as_any_mut(&mut self) -> &mut dyn std::any::Any; -} - -impl Extension for T -where - T: Clone + std::fmt::Debug + Send + Sync + 'static, -{ - fn clone_extension(&self) -> Box { - Box::new(self.clone()) - } - fn as_any(&self) -> &dyn std::any::Any { - self - } - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.as_ref().clone_extension() - } -} - -#[derive(Clone)] -#[repr(transparent)] -struct BoxedExtension(Box); - -impl BoxedExtension { - fn new(inner: T) -> Self { - Self(Box::new(inner)) - } - - fn into_inner(self) -> Box { - self.0 - } - - fn as_ref(&self) -> &T { - self.0.as_ref().as_any().downcast_ref::().unwrap() - } - - fn as_mut(&mut self) -> &mut T { - self.0.as_mut().as_any_mut().downcast_mut::().unwrap() - } -} - -impl std::fmt::Debug for BoxedExtension { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - self.0.fmt(f) - } -} +#[allow(unreachable_pub)] +pub trait Extension: std::fmt::Debug + Clone + std::any::Any + Send + Sync + 'static {} -#[derive(Clone)] -pub(crate) struct BoxedEntry { - id: AnyValueId, - value: BoxedExtension, -} - -impl BoxedEntry { - pub(crate) fn new(r: impl Extension) -> Self { - let id = AnyValueId::from(&r); - let value = BoxedExtension::new(r); - BoxedEntry { id, value } - } -} - -impl From for BoxedEntry { - fn from(inner: R) -> Self { - BoxedEntry::new(inner) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] - struct Number(usize); - - #[test] - fn get() { - let mut ext = Extensions::default(); - ext.set(Number(10)); - assert_eq!(ext.get::(), Some(&Number(10))); - } - - #[test] - fn get_mut() { - let mut ext = Extensions::default(); - ext.set(Number(10)); - *ext.get_mut::().unwrap() = Number(20); - assert_eq!(ext.get::(), Some(&Number(20))); - } - - #[test] - fn get_or_insert_default_empty() { - let mut ext = Extensions::default(); - assert_eq!(ext.get_or_insert_default::(), &Number(0)); - } - - #[test] - fn get_or_insert_default_full() { - let mut ext = Extensions::default(); - ext.set(Number(10)); - assert_eq!(ext.get_or_insert_default::(), &Number(10)); - } - - #[test] - fn set() { - let mut ext = Extensions::default(); - assert!(!ext.set(Number(10))); - assert_eq!(ext.get::(), Some(&Number(10))); - assert!(ext.set(Number(20))); - assert_eq!(ext.get::(), Some(&Number(20))); - } - - #[test] - fn reset() { - let mut ext = Extensions::default(); - assert_eq!(ext.get::(), None); - - assert!(ext.remove::().is_none()); - assert_eq!(ext.get::(), None); - - assert!(!ext.set(Number(10))); - assert_eq!(ext.get::(), Some(&Number(10))); - - assert!(ext.remove::().is_some()); - assert_eq!(ext.get::(), None); - } - - #[test] - fn update() { - let mut ext = Extensions::default(); - assert_eq!(ext.get::(), None); - - let mut new = Extensions::default(); - assert!(!new.set(Number(10))); - - ext.update(&new); - assert_eq!(ext.get::(), Some(&Number(10))); - } -} +impl Extension for T where T: std::fmt::Debug + Clone + std::any::Any + Send + Sync + 'static {} diff --git a/clap_builder/src/builder/mod.rs b/clap_builder/src/builder/mod.rs index 320a45344c0..b428fb03914 100644 --- a/clap_builder/src/builder/mod.rs +++ b/clap_builder/src/builder/mod.rs @@ -28,6 +28,8 @@ pub mod styling; pub use self::str::Str; pub use action::ArgAction; pub use arg::Arg; +#[cfg(feature = "unstable-ext")] +pub use arg::ArgExt; pub use arg_group::ArgGroup; pub use arg_predicate::ArgPredicate; pub use command::Command; diff --git a/clap_builder/src/builder/value_hint.rs b/clap_builder/src/builder/value_hint.rs index 4f4e64be229..71827fb7656 100644 --- a/clap_builder/src/builder/value_hint.rs +++ b/clap_builder/src/builder/value_hint.rs @@ -67,6 +67,9 @@ pub enum ValueHint { EmailAddress, } +#[cfg(feature = "unstable-ext")] +impl crate::builder::ArgExt for ValueHint {} + impl FromStr for ValueHint { type Err = String; fn from_str(s: &str) -> Result::Err> {