diff --git a/src/builder/debug_asserts.rs b/src/builder/debug_asserts.rs index 45dd36d8dbc..d14e22b442e 100644 --- a/src/builder/debug_asserts.rs +++ b/src/builder/debug_asserts.rs @@ -280,20 +280,28 @@ pub(crate) fn assert_app(cmd: &Command) { } for group in cmd.get_groups() { + let derive_hint = if cfg!(feature = "derive") { + " (note: `Args` implicitly creates `ArgGroup`s; disable with `#[group(skip)]`" + } else { + "" + }; + // Name conflicts assert!( cmd.get_groups().filter(|x| x.id == group.id).count() < 2, - "Command {}: Argument group name must be unique\n\n\t'{}' is already in use", + "Command {}: Argument group name must be unique\n\n\t'{}' is already in use{}", cmd.get_name(), group.get_id(), + derive_hint ); // Groups should not have naming conflicts with Args assert!( !cmd.get_arguments().any(|x| x.get_id() == group.get_id()), - "Command {}: Argument group name '{}' must not conflict with argument name", + "Command {}: Argument group name '{}' must not conflict with argument name{}", cmd.get_name(), group.get_id(), + derive_hint ); for arg in &group.args { diff --git a/tests/derive/groups.rs b/tests/derive/groups.rs index 23a9fff3685..487ddbf23f2 100644 --- a/tests/derive/groups.rs +++ b/tests/derive/groups.rs @@ -90,3 +90,32 @@ fn skip_group_avoids_duplicate_ids() { assert_eq!(Compose::::group_id(), None); assert_eq!(Opt::group_id(), Some(clap::Id::from("Opt"))); } + +#[test] +#[should_panic = "\ +Command clap: Argument group name must be unique + +\t'Compose' is already in use (note: `Args` implicitly creates `ArgGroup`s; disable with `#[group(skip)]`"] +fn helpful_panic_on_duplicate_groups() { + #[derive(Parser, Debug)] + struct Opt { + #[command(flatten)] + first: Compose, + #[command(flatten)] + second: Compose, + } + + #[derive(clap::Args, Debug)] + pub struct Compose { + #[clap(flatten)] + pub left: L, + #[clap(flatten)] + pub right: R, + } + + #[derive(clap::Args, Clone, Copy, Debug)] + pub struct Empty; + + use clap::CommandFactory; + Opt::command().debug_assert(); +}