Snap for 10447354 from 611b7f5d2daf4a2974c8f34adfa89df292244042 to mainline-wifi-release

Change-Id: If133913002fef83f103cdd51df1ed40c86546f43
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 1b6d0b1..9595563 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
 {
   "git": {
-    "sha1": "f1f85d2d89cbe09314dc1b59e581b8a43531cf3e"
+    "sha1": "3f3c29726a21c4b541bb2b9aa2c592461897ded0"
   },
   "path_in_vcs": "argh_derive"
 }
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 1d3fdfe..a31ff09 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,8 +1,6 @@
-// This file is generated by cargo2android.py --run --device --tests.
+// This file is generated by cargo2android.py --config cargo2android.json.
 // Do not modify this file as changes will be overridden on upgrade.
 
-
-
 package {
     default_applicable_licenses: ["external_rust_crates_argh_derive_license"],
 }
@@ -24,14 +22,15 @@
     name: "libargh_derive",
     crate_name: "argh_derive",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.1.7",
+    cargo_pkg_version: "0.1.10",
     srcs: ["src/lib.rs"],
     edition: "2018",
     rustlibs: [
         "libargh_shared",
-        "libheck",
         "libproc_macro2",
         "libquote",
         "libsyn",
     ],
+    product_available: true,
+    vendor_available: true,
 }
diff --git a/Cargo.toml b/Cargo.toml
index 82f7d44..b7ddfba 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,8 +12,12 @@
 [package]
 edition = "2018"
 name = "argh_derive"
-version = "0.1.7"
-authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"]
+version = "0.1.10"
+authors = [
+    "Taylor Cramer <cramertj@google.com>",
+    "Benjamin Brittain <bwb@google.com>",
+    "Erick Tryzelaar <etryzelaar@google.com>",
+]
 description = "Derive-based argument parsing optimized for code size"
 readme = "README.md"
 license = "BSD-3-Clause"
@@ -21,11 +25,9 @@
 
 [lib]
 proc-macro = true
-[dependencies.argh_shared]
-version = "0.1.7"
 
-[dependencies.heck]
-version = "0.3.1"
+[dependencies.argh_shared]
+version = "0.1.10"
 
 [dependencies.proc-macro2]
 version = "1.0"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index b74329d..750b494 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
 [package]
 name = "argh_derive"
-version = "0.1.7"
+version = "0.1.10"
 authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"]
 edition = "2018"
 license = "BSD-3-Clause"
@@ -12,8 +12,7 @@
 proc-macro = true
 
 [dependencies]
-heck = "0.3.1"
 proc-macro2 = "1.0"
 quote = "1.0"
 syn = "1.0"
-argh_shared = { version = "0.1.7", path = "../argh_shared" }
+argh_shared = { version = "0.1.10", path = "../argh_shared" }
diff --git a/METADATA b/METADATA
index fb0a64d..c7f6717 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,7 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update rust/crates/argh_derive
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
 name: "argh_derive"
 description: "Derive-based argument parsing optimized for code size"
 third_party {
@@ -7,13 +11,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://static.crates.io/crates/argh_derive/argh_derive-0.1.7.crate"
+    value: "https://static.crates.io/crates/argh_derive/argh_derive-0.1.10.crate"
   }
-  version: "0.1.7"
+  version: "0.1.10"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2022
-    month: 1
-    day: 13
+    year: 2023
+    month: 2
+    day: 1
   }
 }
