Repository: clap-rs/clap
Pull request #3794 (2022-06-06 16:49:50)

feat(derive): Expose control over Actions
This is the derive support for #3774 (see also #3775, #3777)

This combined with `value_parser` replaces `parser`.  The main
frustration with this is that `ArgAction::Count` (the replacement for
`parse(from_occurrences)` must be a `u64`.  We could come up with a
magic attribute that is meant to be the value parser's parsed type.  We
could then use `TryFrom` to convert the parsed type to the user's type
to allow more.  That is an exercise for the future.  Alternatively, we
have #3792.

Prep for this included
- #3782
- #3783
- #3786
- #3789
- #3793


Patch:
diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs
index dbe8115b..5eb12450 100644
--- a/clap_derive/src/attrs.rs
+++ b/clap_derive/src/attrs.rs
@@ -43,6 +43,7 @@ pub struct Attrs {
     doc_comment: Vec<Method>,
     methods: Vec<Method>,
     value_parser: Option<ValueParser>,
+    action: Option<Action>,
     parser: Option<Sp<Parser>>,
     verbatim_doc_comment: Option<Ident>,
     next_display_order: Option<Method>,
@@ -70,6 +71,12 @@ impl Attrs {
                 "`value_parser` attribute is only allowed on fields"
             );
         }
+        if let Some(action) = res.action.as_ref() {
+            abort!(
+                action.span(),
+                "`action` attribute is only allowed on fields"
+            );
+        }
         if let Some(parser) = res.parser.as_ref() {
             abort!(parser.span(), "`parse` attribute is only allowed on fields");
         }
@@ -110,6 +117,12 @@ impl Attrs {
                         "`value_parser` attribute is not allowed for flattened entry"
                     );
                 }