diff --git a/README.md b/README.md
index 4e949e4..7368162 100644
--- a/README.md
+++ b/README.md
@@ -175,3 +175,16 @@
 ```
 
 NOTE: This is not an officially supported Google product.
+
+
+## How to debug the expanded derive macro for `argh`
+
+The `argh::FromArgs` derive macro can be debugged with the [cargo-expand](https://crates.io/crates/cargo-expand) crate.
+
+### Expand the derive macro in `examples/simple_example.rs`
+
+See [argh/examples/simple_example.rs](./argh/examples/simple_example.rs) for the example struct we wish to expand.
+
+First, install `cargo-expand` by running `cargo install cargo-expand`. Note this requires the nightly build of Rust.
+
+Once installed, run `cargo expand` with in the `argh` package and you can see the expanded code.
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 5cbb502..9f55024 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -3,6 +3,9 @@
   "imports": [
     {
       "path": "external/rust/crates/argh"
+    },
+    {
+      "path": "packages/modules/Virtualization/virtualizationmanager"
     }
   ]
 }
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..fe43c3f
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,4 @@
+{
+  "run": true,
+  "device": true
+}
diff --git a/src/errors.rs b/src/errors.rs
index a5b69e6..cfad383 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -146,6 +146,11 @@
         self.push(syn::Error::new(span, msg));
     }
 
+    /// Issue an error spanning over the given syntax tree node.
+    pub fn err_span_tokens<T: ToTokens>(&self, tokens: T, msg: &str) {
+        self.push(syn::Error::new_spanned(tokens, msg));
+    }
+
     /// Push a `syn::Error` onto the list of errors to issue.
     pub fn push(&self, err: syn::Error) {
         self.errors.borrow_mut().push(err);
diff --git a/src/help.rs b/src/help.rs
index 5bf02b1..bd20622 100644
--- a/src/help.rs
+++ b/src/help.rs
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+use std::fmt::Write;
 use {
     crate::{
         errors::Errors,
@@ -28,7 +29,9 @@
 ) -> TokenStream {
     let mut format_lit = "Usage: {command_name}".to_string();
 
-    let positional = fields.iter().filter(|f| f.kind == FieldKind::Positional);
+    let positional = fields.iter().filter(|f| {
+        f.kind == FieldKind::Positional && f.attrs.greedy.is_none() && !f.attrs.hidden_help
+    });
     let mut has_positional = false;
     for arg in positional.clone() {
         has_positional = true;
@@ -36,12 +39,20 @@
         positional_usage(&mut format_lit, arg);
     }
 
-    let options = fields.iter().filter(|f| f.long_name.is_some());
+    let options = fields.iter().filter(|f| f.long_name.is_some() && !f.attrs.hidden_help);
     for option in options.clone() {
         format_lit.push(' ');
         option_usage(&mut format_lit, option);
     }
 
+    let remain = fields.iter().filter(|f| {
+        f.kind == FieldKind::Positional && f.attrs.greedy.is_some() && !f.attrs.hidden_help
+    });
+    for arg in remain {
+        format_lit.push(' ');
+        positional_usage(&mut format_lit, arg);
+    }
+
     if let Some(subcommand) = subcommand {
         format_lit.push(' ');
         if !subcommand.optionality.is_required() {
@@ -85,6 +96,12 @@
         subcommand_calculation = quote! {
             let subcommands = argh::print_subcommands(
                 <#subcommand_ty as argh::SubCommands>::COMMANDS
+                    .iter()
+                    .copied()
+                    .chain(
+                        <#subcommand_ty as argh::SubCommands>::dynamic_commands()
+                            .iter()
+                            .copied())
             );
         };
     } else {
@@ -96,17 +113,17 @@
 
     lits_section(&mut format_lit, "Notes:", &ty_attrs.notes);
 
-    if ty_attrs.error_codes.len() != 0 {
+    if !ty_attrs.error_codes.is_empty() {
         format_lit.push_str(SECTION_SEPARATOR);
         format_lit.push_str("Error codes:");
         for (code, text) in &ty_attrs.error_codes {
             format_lit.push('\n');
             format_lit.push_str(INDENT);
-            format_lit.push_str(&format!("{} {}", code, text.value()));
+            write!(format_lit, "{} {}", code, text.value()).unwrap();
         }
     }
 
-    format_lit.push_str("\n");
+    format_lit.push('\n');
 
     quote! { {
         #subcommand_calculation
@@ -116,7 +133,7 @@
 
 /// A section composed of exactly just the literals provided to the program.
 fn lits_section(out: &mut String, heading: &str, lits: &[syn::LitStr]) {
-    if lits.len() != 0 {
+    if !lits.is_empty() {
         out.push_str(SECTION_SEPARATOR);
         out.push_str(heading);
         for lit in lits {
@@ -135,13 +152,17 @@
     if !field.optionality.is_required() {
         out.push('[');
     }
-    out.push('<');
-    let name = field.arg_name();
+    if field.attrs.greedy.is_none() {
+        out.push('<');
+    }
+    let name = field.positional_arg_name();
     out.push_str(&name);
     if field.optionality == Optionality::Repeating {
         out.push_str("...");
     }
-    out.push('>');
+    if field.attrs.greedy.is_none() {
+        out.push('>');
+    }
     if !field.optionality.is_required() {
         out.push(']');
     }
@@ -209,7 +230,7 @@
 /// Describes a positional argument like this:
 ///  hello       positional argument description
 fn positional_description(out: &mut String, field: &StructField<'_>) {
-    let field_name = field.arg_name();
+    let field_name = field.positional_arg_name();
 
     let mut description = String::from("");
     if let Some(desc) = &field.attrs.description {
@@ -219,7 +240,7 @@
 }
 
 fn positional_description_format(out: &mut String, name: &str, description: &str) {
-    let info = argh_shared::CommandInfo { name: &*name, description };
+    let info = argh_shared::CommandInfo { name, description };
     argh_shared::write_description(out, &info);
 }
 
@@ -249,6 +270,6 @@
     }
     name.push_str(long_with_leading_dashes);
 
-    let info = argh_shared::CommandInfo { name: &*name, description };
+    let info = argh_shared::CommandInfo { name: &name, description };
     argh_shared::write_description(out, &info);
 }
diff --git a/src/lib.rs b/src/lib.rs
index a123d4e..94cc437 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,12 +11,12 @@
 use {
     crate::{
         errors::Errors,
-        parse_attrs::{FieldAttrs, FieldKind, TypeAttrs},
+        parse_attrs::{check_long_name, FieldAttrs, FieldKind, TypeAttrs},
     },
     proc_macro2::{Span, TokenStream},
     quote::{quote, quote_spanned, ToTokens},
-    std::str::FromStr,
-    syn::{spanned::Spanned, LitStr},
+    std::{collections::HashMap, str::FromStr},
+    syn::{spanned::Spanned, GenericArgument, LitStr, PathArguments, Type},
 };
 
 mod errors;
@@ -35,16 +35,14 @@
 /// as well as all errors that occurred.
 fn impl_from_args(input: &syn::DeriveInput) -> TokenStream {
     let errors = &Errors::default();
-    if input.generics.params.len() != 0 {
-        errors.err(
-            &input.generics,
-            "`#![derive(FromArgs)]` cannot be applied to types with generic parameters",
-        );
-    }
     let type_attrs = &TypeAttrs::parse(errors, input);
     let mut output_tokens = match &input.data {
-        syn::Data::Struct(ds) => impl_from_args_struct(errors, &input.ident, type_attrs, ds),
-        syn::Data::Enum(de) => impl_from_args_enum(errors, &input.ident, type_attrs, de),
+        syn::Data::Struct(ds) => {
+            impl_from_args_struct(errors, &input.ident, type_attrs, &input.generics, ds)
+        }
+        syn::Data::Enum(de) => {
+            impl_from_args_enum(errors, &input.ident, type_attrs, &input.generics, de)
+        }
         syn::Data::Union(_) => {
             errors.err(input, "`#[derive(FromArgs)]` cannot be applied to unions");
             TokenStream::new()
@@ -65,22 +63,15 @@
 impl PartialEq<Optionality> for Optionality {
     fn eq(&self, other: &Optionality) -> bool {
         use Optionality::*;
-        match (self, other) {
-            (None, None) | (Optional, Optional) | (Repeating, Repeating) => true,
-            // NB: (Defaulted, Defaulted) can't contain the same token streams
-            _ => false,
-        }
+        // NB: (Defaulted, Defaulted) can't contain the same token streams
+        matches!((self, other), (Optional, Optional) | (Repeating, Repeating))
     }
 }
 
 impl Optionality {
     /// Whether or not this is `Optionality::None`
     fn is_required(&self) -> bool {
-        if let Optionality::None = self {
-            true
-        } else {
-            false
-        }
+        matches!(self, Optionality::None)
     }
 }
 
@@ -125,7 +116,7 @@
                 field,
                 concat!(
                     "Missing `argh` field kind attribute.\n",
-                    "Expected one of: `switch`, `option`, `subcommand`, `positional`",
+                    "Expected one of: `switch`, `option`, `remaining`, `subcommand`, `positional`",
                 ),
             );
             return None;
@@ -187,11 +178,11 @@
         // Defaults to the kebab-case'd field name if `#[argh(long = "...")]` is omitted.
         let long_name = match kind {
             FieldKind::Switch | FieldKind::Option => {
-                let long_name = attrs
-                    .long
-                    .as_ref()
-                    .map(syn::LitStr::value)
-                    .unwrap_or_else(|| heck::KebabCase::to_kebab_case(&*name.to_string()));
+                let long_name = attrs.long.as_ref().map(syn::LitStr::value).unwrap_or_else(|| {
+                    let kebab_name = to_kebab_case(&name.to_string());
+                    check_long_name(errors, name, &kebab_name);
+                    kebab_name
+                });
                 if long_name == "help" {
                     errors.err(field, "Custom `--help` flags are not supported.");
                 }
@@ -204,16 +195,49 @@
         Some(StructField { field, attrs, kind, optionality, ty_without_wrapper, name, long_name })
     }
 
-    pub(crate) fn arg_name(&self) -> String {
-        self.attrs.arg_name.as_ref().map(LitStr::value).unwrap_or_else(|| self.name.to_string())
+    pub(crate) fn positional_arg_name(&self) -> String {
+        self.attrs
+            .arg_name
+            .as_ref()
+            .map(LitStr::value)
+            .unwrap_or_else(|| self.name.to_string().trim_matches('_').to_owned())
     }
 }
 
+fn to_kebab_case(s: &str) -> String {
+    let words = s.split('_').filter(|word| !word.is_empty());
+    let mut res = String::with_capacity(s.len());
+    for word in words {
+        if !res.is_empty() {
+            res.push('-')
+        }
+        res.push_str(word)
+    }
+    res
+}
+
+#[test]
+fn test_kebabs() {
+    #[track_caller]
+    fn check(s: &str, want: &str) {
+        let got = to_kebab_case(s);
+        assert_eq!(got.as_str(), want)
+    }
+    check("", "");
+    check("_", "");
+    check("foo", "foo");
+    check("__foo_", "foo");
+    check("foo_bar", "foo-bar");
+    check("foo__Bar", "foo-Bar");
+    check("foo_bar__baz_", "foo-bar-baz");
+}
+
 /// Implements `FromArgs` and `TopLevelCommand` or `SubCommand` for a `#[derive(FromArgs)]` struct.
 fn impl_from_args_struct(
     errors: &Errors,
     name: &syn::Ident,
     type_attrs: &TypeAttrs,
+    generic_args: &syn::Generics,
     ds: &syn::DataStruct,
 ) -> TokenStream {
     let fields = match &ds.fields {
@@ -240,6 +264,7 @@
         })
         .collect();
 
+    ensure_unique_names(errors, &fields);
     ensure_only_last_positional_is_optional(errors, &fields);
 
     let impl_span = Span::call_site();
@@ -249,10 +274,12 @@
     let redact_arg_values_method =
         impl_from_args_struct_redact_arg_values(errors, type_attrs, &fields);
 
-    let top_or_sub_cmd_impl = top_or_sub_cmd_impl(errors, name, type_attrs);
+    let top_or_sub_cmd_impl = top_or_sub_cmd_impl(errors, name, type_attrs, generic_args);
 
+    let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
     let trait_impl = quote_spanned! { impl_span =>
-        impl argh::FromArgs for #name {
+        #[automatically_derived]
+        impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {
             #from_args_method
 
             #redact_arg_values_method
@@ -269,8 +296,8 @@
     type_attrs: &TypeAttrs,
     fields: &'a [StructField<'a>],
 ) -> TokenStream {
-    let init_fields = declare_local_storage_for_from_args_fields(&fields);
-    let unwrap_fields = unwrap_from_args_fields(&fields);
+    let init_fields = declare_local_storage_for_from_args_fields(fields);
+    let unwrap_fields = unwrap_from_args_fields(fields);
     let positional_fields: Vec<&StructField<'_>> =
         fields.iter().filter(|field| field.kind == FieldKind::Positional).collect();
     let positional_field_idents = positional_fields.iter().map(|field| &field.field.ident);
@@ -279,6 +306,10 @@
         .last()
         .map(|field| field.optionality == Optionality::Repeating)
         .unwrap_or(false);
+    let last_positional_is_greedy = positional_fields
+        .last()
+        .map(|field| field.kind == FieldKind::Positional && field.attrs.greedy.is_some())
+        .unwrap_or(false);
 
     let flag_output_table = fields.iter().filter_map(|field| {
         let field_name = &field.field.ident;
@@ -289,13 +320,13 @@
         }
     });
 
-    let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(&fields);
+    let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields);
 
     let mut subcommands_iter =
         fields.iter().filter(|field| field.kind == FieldKind::SubCommand).fuse();
 
     let subcommand: Option<&StructField<'_>> = subcommands_iter.next();
-    while let Some(dup_subcommand) = subcommands_iter.next() {
+    for dup_subcommand in subcommands_iter {
         errors.duplicate_attrs("subcommand", subcommand.unwrap().field, dup_subcommand.field);
     }
 
@@ -304,7 +335,7 @@
     let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span);
 
     let append_missing_requirements =
-        append_missing_requirements(&missing_requirements_ident, &fields);
+        append_missing_requirements(&missing_requirements_ident, fields);
 
     let parse_subcommands = if let Some(subcommand) = subcommand {
         let name = subcommand.name;
@@ -312,6 +343,7 @@
         quote_spanned! { impl_span =>
             Some(argh::ParseStructSubCommand {
                 subcommands: <#ty as argh::SubCommands>::COMMANDS,
+                dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(),
                 parse_func: &mut |__command, __remaining_args| {
                     #name = Some(<#ty as argh::FromArgs>::from_args(__command, __remaining_args)?);
                     Ok(())
@@ -324,12 +356,14 @@
 
     // Identifier referring to a value containing the name of the current command as an `&[&str]`.
     let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span);
-    let help = help::help(errors, cmd_name_str_array_ident, type_attrs, &fields, subcommand);
+    let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand);
 
     let method_impl = quote_spanned! { impl_span =>
         fn from_args(__cmd_name: &[&str], __args: &[&str])
             -> std::result::Result<Self, argh::EarlyExit>
         {
+            #![allow(clippy::unwrap_in_result)]
+
             #( #init_fields )*
 
             argh::parse_struct_args(
@@ -349,6 +383,7 @@
                         )*
                     ],
                     last_is_repeating: #last_positional_is_repeating,
+                    last_is_greedy: #last_positional_is_greedy,
                 },
                 #parse_subcommands,
                 &|| #help,
@@ -374,8 +409,8 @@
     type_attrs: &TypeAttrs,
     fields: &'a [StructField<'a>],
 ) -> TokenStream {
-    let init_fields = declare_local_storage_for_redacted_fields(&fields);
-    let unwrap_fields = unwrap_redacted_fields(&fields);
+    let init_fields = declare_local_storage_for_redacted_fields(fields);
+    let unwrap_fields = unwrap_redacted_fields(fields);
 
     let positional_fields: Vec<&StructField<'_>> =
         fields.iter().filter(|field| field.kind == FieldKind::Positional).collect();
@@ -385,6 +420,10 @@
         .last()
         .map(|field| field.optionality == Optionality::Repeating)
         .unwrap_or(false);
+    let last_positional_is_greedy = positional_fields
+        .last()
+        .map(|field| field.kind == FieldKind::Positional && field.attrs.greedy.is_some())
+        .unwrap_or(false);
 
     let flag_output_table = fields.iter().filter_map(|field| {
         let field_name = &field.field.ident;
@@ -395,13 +434,13 @@
         }
     });
 
-    let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(&fields);
+    let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields);
 
     let mut subcommands_iter =
         fields.iter().filter(|field| field.kind == FieldKind::SubCommand).fuse();
 
     let subcommand: Option<&StructField<'_>> = subcommands_iter.next();
-    while let Some(dup_subcommand) = subcommands_iter.next() {
+    for dup_subcommand in subcommands_iter {
         errors.duplicate_attrs("subcommand", subcommand.unwrap().field, dup_subcommand.field);
     }
 
@@ -410,7 +449,7 @@
     let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span);
 
     let append_missing_requirements =
-        append_missing_requirements(&missing_requirements_ident, &fields);
+        append_missing_requirements(&missing_requirements_ident, fields);
 
     let redact_subcommands = if let Some(subcommand) = subcommand {
         let name = subcommand.name;
@@ -418,6 +457,7 @@
         quote_spanned! { impl_span =>
             Some(argh::ParseStructSubCommand {
                 subcommands: <#ty as argh::SubCommands>::COMMANDS,
+                dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(),
                 parse_func: &mut |__command, __remaining_args| {
                     #name = Some(<#ty as argh::FromArgs>::redact_arg_values(__command, __remaining_args)?);
                     Ok(())
@@ -428,15 +468,15 @@
         quote_spanned! { impl_span => None }
     };
 
-    let cmd_name = if type_attrs.is_subcommand.is_none() {
-        quote! { __cmd_name.last().expect("no command name").to_string() }
+    let unwrap_cmd_name_err_string = if type_attrs.is_subcommand.is_none() {
+        quote! { "no command name" }
     } else {
-        quote! { __cmd_name.last().expect("no subcommand name").to_string() }
+        quote! { "no subcommand name" }
     };
 
     // Identifier referring to a value containing the name of the current command as an `&[&str]`.
     let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span);
-    let help = help::help(errors, cmd_name_str_array_ident, type_attrs, &fields, subcommand);
+    let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand);
 
     let method_impl = quote_spanned! { impl_span =>
         fn redact_arg_values(__cmd_name: &[&str], __args: &[&str]) -> std::result::Result<Vec<String>, argh::EarlyExit> {
@@ -459,6 +499,7 @@
                         )*
                     ],
                     last_is_repeating: #last_positional_is_repeating,
+                    last_is_greedy: #last_positional_is_greedy,
                 },
                 #redact_subcommands,
                 &|| #help,
@@ -471,7 +512,11 @@
             #missing_requirements_ident.err_on_any()?;
 
             let mut __redacted = vec![
-                #cmd_name,
+                if let Some(cmd_name) = __cmd_name.last() {
+                    (*cmd_name).to_owned()
+                } else {
+                    return Err(argh::EarlyExit::from(#unwrap_cmd_name_err_string.to_owned()));
+                }
             ];
 
             #( #unwrap_fields )*
@@ -503,14 +548,54 @@
     }
 }
 
+/// Ensures that only one short or long name is used.
+fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) {
+    let mut seen_short_names = HashMap::new();
+    let mut seen_long_names = HashMap::new();
+
+    for field in fields {
+        if let Some(short_name) = &field.attrs.short {
+            let short_name = short_name.value();
+            if let Some(first_use_field) = seen_short_names.get(&short_name) {
+                errors.err_span_tokens(
+                    first_use_field,
+                    &format!("The short name of \"-{}\" was already used here.", short_name),
+                );
+                errors.err_span_tokens(field.field, "Later usage here.");
+            }
+
+            seen_short_names.insert(short_name, &field.field);
+        }
+
+        if let Some(long_name) = &field.long_name {
+            if let Some(first_use_field) = seen_long_names.get(&long_name) {
+                errors.err_span_tokens(
+                    *first_use_field,
+                    &format!("The long name of \"{}\" was already used here.", long_name),
+                );
+                errors.err_span_tokens(field.field, "Later usage here.");
+            }
+
+            seen_long_names.insert(long_name, field.field);
+        }
+    }
+}
+
 /// Implement `argh::TopLevelCommand` or `argh::SubCommand` as appropriate.
-fn top_or_sub_cmd_impl(errors: &Errors, name: &syn::Ident, type_attrs: &TypeAttrs) -> TokenStream {
+fn top_or_sub_cmd_impl(
+    errors: &Errors,
+    name: &syn::Ident,
+    type_attrs: &TypeAttrs,
+    generic_args: &syn::Generics,
+) -> TokenStream {
     let description =
         help::require_description(errors, name.span(), &type_attrs.description, "type");
+    let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
     if type_attrs.is_subcommand.is_none() {
         // Not a subcommand
         quote! {
-            impl argh::TopLevelCommand for #name {}
+            #[automatically_derived]
+            impl #impl_generics argh::TopLevelCommand for #name #ty_generics #where_clause {}
         }
     } else {
         let empty_str = syn::LitStr::new("", Span::call_site());
@@ -519,7 +604,8 @@
             &empty_str
         });
         quote! {
-            impl argh::SubCommand for #name {
+            #[automatically_derived]
+            impl #impl_generics argh::SubCommand for #name #ty_generics #where_clause {
                 const COMMAND: &'static argh::CommandInfo = &argh::CommandInfo {
                     name: #subcommand_name,
                     description: #description,
@@ -586,7 +672,9 @@
         let field_name = field.name;
         match field.kind {
             FieldKind::Option | FieldKind::Positional => match &field.optionality {
-                Optionality::None => quote! { #field_name: #field_name.slot.unwrap() },
+                Optionality::None => quote! {
+                    #field_name: #field_name.slot.unwrap()
+                },
                 Optionality::Optional | Optionality::Repeating => {
                     quote! { #field_name: #field_name.slot }
                 }
@@ -639,7 +727,7 @@
                     let mut #field_name: argh::ParseValueSlotTy::<#field_slot_type, String> =
                         argh::ParseValueSlotTy {
                         slot: std::default::Default::default(),
-                        parse_func: |arg, _| { Ok(arg.to_string()) },
+                        parse_func: |arg, _| { Ok(arg.to_owned()) },
                     };
                 }
             }
@@ -653,12 +741,12 @@
                     }
                 };
 
-                let arg_name = field.arg_name();
+                let arg_name = field.positional_arg_name();
                 quote! {
                     let mut #field_name: argh::ParseValueSlotTy::<#field_slot_type, String> =
                         argh::ParseValueSlotTy {
                         slot: std::default::Default::default(),
-                        parse_func: |_, _| { Ok(#arg_name.to_string()) },
+                        parse_func: |_, _| { Ok(#arg_name.to_owned()) },
                     };
                 }
             }
@@ -745,7 +833,7 @@
         match field.kind {
             FieldKind::Switch => unreachable!("switches are always optional"),
             FieldKind::Positional => {
-                let name = field.arg_name();
+                let name = field.positional_arg_name();
                 quote! {
                     if #field_name.slot.is_none() {
                         #mri.missing_positional_arg(#name)
@@ -765,7 +853,14 @@
                 quote! {
                     if #field_name.is_none() {
                         #mri.missing_subcommands(
-                            <#ty as argh::SubCommands>::COMMANDS,
+                            <#ty as argh::SubCommands>::COMMANDS
+                                .iter()
+                                .cloned()
+                                .chain(
+                                    <#ty as argh::SubCommands>::dynamic_commands()
+                                        .iter()
+                                        .copied()
+                                ),
                         )
                     }
                 }
@@ -786,6 +881,16 @@
                 return false;
             }
             let ident = &path.path.segments[0].ident;
+            // `Option<bool>` can be used as a `switch`.
+            if ident == "Option" {
+                if let PathArguments::AngleBracketed(args) = &path.path.segments[0].arguments {
+                    if let GenericArgument::Type(Type::Path(p)) = &args.args[0] {
+                        if p.path.segments[0].ident == "bool" {
+                            return true;
+                        }
+                    }
+                }
+            }
             ["bool", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128"]
                 .iter()
                 .any(|path| ident == path)
@@ -796,7 +901,7 @@
 
     let res = ty_can_be_switch(ty);
     if !res {
-        errors.err(ty, "switches must be of type `bool` or integer type");
+        errors.err(ty, "switches must be of type `bool`, `Option<bool>`, or integer type");
     }
     res
 }
@@ -831,6 +936,7 @@
     errors: &Errors,
     name: &syn::Ident,
     type_attrs: &TypeAttrs,
+    generic_args: &syn::Generics,
     de: &syn::DataEnum,
 ) -> TokenStream {
     parse_attrs::check_enum_type_attrs(errors, type_attrs, &de.enum_token.span);
@@ -841,27 +947,66 @@
         ty: &'a syn::Type,
     }
 
+    let mut dynamic_type_and_variant = None;
+
     let variants: Vec<SubCommandVariant<'_>> = de
         .variants
         .iter()
         .filter_map(|variant| {
-            parse_attrs::check_enum_variant_attrs(errors, variant);
             let name = &variant.ident;
             let ty = enum_only_single_field_unnamed_variants(errors, &variant.fields)?;
-            Some(SubCommandVariant { name, ty })
+            if parse_attrs::VariantAttrs::parse(errors, variant).is_dynamic.is_some() {
+                if dynamic_type_and_variant.is_some() {
+                    errors.err(variant, "Only one variant can have the `dynamic` attribute");
+                }
+                dynamic_type_and_variant = Some((ty, name));
+                None
+            } else {
+                Some(SubCommandVariant { name, ty })
+            }
         })
         .collect();
 
     let name_repeating = std::iter::repeat(name.clone());
     let variant_ty = variants.iter().map(|x| x.ty).collect::<Vec<_>>();
     let variant_names = variants.iter().map(|x| x.name).collect::<Vec<_>>();
+    let dynamic_from_args =
+        dynamic_type_and_variant.as_ref().map(|(dynamic_type, dynamic_variant)| {
+            quote! {
+                if let Some(result) = <#dynamic_type as argh::DynamicSubCommand>::try_from_args(
+                    command_name, args) {
+                    return result.map(#name::#dynamic_variant);
+                }
+            }
+        });
+    let dynamic_redact_arg_values = dynamic_type_and_variant.as_ref().map(|(dynamic_type, _)| {
+        quote! {
+            if let Some(result) = <#dynamic_type as argh::DynamicSubCommand>::try_redact_arg_values(
+                command_name, args) {
+                return result;
+            }
+        }
+    });
+    let dynamic_commands = dynamic_type_and_variant.as_ref().map(|(dynamic_type, _)| {
+        quote! {
+            fn dynamic_commands() -> &'static [&'static argh::CommandInfo] {
+                <#dynamic_type as argh::DynamicSubCommand>::commands()
+            }
+        }
+    });
 
+    let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
     quote! {
-        impl argh::FromArgs for #name {
+        impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {
             fn from_args(command_name: &[&str], args: &[&str])
                 -> std::result::Result<Self, argh::EarlyExit>
             {
-                let subcommand_name = *command_name.last().expect("no subcommand name");
+                let subcommand_name = if let Some(subcommand_name) = command_name.last() {
+                    *subcommand_name
+                } else {
+                    return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
+                };
+
                 #(
                     if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name {
                         return Ok(#name_repeating::#variant_names(
@@ -869,24 +1014,37 @@
                         ));
                     }
                 )*
-                unreachable!("no subcommand matched")
+
+                #dynamic_from_args
+
+                Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
             }
 
             fn redact_arg_values(command_name: &[&str], args: &[&str]) -> std::result::Result<Vec<String>, argh::EarlyExit> {
-                let subcommand_name = *command_name.last().expect("no subcommand name");
+                let subcommand_name = if let Some(subcommand_name) = command_name.last() {
+                    *subcommand_name
+                } else {
+                    return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
+                };
+
                 #(
                     if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name {
                         return <#variant_ty as argh::FromArgs>::redact_arg_values(command_name, args);
                     }
                 )*
-                unreachable!("no subcommand matched")
+
+                #dynamic_redact_arg_values
+
+                Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
             }
         }
 
-        impl argh::SubCommands for #name {
+        impl #impl_generics argh::SubCommands for #name #ty_generics #where_clause {
             const COMMANDS: &'static [&'static argh::CommandInfo] = &[#(
                 <#variant_ty as argh::SubCommand>::COMMAND,
             )*];
+
+            #dynamic_commands
         }
     }
 }
diff --git a/src/parse_attrs.rs b/src/parse_attrs.rs
index 83807ed..04dcbdd 100644
--- a/src/parse_attrs.rs
+++ b/src/parse_attrs.rs
@@ -18,6 +18,8 @@
     pub long: Option<syn::LitStr>,
     pub short: Option<syn::LitChar>,
     pub arg_name: Option<syn::LitStr>,
+    pub greedy: Option<syn::Path>,
+    pub hidden_help: bool,
 }
 
 /// The purpose of a particular field on a `#![derive(FromArgs)]` struct.
@@ -79,29 +81,29 @@
 
                 let name = meta.path();
                 if name.is_ident("arg_name") {
-                    if let Some(m) = errors.expect_meta_name_value(&meta) {
+                    if let Some(m) = errors.expect_meta_name_value(meta) {
                         this.parse_attr_arg_name(errors, m);
                     }
                 } else if name.is_ident("default") {
-                    if let Some(m) = errors.expect_meta_name_value(&meta) {
+                    if let Some(m) = errors.expect_meta_name_value(meta) {
                         this.parse_attr_default(errors, m);
                     }
                 } else if name.is_ident("description") {
-                    if let Some(m) = errors.expect_meta_name_value(&meta) {
+                    if let Some(m) = errors.expect_meta_name_value(meta) {
                         parse_attr_description(errors, m, &mut this.description);
                     }
                 } else if name.is_ident("from_str_fn") {
-                    if let Some(m) = errors.expect_meta_list(&meta) {
+                    if let Some(m) = errors.expect_meta_list(meta) {
                         this.parse_attr_from_str_fn(errors, m);
                     }
                 } else if name.is_ident("long") {
-                    if let Some(m) = errors.expect_meta_name_value(&meta) {
+                    if let Some(m) = errors.expect_meta_name_value(meta) {
                         this.parse_attr_long(errors, m);
                     }
                 } else if name.is_ident("option") {
                     parse_attr_field_type(errors, meta, FieldKind::Option, &mut this.field_type);
                 } else if name.is_ident("short") {
-                    if let Some(m) = errors.expect_meta_name_value(&meta) {
+                    if let Some(m) = errors.expect_meta_name_value(meta) {
                         this.parse_attr_short(errors, m);
                     }
                 } else if name.is_ident("subcommand") {
@@ -120,13 +122,17 @@
                         FieldKind::Positional,
                         &mut this.field_type,
                     );
+                } else if name.is_ident("greedy") {
+                    this.greedy = Some(name.clone());
+                } else if name.is_ident("hidden_help") {
+                    this.hidden_help = true;
                 } else {
                     errors.err(
                         &meta,
                         concat!(
                             "Invalid field-level `argh` attribute\n",
-                            "Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `long`, ",
-                            "`option`, `short`, `subcommand`, `switch`",
+                            "Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `greedy`, ",
+                            "`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`",
                         ),
                     );
                 }
@@ -144,6 +150,16 @@
             }
         }
 
+        match (&this.greedy, this.field_type.as_ref().map(|f| f.kind)) {
+            (Some(_), Some(FieldKind::Positional)) => {}
+            (Some(greedy), Some(_)) => errors.err(
+                &greedy,
+                "`greedy` may only be specified on `#[argh(positional)]` \
+                    fields",
+            ),
+            _ => {}
+        }
+
         if let Some(d) = &this.description {
             check_option_description(errors, d.content.value().trim(), d.content.span());
         }
@@ -167,12 +183,7 @@
         parse_attr_single_string(errors, m, "long", &mut self.long);
         let long = self.long.as_ref().unwrap();
         let value = long.value();
-        if !value.is_ascii() {
-            errors.err(long, "Long names must be ASCII");
-        }
-        if !value.chars().all(|c| c.is_lowercase() || c == '-') {
-            errors.err(long, "Long names must be lowercase");
-        }
+        check_long_name(errors, long, &value);
     }
 
     fn parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
@@ -187,6 +198,15 @@
     }
 }
 