+                if let Some(action) = res.action.as_ref() {
+                    abort!(
+                        action.span(),
+                        "`action` attribute is not allowed for flattened entry"
+                    );
+                }
                 if let Some(parser) = res.parser.as_ref() {
                     abort!(
                         parser.span(),
@@ -136,6 +149,12 @@ impl Attrs {
                         "`value_parser` attribute is not allowed for subcommand"
                     );
                 }
+                if let Some(action) = res.action.as_ref() {
+                    abort!(
+                        action.span(),
+                        "`action` attribute is not allowed for subcommand"
+                    );
+                }
                 if let Some(parser) = res.parser.as_ref() {
                     abort!(
                         parser.span(),
@@ -210,6 +229,12 @@ impl Attrs {
                 "`value_parser` attribute is only allowed on fields"
             );
         }
+        if let Some(action) = res.action.as_ref() {
+            abort!(
+                action.span(),
+                "`action` attribute is only allowed on fields"
+            );
+        }
         if let Some(parser) = res.parser.as_ref() {
             abort!(parser.span(), "`parse` attribute is only allowed on fields");
         }
@@ -250,6 +275,12 @@ impl Attrs {
                         "`value_parser` attribute is not allowed for flattened entry"
                     );
                 }
+                if let Some(action) = res.action.as_ref() {
+                    abort!(
+                        action.span(),
+                        "`action` attribute is not allowed for flattened entry"
+                    );
+                }
                 if let Some(parser) = res.parser.as_ref() {
                     abort!(
                         parser.span(),
@@ -280,6 +311,12 @@ impl Attrs {
                         "`value_parser` attribute is not allowed for subcommand"
                     );
                 }
+                if let Some(action) = res.action.as_ref() {
+                    abort!(
+                        action.span(),
+                        "`action` attribute is not allowed for subcommand"
+                    );
+                }
                 if let Some(parser) = res.parser.as_ref() {
                     abort!(
                         parser.span(),
@@ -333,6 +370,12 @@ impl Attrs {
                             "`value_parser` attribute conflicts with `parse` attribute"
                         );
                     }
+                    if let Some(action) = res.action.as_ref() {
+                        abort!(
+                            action.span(),
+                            "`action` attribute conflicts with `parse` attribute"
+                        );
+                    }
                     match *ty {
                         Ty::Option | Ty::Vec | Ty::OptionVec => (),
                         _ => ty = Sp::new(Ty::Other, ty.span()),
@@ -386,6 +429,7 @@ impl Attrs {
             doc_comment: vec![],
             methods: vec![],
             value_parser: None,
+            action: None,
             parser: None,
             verbatim_doc_comment: None,
             next_display_order: None,
@@ -401,6 +445,8 @@ impl Attrs {
             self.name = Name::Assigned(quote!(#arg));
         } else if name == "value_parser" {
             self.value_parser = Some(ValueParser::Explicit(Method::new(name, quote!(#arg))));
+        } else if name == "action" {
+            self.action = Some(Action::Explicit(Method::new(name, quote!(#arg))));
         } else {
             self.methods.push(Method::new(name, quote!(#arg)));
         }
@@ -426,6 +472,11 @@ impl Attrs {
                     self.value_parser = Some(ValueParser::Implicit(ident));
                 }
 
+                Action(ident) => {
+                    use crate::attrs::Action;
+                    self.action = Some(Action::Implicit(ident));
+                }
+
                 Env(ident) => {
                     self.push_method(ident, self.name.clone().translate(*self.env_casing));
                 }
@@ -718,11 +769,31 @@ impl Attrs {
                 let inner_type = inner_type(field_type);
                 p.resolve(inner_type)
             })
-            .unwrap_or_else(|| self.parser(field_type).value_parser())
+            .unwrap_or_else(|| {
+                if let Some(action) = self.action.as_ref() {
+                    let inner_type = inner_type(field_type);
+                    default_value_parser(inner_type, action.span())
+                } else {
+                    self.parser(field_type).value_parser()
+                }
+            })
+    }
+
+    pub fn action(&self, field_type: &Type) -> Method {
+        self.action
+            .clone()
+            .map(|p| p.resolve(field_type))
+            .unwrap_or_else(|| {
+                if let Some(value_parser) = self.value_parser.as_ref() {
+                    default_action(field_type, value_parser.span())
+                } else {
+                    self.parser(field_type).action()
+                }
+            })
     }
 
     pub fn ignore_parser(&self) -> bool {
-        self.value_parser.is_some()
+        self.value_parser.is_some() || self.action.is_some()
     }
 
     pub fn parser(&self, field_type: &Type) -> Sp<Parser> {
@@ -780,15 +851,7 @@ impl ValueParser {
     fn resolve(self, inner_type: &Type) -> Method {
         match self {
             Self::Explicit(method) => method,
-            Self::Implicit(ident) => {
-                let func = Ident::new("value_parser", ident.span());
-                Method::new(
-                    func,
-                    quote_spanned! { ident.span()=>
-                        clap::value_parser!(#inner_type)
-                    },
-                )
-            }
+            Self::Implicit(ident) => default_value_parser(inner_type, ident.span()),
         }
     }
 
@@ -800,6 +863,68 @@ impl ValueParser {
     }
 }
 
+fn default_value_parser(inner_type: &Type, span: Span) -> Method {
+    let func = Ident::new("value_parser", span);
+    Method::new(
+        func,
+        quote_spanned! { span=>
+            clap::value_parser!(#inner_type)
+        },
+    )
+}
+
+#[derive(Clone)]
+pub enum Action {
+    Explicit(Method),
+    Implicit(Ident),
+}
+
+impl Action {
+    pub fn resolve(self, field_type: &Type) -> Method {
+        match self {
+            Self::Explicit(method) => method,
+            Self::Implicit(ident) => default_action(field_type, ident.span()),
+        }
+    }
+
+    pub fn span(&self) -> Span {
+        match self {
+            Self::Explicit(method) => method.name.span(),
+            Self::Implicit(ident) => ident.span(),
+        }
+    }
+}
+
+fn default_action(field_type: &Type, span: Span) -> Method {
+    let ty = Ty::from_syn_ty(field_type);
+    let args = match *ty {
+        Ty::Vec | Ty::OptionVec => {
+            quote_spanned! { span=>
+                clap::ArgAction::Append
+            }
+        }
+        Ty::Option | Ty::OptionOption => {
+            quote_spanned! { span=>
+                clap::ArgAction::Set
+            }
+        }
+        _ => {
+            if is_simple_ty(field_type, "bool") {
+                quote_spanned! { span=>
+                    clap::ArgAction::SetTrue
+                }
+            } else {
+                quote_spanned! { span=>
+                    clap::ArgAction::Set
+                }
+            }
+        }
+    };
+
+    let func = Ident::new("action", span);
+    Method::new(func, args)
+}
+
 #[allow(clippy::large_enum_variant)]
 #[derive(Clone)]
 pub enum Kind {
@@ -846,6 +971,10 @@ impl Method {
 
         Some(Method::new(ident, quote!(#lit)))
     }
+
+    pub(crate) fn args(&self) -> &TokenStream {
+        &self.args
+    }
 }
 
 impl ToTokens for Method {
@@ -962,6 +1091,23 @@ impl Parser {
             ),
         }
     }
+
+    fn action(&self) -> Method {
+        let func = Ident::new("action", self.kind.span());
+        match *self.kind {
+            ParserKind::FromStr
+            | ParserKind::TryFromStr
+            | ParserKind::FromOsStr
+            | ParserKind::TryFromOsStr => Method::new(
+                func,
+                quote_spanned! { self.kind.span()=> clap::ArgAction::StoreValue},
+            ),
+            ParserKind::FromOccurrences | ParserKind::FromFlag => Method::new(
+                func,
+                quote_spanned! { self.kind.span()=> clap::ArgAction::IncOccurrence},
+            ),
+        }
+    }
 }
 
 #[derive(Debug, PartialEq, Clone, Copy)]
diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs
index 98b83cc6..47565edb 100644
--- a/clap_derive/src/derives/args.rs
+++ b/clap_derive/src/derives/args.rs
@@ -246,6 +246,7 @@ pub fn gen_augment(
                 let parser = attrs.parser(&field.ty);
 
                 let value_parser = attrs.value_parser(&field.ty);
+                let action = attrs.action(&field.ty);
                 let func = &parser.func;
 
                 let mut occurrences = false;
@@ -287,6 +288,7 @@ pub fn gen_augment(
                             #possible_values
                             #validator
                             #value_parser
+                            #action
                         }
                     }
 
@@ -299,6 +301,7 @@ pub fn gen_augment(
                         #possible_values
                         #validator
                         #value_parser
+                        #action
                     },
 
                     Ty::OptionVec => quote_spanned! { ty.span()=>
@@ -308,6 +311,7 @@ pub fn gen_augment(
                         #possible_values
                         #validator
                         #value_parser
+                        #action
                     },
 
                     Ty::Vec => {
@@ -318,6 +322,7 @@ pub fn gen_augment(
                             #possible_values
                             #validator
                             #value_parser
+                            #action
                         }
                     }
 
@@ -331,13 +336,18 @@ pub fn gen_augment(
 
                     Ty::Other => {
                         let required = attrs.find_default_method().is_none() && !override_required;
+                        // `ArgAction::takes_values` is assuming `ArgAction::default_value` will be
+                        // set though that won't always be true but this should be good enough,
+                        // otherwise we'll report an "arg required" error when unwrapping.
+                        let action_value = action.args();
                         quote_spanned! { ty.span()=>
                             .takes_value(true)
                             .value_name(#value_name)
-                            .required(#required)
+                            .required(#required && #action_value.takes_values())
                             #possible_values
                             #validator
                             #value_parser
+                            #action
                         }
                     }
                 };
diff --git a/clap_derive/src/parse.rs b/clap_derive/src/parse.rs
index 9dbc1454..15cd3e0f 100644
--- a/clap_derive/src/parse.rs
+++ b/clap_derive/src/parse.rs
@@ -27,6 +27,7 @@ pub enum ClapAttr {
     Short(Ident),
     Long(Ident),
     ValueParser(Ident),
+    Action(Ident),
     Env(Ident),
     Flatten(Ident),
     ArgEnum(Ident),
@@ -184,6 +185,7 @@ impl Parse for ClapAttr {
                 "long" => Ok(Long(name)),
                 "short" => Ok(Short(name)),
                 "value_parser" => Ok(ValueParser(name)),
+                "action" => Ok(Action(name)),
                 "env" => Ok(Env(name)),
                 "flatten" => Ok(Flatten(name)),
                 "arg_enum" => Ok(ArgEnum(name)),
diff --git a/examples/derive_ref/README.md b/examples/derive_ref/README.md
index 1b97baf4..35bee90f 100644
--- a/examples/derive_ref/README.md
+++ b/examples/derive_ref/README.md
@@ -179,6 +179,10 @@ These correspond to a `clap::Arg`.
 - `value_parser [= <expr>]`: `clap::Arg::value_parser`
   - When not present: will auto-select an implementation based on the field type
   - To register a custom type's `ValueParser`, implement `ValueParserFactory`
+  - When present, implies `#[clap(action)]`
+- `action [= <expr>]`: `clap::Arg::action`
+  - When not present: will auto-select an action based on the field type
+  - When present, implies `#[clap(value_parser)]`
 - `help = <expr>`: `clap::Arg::help`
   - When not present: [Doc comment summary](#doc-comments)
 - `long_help = <expr>`: `clap::Arg::long_help`
@@ -346,7 +350,7 @@ struct Robo {
     /// I am artificial superintelligence. I won't rest
     /// until I'll have destroyed humanity. Enjoy your
     /// pathetic existence, you mere mortals.
-    #[clap(long)]
+    #[clap(long, action)]
     kill_all_humans: bool,
 }
 ```
diff --git a/examples/derive_ref/augment_args.rs b/examples/derive_ref/augment_args.rs
index 59765ad6..390c72f4 100644
--- a/examples/derive_ref/augment_args.rs
+++ b/examples/derive_ref/augment_args.rs
@@ -2,20 +2,23 @@ use clap::{arg, Args as _, Command, FromArgMatches as _, Parser};
 
 #[derive(Parser, Debug)]
 struct DerivedArgs {
-    #[clap(short, long)]
+    #[clap(short, long, action)]
     derived: bool,
 }
 
 fn main() {
-    let cli = Command::new("CLI").arg(arg!(-b - -built));
+    let cli = Command::new("CLI").arg(arg!(-b - -built).action(clap::ArgAction::SetTrue));
     // Augment built args with derived args
     let cli = DerivedArgs::augment_args(cli);
 
     let matches = cli.get_matches();
-    println!("Value of built: {:?}", matches.is_present("built"));
+    println!(
+        "Value of built: {:?}",
+        *matches.get_one::<bool>("built").unwrap()
+    );
     println!(
         "Value of derived via ArgMatches: {:?}",
-        matches.is_present("derived")
+        *matches.get_one::<bool>("derived").unwrap()
     );
 
     // Since DerivedArgs implements FromArgMatches, we can extract it from the unstructured ArgMatches.
diff --git a/examples/derive_ref/augment_subcommands.rs b/examples/derive_ref/augment_subcommands.rs
index 299c7f8c..199da98b 100644
--- a/examples/derive_ref/augment_subcommands.rs
+++ b/examples/derive_ref/augment_subcommands.rs
@@ -3,7 +3,7 @@ use clap::{Command, FromArgMatches as _, Parser, Subcommand as _};
 #[derive(Parser, Debug)]
 enum Subcommands {
     Derived {
-        #[clap(short, long)]
+        #[clap(short, long, action)]
         derived_flag: bool,
     },
 }
diff --git a/examples/derive_ref/custom-bool.md b/examples/derive_ref/custom-bool.md
index c2c9bb1d..619f9ba8 100644
--- a/examples/derive_ref/custom-bool.md
+++ b/examples/derive_ref/custom-bool.md
@@ -11,11 +11,11 @@ USAGE:
     custom-bool[EXE] [OPTIONS] --foo <FOO> <BOOM>
 
 ARGS:
-    <BOOM>    
+    <BOOM>    [possible values: true, false]
 
 OPTIONS:
         --bar <BAR>    [default: false]
-        --foo <FOO>    
+        --foo <FOO>    [possible values: true, false]
     -h, --help         Print help information
     -V, --version      Print version information
 
@@ -31,14 +31,14 @@ USAGE:
 For more information try --help
 
 $ custom-bool --foo true false
-[examples/derive_ref/custom-bool.rs:32] opt = Opt {
+[examples/derive_ref/custom-bool.rs:31] opt = Opt {
     foo: true,
     bar: false,
     boom: false,
 }
 
 $ custom-bool --foo true --bar true false
-[examples/derive_ref/custom-bool.rs:32] opt = Opt {
+[examples/derive_ref/custom-bool.rs:31] opt = Opt {
     foo: true,
     bar: true,
     boom: false,
diff --git a/examples/derive_ref/custom-bool.rs b/examples/derive_ref/custom-bool.rs
index b0e4c81c..d3c321e7 100644
--- a/examples/derive_ref/custom-bool.rs
+++ b/examples/derive_ref/custom-bool.rs
@@ -1,21 +1,20 @@
-#![allow(deprecated)] // Can't opt-out of implicit flags until #3405
 use clap::Parser;
 
 #[derive(Parser, Debug, PartialEq)]
 #[clap(author, version, about, long_about = None)]
 struct Opt {
-    // Default parser for `try_from_str` is FromStr::from_str.
+    // Default parser for `Set` is FromStr::from_str.
     // `impl FromStr for bool` parses `true` or `false` so this
     // works as expected.
-    #[clap(long, parse(try_from_str))]
+    #[clap(long, action = clap::ArgAction::Set)]
     foo: bool,
 
     // Of course, this could be done with an explicit parser function.
-    #[clap(long, parse(try_from_str = true_or_false), default_value_t)]
+    #[clap(long, action = clap::ArgAction::Set, value_parser = true_or_false, default_value_t)]
     bar: bool,
 
-    // `bool` can be positional only with explicit `parse(...)` annotation
-    #[clap(parse(try_from_str))]
+    // `bool` can be positional only with explicit `action` annotation
+    #[clap(action = clap::ArgAction::Set)]
     boom: bool,
 }
 
diff --git a/examples/derive_ref/hand_subcommand.rs b/examples/derive_ref/hand_subcommand.rs
index d85a2949..e9423bdc 100644
--- a/examples/derive_ref/hand_subcommand.rs
+++ b/examples/derive_ref/hand_subcommand.rs
@@ -8,7 +8,7 @@ struct AddArgs {
 }
 #[derive(Parser, Debug)]
 struct RemoveArgs {
-    #[clap(short, long)]
+    #[clap(short, long, action)]
     force: bool,
     #[clap(value_parser)]
     name: Vec<String>,
@@ -69,7 +69,7 @@ impl Subcommand for CliSub {
 
 #[derive(Parser, Debug)]
 struct Cli {
-    #[clap(short, long)]
+    #[clap(short, long, action)]
     top_level: bool,
     #[clap(subcommand)]
     subcommand: CliSub,
diff --git a/examples/escaped-positional-derive.rs b/examples/escaped-positional-derive.rs
index dd52c090..54dbc853 100644
--- a/examples/escaped-positional-derive.rs
+++ b/examples/escaped-positional-derive.rs
@@ -5,7 +5,7 @@ use clap::Parser;
 #[derive(Parser)]
 #[clap(author, version, about, long_about = None)]
 struct Cli {
-    #[clap(short = 'f')]
+    #[clap(short = 'f', action)]
     eff: bool,
 
     #[clap(short = 'p', value_name = "PEAR", value_parser)]
diff --git a/examples/tutorial_derive/01_quick.rs b/examples/tutorial_derive/01_quick.rs
index b78bf782..bc8d6a9f 100644
--- a/examples/tutorial_derive/01_quick.rs
+++ b/examples/tutorial_derive/01_quick.rs
@@ -14,8 +14,8 @@ struct Cli {
     config: Option<PathBuf>,
 
     /// Turn debugging information on
-    #[clap(short, long, parse(from_occurrences))]
-    debug: usize,
+    #[clap(short, long, action = clap::ArgAction::Count)]
+    debug: u64,
 
     #[clap(subcommand)]
     command: Option<Commands>,
@@ -26,7 +26,7 @@ enum Commands {
     /// does testing things
     Test {
         /// lists test values
-        #[clap(short, long)]
+        #[clap(short, long, action)]
         list: bool,
     },
 }
diff --git a/examples/tutorial_derive/03_01_flag_bool.rs b/examples/tutorial_derive/03_01_flag_bool.rs
index 8b574b74..de677d8c 100644
--- a/examples/tutorial_derive/03_01_flag_bool.rs
+++ b/examples/tutorial_derive/03_01_flag_bool.rs
@@ -3,7 +3,7 @@ use clap::Parser;
 #[derive(Parser)]
 #[clap(author, version, about, long_about = None)]
 struct Cli {
-    #[clap(short, long)]
+    #[clap(short, long, action)]
     verbose: bool,
 }
 
diff --git a/examples/tutorial_derive/03_01_flag_count.rs b/examples/tutorial_derive/03_01_flag_count.rs
index 2ab88397..a520c7a2 100644
--- a/examples/tutorial_derive/03_01_flag_count.rs
+++ b/examples/tutorial_derive/03_01_flag_count.rs
@@ -3,8 +3,8 @@ use clap::Parser;
 #[derive(Parser)]
 #[clap(author, version, about, long_about = None)]
 struct Cli {
-    #[clap(short, long, parse(from_occurrences))]
-    verbose: usize,
+    #[clap(short, long, action = clap::ArgAction::Count)]
+    verbose: u64,
 }
 
 fn main() {
diff --git a/examples/tutorial_derive/04_03_relations.rs b/examples/tutorial_derive/04_03_relations.rs
index 32b85805..f8523902 100644
--- a/examples/tutorial_derive/04_03_relations.rs
+++ b/examples/tutorial_derive/04_03_relations.rs
@@ -13,15 +13,15 @@ struct Cli {
     set_ver: Option<String>,
 
     /// auto inc major
-    #[clap(long)]
+    #[clap(long, action)]
     major: bool,
 
     /// auto inc minor
-    #[clap(long)]
+    #[clap(long, action)]
     minor: bool,
 
     /// auto inc patch
-    #[clap(long)]
+    #[clap(long, action)]
     patch: bool,
 
     /// some regular input
diff --git a/examples/tutorial_derive/04_04_custom.rs b/examples/tutorial_derive/04_04_custom.rs
index d163e20e..454d489c 100644
--- a/examples/tutorial_derive/04_04_custom.rs
+++ b/examples/tutorial_derive/04_04_custom.rs
@@ -8,15 +8,15 @@ struct Cli {
     set_ver: Option<String>,
 
     /// auto inc major
-    #[clap(long)]
+    #[clap(long, action)]
     major: bool,
 
     /// auto inc minor
-    #[clap(long)]
+    #[clap(long, action)]
     minor: bool,
 
     /// auto inc patch
-    #[clap(long)]
+    #[clap(long, action)]
     patch: bool,
 
     /// some regular input
diff --git a/examples/tutorial_derive/README.md b/examples/tutorial_derive/README.md
index 93d35454..7e42a8ad 100644
--- a/examples/tutorial_derive/README.md
+++ b/examples/tutorial_derive/README.md
@@ -236,17 +236,11 @@ $ 03_01_flag_bool_derive --verbose
 verbose: true
 
 $ 03_01_flag_bool_derive --verbose --verbose
-? failed
-error: The argument '--verbose' was provided more than once, but cannot be used multiple times
-
-USAGE:
-    03_01_flag_bool_derive[EXE] [OPTIONS]
-
-For more information try --help
+verbose: true
 
 ```
 
-Or counted with `#[clap(parse(from_occurrences))]`:
+Or counted with `#[clap(action = clap::ArgAction::Count)]`:
 
 [Example:](03_01_flag_count.rs)
 ```console
diff --git a/src/builder/action.rs b/src/builder/action.rs
index acfe2027..3e4404c7 100644
--- a/src/builder/action.rs
+++ b/src/builder/action.rs
@@ -276,6 +276,24 @@ pub enum ArgAction {
 }
 
 impl ArgAction {
+    /// Returns whether this action accepts values on the command-line
+    ///
+    /// [`default_values`][super::Arg::default_values] and [`env`][super::Arg::env] may still be
+    /// processed.
+    pub fn takes_values(&self) -> bool {
+        match self {
+            Self::Set => true,
+            Self::Append => true,
+            Self::StoreValue => true,
+            Self::IncOccurrence => false,
+            Self::SetTrue => false,
+            Self::SetFalse => false,
+            Self::Count => false,
+            Self::Help => false,
+            Self::Version => false,
+        }
+    }
+
     pub(crate) fn default_value(&self) -> Option<&'static std::ffi::OsStr> {
         match self {
             Self::Set => None,
@@ -290,20 +308,6 @@ impl ArgAction {
         }
     }
 
-    pub(crate) fn takes_value(&self) -> bool {
-        match self {
-            Self::Set => true,
-            Self::Append => true,
-            Self::StoreValue => true,
-            Self::IncOccurrence => false,
-            Self::SetTrue => false,
-            Self::SetFalse => false,
-            Self::Count => false,
-            Self::Help => false,
-            Self::Version => false,
-        }
-    }
-
     pub(crate) fn default_value_parser(&self) -> Option<super::ValueParser> {
         match self {
             Self::Set => None,
diff --git a/src/builder/arg.rs b/src/builder/arg.rs
index 6ac18271..048fe2af 100644
--- a/src/builder/arg.rs
+++ b/src/builder/arg.rs
@@ -4897,7 +4897,7 @@ impl<'help> Arg<'help> {
                     self.default_vals = vec![default_value];
                 }
             }
-            if action.takes_value() {
+            if action.takes_values() {
                 self.settings.set(ArgSettings::TakesValue);
             } else {
                 self.settings.unset(ArgSettings::TakesValue);
diff --git a/src/builder/debug_asserts.rs b/src/builder/debug_asserts.rs
index fd691c46..78db63c5 100644
--- a/src/builder/debug_asserts.rs
+++ b/src/builder/debug_asserts.rs
@@ -643,7 +643,7 @@ fn assert_arg(arg: &Arg) {
     );
 
     assert_eq!(
-        arg.get_action().takes_value(),
+        arg.get_action().takes_values(),
         arg.is_takes_value_set(),
         "Argument `{}`'s selected action {:?} contradicts `takes_value`",
         arg.name,


Test patch:
diff --git a/tests/derive/doc_comments_help.rs b/tests/derive/doc_comments_help.rs
index 697672ce..5ff6290f 100644
--- a/tests/derive/doc_comments_help.rs
+++ b/tests/derive/doc_comments_help.rs
@@ -23,7 +23,7 @@ fn doc_comments() {
     struct LoremIpsum {
         /// Fooify a bar
         /// and a baz
-        #[clap(short, long)]
+        #[clap(short, long, action)]
         foo: bool,
     }
 
@@ -39,7 +39,12 @@ fn help_is_better_than_comments() {
     #[clap(name = "lorem-ipsum", about = "Dolor sit amet")]
     struct LoremIpsum {
         /// Fooify a bar
-        #[clap(short, long, help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")]
+        #[clap(
+            short,
+            long,
+            help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES",
+            action
+        )]
         foo: bool,
     }
 
@@ -71,11 +76,11 @@ fn field_long_doc_comment_both_help_long_help() {
         /// Dot is removed from multiline comments.
         ///
         /// Long help
-        #[clap(long)]
+        #[clap(long, action)]
         foo: bool,
 
         /// Dot is removed from one short comment.
-        #[clap(long)]
+        #[clap(long, action)]
         bar: bool,
     }
 
@@ -141,7 +146,7 @@ fn verbatim_doc_comment() {
     #[derive(Parser, Debug)]
     #[clap(verbatim_doc_comment)]
     struct SeeFigure1 {
-        #[clap(long)]
+        #[clap(long, action)]
         foo: bool,
     }
 
@@ -171,10 +176,10 @@ fn verbatim_doc_comment_field() {
     #[derive(Parser, Debug)]
     struct Command {
         /// This help ends in a period.
-        #[clap(long, verbatim_doc_comment)]
+        #[clap(long, verbatim_doc_comment, action)]
         foo: bool,
         /// This help does not end in a period.
-        #[clap(long)]
+        #[clap(long, action)]
         bar: bool,
     }
 
diff --git a/tests/derive/flags.rs b/tests/derive/flags.rs
index cbf2ef9f..8b17dd6e 100644
--- a/tests/derive/flags.rs
+++ b/tests/derive/flags.rs
@@ -20,7 +20,7 @@ use clap::Parser;
 fn bool_type_is_flag() {
     #[derive(Parser, PartialEq, Debug)]
     struct Opt {
-        #[clap(short, long)]
+        #[clap(short, long, action)]
         alice: bool,
     }
 
@@ -32,24 +32,26 @@ fn bool_type_is_flag() {
         Opt { alice: true },
         Opt::try_parse_from(&["test", "-a"]).unwrap()
     );
+    assert_eq!(
+        Opt { alice: true },
+        Opt::try_parse_from(&["test", "-a", "-a"]).unwrap()
+    );
     assert_eq!(
         Opt { alice: true },
         Opt::try_parse_from(&["test", "--alice"]).unwrap()
     );
     assert!(Opt::try_parse_from(&["test", "-i"]).is_err());
     assert!(Opt::try_parse_from(&["test", "-a", "foo"]).is_err());
-    assert!(Opt::try_parse_from(&["test", "-a", "-a"]).is_err());
-    assert!(Opt::try_parse_from(&["test", "-a", "--alice"]).is_err());
 }
 
 #[test]
-fn from_occurrences() {
+fn count() {
     #[derive(Parser, PartialEq, Debug)]
     struct Opt {
-        #[clap(short, long, parse(from_occurrences))]
+        #[clap(short, long, action = clap::ArgAction::Count)]
         alice: u64,
-        #[clap(short, long, parse(from_occurrences))]
-        bob: u8,
+        #[clap(short, long, action = clap::ArgAction::Count)]
+        bob: u64,
     }
 
     assert_eq!(
@@ -111,9 +113,9 @@ fn non_bool_type_flag() {
 fn mixed_type_flags() {
     #[derive(Parser, PartialEq, Debug)]
     struct Opt {
-        #[clap(short, long)]
+        #[clap(short, long, action)]
         alice: bool,
-        #[clap(short, long, parse(from_occurrences))]
+        #[clap(short, long, action = clap::ArgAction::Count)]
         bob: u64,
     }
 
@@ -191,10 +193,10 @@ fn ignore_qualified_bool_type() {
 }
 
 #[test]
-fn override_implicit_from_flag() {
+fn override_implicit_action() {
     #[derive(Parser, PartialEq, Debug)]
     struct Opt {
-        #[clap(long, parse(try_from_str))]
+        #[clap(long, action = clap::ArgAction::Set)]
         arg: bool,
     }
 
@@ -213,7 +215,7 @@ fn override_implicit_from_flag() {
 fn override_implicit_from_flag_positional() {
     #[derive(Parser, PartialEq, Debug)]
     struct Opt {
-        #[clap(parse(try_from_str))]
+        #[clap(action = clap::ArgAction::Set)]
         arg: bool,
     }
 
diff --git a/tests/derive/flatten.rs b/tests/derive/flatten.rs
index 15bef4db..df451265 100644
--- a/tests/derive/flatten.rs
+++ b/tests/derive/flatten.rs
@@ -70,7 +70,7 @@ fn flatten_in_subcommand() {
 
     #[derive(Args, PartialEq, Debug)]
     struct Add {
-        #[clap(short)]
+        #[clap(short, action)]
         interactive: bool,
         #[clap(flatten)]
         common: Common,
@@ -79,7 +79,7 @@ fn flatten_in_subcommand() {
     #[derive(Parser, PartialEq, Debug)]
     enum Opt {
         Fetch {
-            #[clap(short)]
+            #[clap(short, action)]
             all: bool,
             #[clap(flatten)]
             common: Common,
@@ -227,7 +227,7 @@ fn docstrings_ordering_with_multiple_clap() {
     /// This is the docstring for Flattened
     #[derive(Args)]
     struct Flattened {
-        #[clap(long)]
+        #[clap(long, action)]
         foo: bool,
     }
 
@@ -248,7 +248,7 @@ fn docstrings_ordering_with_multiple_clap_partial() {
     /// This is the docstring for Flattened
     #[derive(Args)]
     struct Flattened {
-        #[clap(long)]
+        #[clap(long, action)]
         foo: bool,
     }
 
diff --git a/tests/derive/help.rs b/tests/derive/help.rs
index 4254dda5..8e9f7d50 100644
--- a/tests/derive/help.rs
+++ b/tests/derive/help.rs
@@ -274,8 +274,8 @@ fn derive_generated_error_has_full_context() {
     #[derive(Debug, Parser)]
     enum SubCommands {
         Sub {
-            #[clap(short, long, parse(from_occurrences))]
-            verbose: u8,
+            #[clap(short, long, action = clap::ArgAction::Count)]
+            verbose: u64,
         },
     }
 
@@ -339,7 +339,7 @@ OPTIONS:
     #[clap(next_display_order = 10000)]
     struct A {
         /// second flag
-        #[clap(long)]
+        #[clap(long, action)]
         flag_a: bool,
         /// second option
         #[clap(long, value_parser)]
@@ -350,7 +350,7 @@ OPTIONS:
     #[clap(next_display_order = 10)]
     struct B {
         /// first flag
-        #[clap(long)]
+        #[clap(long, action)]
         flag_b: bool,
         /// first option
         #[clap(long, value_parser)]
@@ -397,7 +397,7 @@ OPTIONS:
     #[derive(Args, Debug)]
     struct A {
         /// second flag
-        #[clap(long)]
+        #[clap(long, action)]
         flag_a: bool,
         /// second option
         #[clap(long, value_parser)]
@@ -407,7 +407,7 @@ OPTIONS:
     #[derive(Args, Debug)]
     struct B {
         /// first flag
-        #[clap(long)]
+        #[clap(long, action)]
         flag_b: bool,
         /// first option
         #[clap(long, value_parser)]
@@ -453,7 +453,7 @@ OPTIONS:
     #[derive(Args, Debug)]
     struct A {
         /// first flag
-        #[clap(long)]
+        #[clap(long, action)]
         flag_a: bool,
         /// first option
         #[clap(long, value_parser)]
@@ -463,7 +463,7 @@ OPTIONS:
     #[derive(Args, Debug)]
     struct B {
         /// second flag
-        #[clap(long)]
+        #[clap(long, action)]
         flag_b: bool,
         /// second option
         #[clap(long, value_parser)]
diff --git a/tests/derive/issues.rs b/tests/derive/issues.rs
index ee3ff1ba..22c93c87 100644
--- a/tests/derive/issues.rs
+++ b/tests/derive/issues.rs
@@ -5,14 +5,14 @@ use crate::utils;
 use clap::{ArgGroup, Args, Parser, Subcommand};
 
 #[test]
-fn issue_151() {
+fn issue_151_groups_within_subcommands() {
     #[derive(Args, Debug)]
     #[clap(group = ArgGroup::new("verb").required(true).multiple(true))]
     struct Opt {
-        #[clap(long, group = "verb")]
-        foo: bool,
-        #[clap(long, group = "verb")]
-        bar: bool,
+        #[clap(long, group = "verb", value_parser)]
+        foo: Option<String>,
+        #[clap(long, group = "verb", value_parser)]
+        bar: Option<String>,
     }
 
     #[derive(Debug, Parser)]
@@ -22,10 +22,10 @@ fn issue_151() {
     }
 
     assert!(Cli::try_parse_from(&["test"]).is_err());
-    assert!(Cli::try_parse_from(&["test", "--foo"]).is_ok());
-    assert!(Cli::try_parse_from(&["test", "--bar"]).is_ok());
-    assert!(Cli::try_parse_from(&["test", "--zebra"]).is_err());
-    assert!(Cli::try_parse_from(&["test", "--foo", "--bar"]).is_ok());
+    assert!(Cli::try_parse_from(&["test", "--foo=v1"]).is_ok());
+    assert!(Cli::try_parse_from(&["test", "--bar=v2"]).is_ok());
+    assert!(Cli::try_parse_from(&["test", "--zebra=v3"]).is_err());
+    assert!(Cli::try_parse_from(&["test", "--foo=v1", "--bar=v2"]).is_ok());
 }
 
 #[test]
diff --git a/tests/derive/naming.rs b/tests/derive/naming.rs
index b7889921..e39cf2c8 100644
--- a/tests/derive/naming.rs
+++ b/tests/derive/naming.rs
@@ -5,7 +5,7 @@ fn test_standalone_long_generates_kebab_case() {
     #[derive(Parser, Debug, PartialEq)]
     #[allow(non_snake_case)]
     struct Opt {
-        #[clap(long)]
+        #[clap(long, action)]
         FOO_OPTION: bool,
     }
 
@@ -19,7 +19,7 @@ fn test_standalone_long_generates_kebab_case() {
 fn test_custom_long_overwrites_default_name() {
     #[derive(Parser, Debug, PartialEq)]
     struct Opt {
-        #[clap(long = "foo")]
+        #[clap(long = "foo", action)]
         foo_option: bool,
     }
 
diff --git a/tests/derive/nested_subcommands.rs b/tests/derive/nested_subcommands.rs
index 98fec967..7fe74ab6 100644
--- a/tests/derive/nested_subcommands.rs
+++ b/tests/derive/nested_subcommands.rs
@@ -16,9 +16,9 @@ use clap::{Parser, Subcommand};
 
 #[derive(Parser, PartialEq, Debug)]
 struct Opt {
-    #[clap(short, long)]
+    #[clap(short, long, action)]
     force: bool,
-    #[clap(short, long, parse(from_occurrences))]
+    #[clap(short, long, action = clap::ArgAction::Count)]
     verbose: u64,
     #[clap(subcommand)]
     cmd: Sub,
@@ -32,9 +32,9 @@ enum Sub {
 
 #[derive(Parser, PartialEq, Debug)]
 struct Opt2 {
-    #[clap(short, long)]
+    #[clap(short, long, action)]
     force: bool,
-    #[clap(short, long, parse(from_occurrences))]
+    #[clap(short, long, action = clap::ArgAction::Count)]
     verbose: u64,
     #[clap(subcommand)]
     cmd: Option<Sub>,
@@ -109,7 +109,7 @@ fn test_badinput() {
 
 #[derive(Parser, PartialEq, Debug)]
 struct Opt3 {
-    #[clap(short, long)]
+    #[clap(short, long, action)]
     all: bool,
     #[clap(subcommand)]
     cmd: Sub2,
diff --git a/tests/derive/options.rs b/tests/derive/options.rs
index fb23e245..8c774bc0 100644
--- a/tests/derive/options.rs
+++ b/tests/derive/options.rs
@@ -37,8 +37,11 @@ fn required_option() {
         Opt { arg: 42 },
         Opt::try_parse_from(&["test", "--arg", "42"]).unwrap()
     );
+    assert_eq!(
+        Opt { arg: 42 },
+        Opt::try_parse_from(&["test", "--arg", "24", "--arg", "42"]).unwrap()
+    );
     assert!(Opt::try_parse_from(&["test"]).is_err());
-    assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
 }
 
 #[test]
@@ -52,8 +55,11 @@ fn option_with_default() {
         Opt { arg: 24 },
         Opt::try_parse_from(&["test", "-a24"]).unwrap()
     );
+    assert_eq!(
+        Opt { arg: 42 },
+        Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
+    );
     assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap());
-    assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
 }
 
 #[test]
@@ -67,8 +73,11 @@ fn option_with_raw_default() {
         Opt { arg: 24 },
         Opt::try_parse_from(&["test", "-a24"]).unwrap()
     );
+    assert_eq!(
+        Opt { arg: 42 },
+        Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
+    );
     assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(&["test"]).unwrap());
-    assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
 }
 
 #[test]
@@ -157,8 +166,11 @@ fn option_type_is_optional() {
         Opt { arg: Some(42) },
         Opt::try_parse_from(&["test", "-a42"]).unwrap()
     );
+    assert_eq!(
+        Opt { arg: Some(42) },
+        Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
+    );
     assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
-    assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
 }
 
 #[test]
@@ -176,8 +188,8 @@ fn required_with_option_type() {
     #[derive(Debug, PartialEq, Eq, Subcommand)]
     enum SubCommands {
         ExSub {
-            #[clap(short, long, parse(from_occurrences))]
-            verbose: u8,
+            #[clap(short, long, action = clap::ArgAction::Count)]
+            verbose: u64,
         },
     }
 
@@ -238,8 +250,13 @@ fn option_option_type_is_optional_value() {
         Opt { arg: Some(None) },
         Opt::try_parse_from(&["test", "-a"]).unwrap()
     );
+    assert_eq!(
+        Opt {
+            arg: Some(Some(42))
+        },
+        Opt::try_parse_from(&["test", "-a", "24", "-a", "42"]).unwrap()
+    );
     assert_eq!(Opt { arg: None }, Opt::try_parse_from(&["test"]).unwrap());
-    assert!(Opt::try_parse_from(&["test", "-a42", "-a24"]).is_err());
 }
 
 #[test]
diff --git a/tests/derive/subcommands.rs b/tests/derive/subcommands.rs
index 4f3ea1df..2dcb8976 100644
--- a/tests/derive/subcommands.rs
+++ b/tests/derive/subcommands.rs
@@ -20,19 +20,19 @@ use clap::{Args, Parser, Subcommand};
 enum Opt {
     /// Fetch stuff from GitHub
     Fetch {
-        #[clap(long)]
+        #[clap(long, action)]
         all: bool,
-        #[clap(short, long)]
         /// Overwrite local branches.
+        #[clap(short, long, action)]
         force: bool,
         #[clap(value_parser)]
         repo: String,
     },
 
     Add {
-        #[clap(short, long)]
+        #[clap(short, long, action)]
         interactive: bool,
-        #[clap(short, long)]
+        #[clap(short, long, action)]
         verbose: bool,
     },
 }
@@ -173,7 +173,7 @@ fn test_tuple_commands() {
 fn global_passed_down() {
     #[derive(Debug, PartialEq, Parser)]
     struct Opt {
-        #[clap(global = true, long)]
+        #[clap(global = true, long, action)]
         other: bool,
         #[clap(subcommand)]
         sub: Subcommands,
@@ -187,7 +187,7 @@ fn global_passed_down() {
 
     #[derive(Debug, PartialEq, Args)]
     struct GlobalCmd {
-        #[clap(from_global)]
+        #[clap(from_global, action)]
         other: bool,
     }