+pub(crate) fn check_long_name(errors: &Errors, spanned: &impl syn::spanned::Spanned, value: &str) {
+    if !value.is_ascii() {
+        errors.err(spanned, "Long names must be ASCII");
+    }
+    if !value.chars().all(|c| c.is_lowercase() || c == '-' || c.is_ascii_digit()) {
+        errors.err(spanned, "Long names must be lowercase");
+    }
+}
+
 fn parse_attr_fn_name(
     errors: &Errors,
     m: &syn::MetaList,
@@ -218,10 +238,8 @@
     if let Some(path) = errors.expect_meta_word(meta) {
         if let Some(first) = slot {
             errors.duplicate_attrs("field kind", &first.ident, path);
-        } else {
-            if let Some(word) = path.get_ident() {
-                *slot = Some(FieldType { kind, ident: word.clone() });
-            }
+        } else if let Some(word) = path.get_ident() {
+            *slot = Some(FieldType { kind, ident: word.clone() });
         }
     }
 }
@@ -304,28 +322,27 @@
 
                 let name = meta.path();
                 if name.is_ident("description") {
-                    if let Some(m) = errors.expect_meta_name_value(&meta) {
+                    if let Some(m) = errors.expect_meta_name_value(meta) {
                         parse_attr_description(errors, m, &mut this.description);
                     }
                 } else if name.is_ident("error_code") {
-                    if let Some(m) = errors.expect_meta_list(&meta) {
+                    if let Some(m) = errors.expect_meta_list(meta) {
                         this.parse_attr_error_code(errors, m);
                     }
                 } else if name.is_ident("example") {
-                    if let Some(m) = errors.expect_meta_name_value(&meta) {
+                    if let Some(m) = errors.expect_meta_name_value(meta) {
                         this.parse_attr_example(errors, m);
                     }
                 } else if name.is_ident("name") {
-                    if let Some(m) = errors.expect_meta_name_value(&meta) {
+                    if let Some(m) = errors.expect_meta_name_value(meta) {
                         this.parse_attr_name(errors, m);
                     }
                 } else if name.is_ident("note") {
-                    if let Some(m) = errors.expect_meta_name_value(&meta) {
+                    if let Some(m) = errors.expect_meta_name_value(meta) {
                         this.parse_attr_note(errors, m);
                     }
                 } else if name.is_ident("subcommand") {
-                    if let Some(ident) = errors.expect_meta_word(&meta).and_then(|p| p.get_ident())
-                    {
+                    if let Some(ident) = errors.expect_meta_word(meta).and_then(|p| p.get_ident()) {
                         this.parse_attr_subcommand(errors, ident);
                     }
                 } else {
@@ -421,6 +438,62 @@
     }
 }
 
+/// Represents an enum variant's attributes.
+#[derive(Default)]
+pub struct VariantAttrs {
+    pub is_dynamic: Option<syn::Path>,
+}
+
+impl VariantAttrs {
+    /// Parse enum variant `#[argh(...)]` attributes
+    pub fn parse(errors: &Errors, variant: &syn::Variant) -> Self {
+        let mut this = VariantAttrs::default();
+
+        let fields = match &variant.fields {
+            syn::Fields::Named(fields) => Some(&fields.named),
+            syn::Fields::Unnamed(fields) => Some(&fields.unnamed),
+            syn::Fields::Unit => None,
+        };
+
+        for field in fields.into_iter().flatten() {
+            for attr in &field.attrs {
+                if is_argh_attr(attr) {
+                    err_unused_enum_attr(errors, attr);
+                }
+            }
+        }
+
+        for attr in &variant.attrs {
+            let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
+                ml
+            } else {
+                continue;
+            };
+
+            for meta in &ml.nested {
+                let meta = if let Some(m) = errors.expect_nested_meta(meta) { m } else { continue };
+
+                let name = meta.path();
+                if name.is_ident("dynamic") {
+                    if let Some(prev) = this.is_dynamic.as_ref() {
+                        errors.duplicate_attrs("dynamic", prev, meta);
+                    } else {
+                        this.is_dynamic = errors.expect_meta_word(meta).cloned();
+                    }
+                } else {
+                    errors.err(
+                        &meta,
+                        "Invalid variant-level `argh` attribute\n\
+                         Variants can only have the #[argh(dynamic)] attribute.",
+                    );
+                }
+            }
+        }
+
+        this
+    }
+}
+
 fn check_option_description(errors: &Errors, desc: &str, span: Span) {
     let chars = &mut desc.trim().chars();
     match (chars.next(), chars.next()) {
@@ -498,7 +571,7 @@
     // Ensure that `#[argh(subcommand)]` is present.
     if is_subcommand.is_none() {
         errors.err_span(
-            type_span.clone(),
+            *type_span,
             concat!(
                 "`#![derive(FromArgs)]` on `enum`s can only be used to enumerate subcommands.\n",
                 "Consider adding `#[argh(subcommand)]` to the `enum` declaration.",
@@ -526,29 +599,6 @@
     }
 }
 
-/// Checks that an enum variant and its fields have no `#[argh(...)]` attributes.
-pub fn check_enum_variant_attrs(errors: &Errors, variant: &syn::Variant) {
-    for attr in &variant.attrs {
-        if is_argh_attr(attr) {
-            err_unused_enum_attr(errors, attr);
-        }
-    }
-
-    let fields = match &variant.fields {
-        syn::Fields::Named(fields) => &fields.named,
-        syn::Fields::Unnamed(fields) => &fields.unnamed,
-        syn::Fields::Unit => return,
-    };
-
-    for field in fields {
-        for attr in &field.attrs {
-            if is_argh_attr(attr) {
-                err_unused_enum_attr(errors, attr);
-            }
-        }
-    }
-}
-
 fn err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned) {
     errors.err(
         location,