From c56277c0844d525266c0c79c34c025d767f94258 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 2 Sep 2022 10:23:05 -0500 Subject: [PATCH 1/9] refactor(derive): Make Sp copyable --- clap_derive/src/item.rs | 4 ++-- clap_derive/src/utils/spanned.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index 67d336d6604..0c623813036 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -949,11 +949,11 @@ impl Item { } pub fn casing(&self) -> Sp { - self.casing.clone() + self.casing } pub fn env_casing(&self) -> Sp { - self.env_casing.clone() + self.env_casing } pub fn has_explicit_methods(&self) -> bool { diff --git a/clap_derive/src/utils/spanned.rs b/clap_derive/src/utils/spanned.rs index 11415f6f0ec..2d68d9a8e91 100644 --- a/clap_derive/src/utils/spanned.rs +++ b/clap_derive/src/utils/spanned.rs @@ -5,7 +5,7 @@ use syn::LitStr; use std::ops::{Deref, DerefMut}; /// An entity with a span attached. -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub struct Sp { val: T, span: Span, From 11ffcf939333c670bc69618e951c0de62c0b8650 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 2 Sep 2022 10:41:34 -0500 Subject: [PATCH 2/9] refactor(derive): Determine an Item's Kind first This will allow the attribute processing to change based on the kind --- clap_derive/src/item.rs | 94 +++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index 0c623813036..5101d0db7e3 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -437,6 +437,60 @@ impl Item { fn push_attrs(&mut self, attrs: &[Attribute]) { let parsed = ClapAttr::parse_all(attrs); + + for attr in &parsed { + if let Some(AttrValue::Call(_)) = &attr.value { + continue; + } + + let kind = match &attr.magic { + Some(MagicAttrName::FromGlobal) => { + if attr.value.is_some() { + let expr = attr.value_or_abort(); + abort!(expr, "attribute `{}` does not accept a value", attr.name); + } + let ty = Sp::call_site(Ty::Other); + let kind = Sp::new(Kind::FromGlobal(ty), attr.name.clone().span()); + Some(kind) + } + Some(MagicAttrName::Subcommand) if attr.value.is_none() => { + if attr.value.is_some() { + let expr = attr.value_or_abort(); + abort!(expr, "attribute `{}` does not accept a value", attr.name); + } + let ty = Sp::call_site(Ty::Other); + let kind = Sp::new(Kind::Subcommand(ty), attr.name.clone().span()); + Some(kind) + } + Some(MagicAttrName::ExternalSubcommand) if attr.value.is_none() => { + if attr.value.is_some() { + let expr = attr.value_or_abort(); + abort!(expr, "attribute `{}` does not accept a value", attr.name); + } + let kind = Sp::new(Kind::ExternalSubcommand, attr.name.clone().span()); + Some(kind) + } + Some(MagicAttrName::Flatten) if attr.value.is_none() => { + if attr.value.is_some() { + let expr = attr.value_or_abort(); + abort!(expr, "attribute `{}` does not accept a value", attr.name); + } + let kind = Sp::new(Kind::Flatten, attr.name.clone().span()); + Some(kind) + } + Some(MagicAttrName::Skip) => { + let expr = attr.value.clone(); + let kind = Sp::new(Kind::Skip(expr), attr.name.clone().span()); + Some(kind) + } + _ => None, + }; + + if let Some(kind) = kind { + self.set_kind(kind); + } + } + for attr in &parsed { if let Some(AttrValue::Call(tokens)) = &attr.value { // Force raw mode with method call syntax @@ -475,28 +529,6 @@ impl Item { Some(MagicAttrName::ValueEnum) if attr.value.is_none() => self.is_enum = true, - Some(MagicAttrName::FromGlobal) if attr.value.is_none() => { - let ty = Sp::call_site(Ty::Other); - let kind = Sp::new(Kind::FromGlobal(ty), attr.name.clone().span()); - self.set_kind(kind); - } - - Some(MagicAttrName::Subcommand) if attr.value.is_none() => { - let ty = Sp::call_site(Ty::Other); - let kind = Sp::new(Kind::Subcommand(ty), attr.name.clone().span()); - self.set_kind(kind); - } - - Some(MagicAttrName::ExternalSubcommand) if attr.value.is_none() => { - let kind = Sp::new(Kind::ExternalSubcommand, attr.name.clone().span()); - self.set_kind(kind); - } - - Some(MagicAttrName::Flatten) if attr.value.is_none() => { - let kind = Sp::new(Kind::Flatten, attr.name.clone().span()); - self.set_kind(kind); - } - Some(MagicAttrName::VerbatimDocComment) if attr.value.is_none() => { self.verbatim_doc_comment = true } @@ -521,12 +553,6 @@ impl Item { } } - Some(MagicAttrName::Skip) => { - let expr = attr.value.clone(); - let kind = Sp::new(Kind::Skip(expr), attr.name.clone().span()); - self.set_kind(kind); - } - Some(MagicAttrName::DefaultValueT) => { let ty = if let Some(ty) = self.ty.as_ref() { ty @@ -791,14 +817,18 @@ impl Item { // Directives that never receive a value Some(MagicAttrName::ValueEnum) - | Some(MagicAttrName::FromGlobal) - | Some(MagicAttrName::Subcommand) - | Some(MagicAttrName::ExternalSubcommand) - | Some(MagicAttrName::Flatten) | Some(MagicAttrName::VerbatimDocComment) => { let expr = attr.value_or_abort(); abort!(expr, "attribute `{}` does not accept a value", attr.name); } + + // Kinds + Some(MagicAttrName::FromGlobal) + | Some(MagicAttrName::Subcommand) + | Some(MagicAttrName::ExternalSubcommand) + | Some(MagicAttrName::Flatten) + | Some(MagicAttrName::Skip) => { + } } } } From edce5c211971b973dfd1bc19edffbbfe2dee7f14 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 2 Sep 2022 10:48:36 -0500 Subject: [PATCH 3/9] fix(derive): Improve Kind conflict errors This makes it better scale for the future --- clap_derive/src/item.rs | 22 ++++++++++++++----- tests/derive_ui/skip_flatten.stderr | 4 ++-- tests/derive_ui/skip_subcommand.stderr | 4 ++-- tests/derive_ui/subcommand_and_flatten.stderr | 4 ++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index 5101d0db7e3..549836d324b 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -855,13 +855,10 @@ impl Item { } fn set_kind(&mut self, kind: Sp) { - if let Kind::Arg(_) = *self.kind { - self.kind = kind; + if let (Some(old), Some(new)) = (self.kind.name(), kind.name()) { + abort!(kind.span(), "`{}` cannot be used with `{}`", new, old); } else { - abort!( - kind.span(), - "`subcommand`, `flatten`, `external_subcommand` and `skip` cannot be used together" - ); + self.kind = kind; } } @@ -1094,6 +1091,19 @@ pub enum Kind { ExternalSubcommand, } +impl Kind { + fn name(&self) -> Option<&'static str> { + match self { + Self::Arg(_) => None, + Self::FromGlobal(_) => Some("from_global"), + Self::Subcommand(_) => Some("subcommand"), + Self::Flatten => Some("flatten"), + Self::Skip(_) => Some("skip"), + Self::ExternalSubcommand => Some("external_subcommand"), + } + } +} + #[derive(Clone)] pub struct Method { name: Ident, diff --git a/tests/derive_ui/skip_flatten.stderr b/tests/derive_ui/skip_flatten.stderr index 328dc9c3512..b4f689462cd 100644 --- a/tests/derive_ui/skip_flatten.stderr +++ b/tests/derive_ui/skip_flatten.stderr @@ -1,5 +1,5 @@ -error: `subcommand`, `flatten`, `external_subcommand` and `skip` cannot be used together - --> $DIR/skip_flatten.rs:17:18 +error: `flatten` cannot be used with `skip` + --> tests/derive_ui/skip_flatten.rs:17:18 | 17 | #[clap(skip, flatten)] | ^^^^^^^ diff --git a/tests/derive_ui/skip_subcommand.stderr b/tests/derive_ui/skip_subcommand.stderr index aeea102976c..e29ea0caff1 100644 --- a/tests/derive_ui/skip_subcommand.stderr +++ b/tests/derive_ui/skip_subcommand.stderr @@ -1,5 +1,5 @@ -error: `subcommand`, `flatten`, `external_subcommand` and `skip` cannot be used together - --> $DIR/skip_subcommand.rs:17:24 +error: `skip` cannot be used with `subcommand` + --> tests/derive_ui/skip_subcommand.rs:17:24 | 17 | #[clap(subcommand, skip)] | ^^^^ diff --git a/tests/derive_ui/subcommand_and_flatten.stderr b/tests/derive_ui/subcommand_and_flatten.stderr index c9fcc4312cd..3859fb10a57 100644 --- a/tests/derive_ui/subcommand_and_flatten.stderr +++ b/tests/derive_ui/subcommand_and_flatten.stderr @@ -1,5 +1,5 @@ -error: `subcommand`, `flatten`, `external_subcommand` and `skip` cannot be used together - --> $DIR/subcommand_and_flatten.rs:16:24 +error: `flatten` cannot be used with `subcommand` + --> tests/derive_ui/subcommand_and_flatten.rs:16:24 | 16 | #[clap(subcommand, flatten)] | ^^^^^^^ From 7eaa226526a51e59d634fedbc84ac5d7702fca91 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 2 Sep 2022 11:14:31 -0500 Subject: [PATCH 4/9] fix(derive): Provide deprecations for bald action/value_parser These will be going away, so we should tell the user. This is mostly a test bed for our new deprecation plumbing. --- clap_derive/src/derives/args.rs | 13 +++++++ clap_derive/src/derives/subcommand.rs | 37 +++++++++++++++++-- clap_derive/src/derives/value_enum.rs | 15 +++++--- clap_derive/src/item.rs | 52 +++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 8 deletions(-) diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs index 9cf24df4aef..0509805eedf 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -291,9 +291,16 @@ pub fn gen_augment( let id = item.id(); let explicit_methods = item.field_methods(true); + let deprecations = if !override_required { + item.deprecations() + } else { + quote!() + }; Some(quote_spanned! { field.span()=> let #app_var = #app_var.arg({ + #deprecations + #[allow(deprecated)] let arg = clap::Arg::new(#id) #implicit_methods; @@ -307,9 +314,15 @@ pub fn gen_augment( } }); + let deprecations = if !override_required { + parent_item.deprecations() + } else { + quote!() + }; let initial_app_methods = parent_item.initial_top_level_methods(); let final_app_methods = parent_item.final_top_level_methods(); quote! {{ + #deprecations let #app_var = #app_var #initial_app_methods; #( #args )* #subcmd diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs index 408f20deb42..772b63a0698 100644 --- a/clap_derive/src/derives/subcommand.rs +++ b/clap_derive/src/derives/subcommand.rs @@ -143,9 +143,15 @@ fn gen_augment( or `Vec`." ), }; + let deprecations = if !override_required { + item.deprecations() + } else { + quote!() + }; let subcommand = match subty_if_name(ty, "Vec") { Some(subty) => { quote_spanned! { kind.span()=> + #deprecations let #app_var = #app_var.external_subcommand_value_parser(clap::value_parser!(#subty)); } } @@ -162,11 +168,17 @@ fn gen_augment( Kind::Flatten => match variant.fields { Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0]; + let deprecations = if !override_required { + item.deprecations() + } else { + quote!() + }; let old_heading_var = format_ident!("__clap_old_heading"); let next_help_heading = item.next_help_heading(); let next_display_order = item.next_display_order(); let subcommand = if override_required { quote! { + #deprecations let #old_heading_var = #app_var.get_next_help_heading().map(|s| clap::builder::Str::from(s.to_owned())); let #app_var = #app_var #next_help_heading #next_display_order; let #app_var = <#ty as clap::Subcommand>::augment_subcommands_for_update(#app_var); @@ -174,6 +186,7 @@ fn gen_augment( } } else { quote! { + #deprecations let #old_heading_var = #app_var.get_next_help_heading().map(|s| clap::builder::Str::from(s.to_owned())); let #app_var = #app_var #next_help_heading #next_display_order; let #app_var = <#ty as clap::Subcommand>::augment_subcommands(#app_var); @@ -217,10 +230,16 @@ fn gen_augment( }; let name = item.cased_name(); + let deprecations = if !override_required { + item.deprecations() + } else { + quote!() + }; let initial_app_methods = item.initial_top_level_methods(); let final_from_attrs = item.final_top_level_methods(); let subcommand = quote! { let #app_var = #app_var.subcommand({ + #deprecations; let #subcommand_var = clap::Command::new(#name); let #subcommand_var = #subcommand_var #initial_app_methods; let #subcommand_var = #arg_block; @@ -286,9 +305,15 @@ fn gen_augment( } }; + let deprecations = if !override_required { + item.deprecations() + } else { + quote!() + }; let name = item.cased_name(); let subcommand = quote! { let #app_var = #app_var.subcommand({ + #deprecations let #subcommand_var = clap::Command::new(#name); #sub_augment }); @@ -299,12 +324,18 @@ fn gen_augment( }) .collect(); + let deprecations = if !override_required { + parent_item.deprecations() + } else { + quote!() + }; let initial_app_methods = parent_item.initial_top_level_methods(); let final_app_methods = parent_item.final_top_level_methods(); quote! { - let #app_var = #app_var #initial_app_methods; - #( #subcommands )*; - #app_var #final_app_methods + #deprecations; + let #app_var = #app_var #initial_app_methods; + #( #subcommands )*; + #app_var #final_app_methods } } diff --git a/clap_derive/src/derives/value_enum.rs b/clap_derive/src/derives/value_enum.rs index 8ef32903100..6a13156d0e4 100644 --- a/clap_derive/src/derives/value_enum.rs +++ b/clap_derive/src/derives/value_enum.rs @@ -41,10 +41,10 @@ pub fn derive_value_enum(input: &DeriveInput) -> TokenStream { } } -pub fn gen_for_enum(_item: &Item, item_name: &Ident, variants: &[(&Variant, Item)]) -> TokenStream { +pub fn gen_for_enum(item: &Item, item_name: &Ident, variants: &[(&Variant, Item)]) -> TokenStream { let lits = lits(variants); let value_variants = gen_value_variants(&lits); - let to_possible_value = gen_to_possible_value(&lits); + let to_possible_value = gen_to_possible_value(item, &lits); quote! { #[allow(dead_code, unreachable_code, unused_variables, unused_braces)] @@ -78,12 +78,14 @@ fn lits(variants: &[(&Variant, Item)]) -> Vec<(TokenStream, Ident)> { abort!(variant.span(), "`#[derive(ValueEnum)]` only supports unit variants. Non-unit variants must be skipped"); } let fields = item.field_methods(false); + let deprecations = item.deprecations(); let name = item.cased_name(); Some(( - quote_spanned! { variant.span()=> + quote_spanned! { variant.span()=> { + #deprecations clap::builder::PossibleValue::new(#name) #fields - }, + }}, variant.ident.clone(), )) } @@ -101,11 +103,14 @@ fn gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream { } } -fn gen_to_possible_value(lits: &[(TokenStream, Ident)]) -> TokenStream { +fn gen_to_possible_value(item: &Item, lits: &[(TokenStream, Ident)]) -> TokenStream { let (lit, variant): (Vec, Vec) = lits.iter().cloned().unzip(); + let deprecations = item.deprecations(); + quote! { fn to_possible_value<'a>(&self) -> ::std::option::Option { + #deprecations match self { #(Self::#variant => Some(#lit),)* _ => None diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index 549836d324b..03e3aa4e149 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -41,6 +41,7 @@ pub struct Item { ty: Option, doc_comment: Vec, methods: Vec, + deprecations: Vec, value_parser: Option, action: Option, verbatim_doc_comment: bool, @@ -409,6 +410,7 @@ impl Item { env_casing, doc_comment: vec![], methods: vec![], + deprecations: vec![], value_parser: None, action: None, verbatim_doc_comment: false, @@ -512,11 +514,23 @@ impl Item { #[cfg(not(feature = "unstable-v5"))] Some(MagicAttrName::ValueParser) if attr.value.is_none() => { + self.deprecations.push(Deprecation { + span: attr.name.span(), + id: "bare_value_parser", + version: "4.0.0", + description: "`#[clap(value_parser)]` is now the default and is no longer needed`".to_owned(), + }); self.value_parser = Some(ValueParser::Implicit(attr.name.clone())); } #[cfg(not(feature = "unstable-v5"))] Some(MagicAttrName::Action) if attr.value.is_none() => { + self.deprecations.push(Deprecation { + span: attr.name.span(), + id: "bare_action", + version: "4.0.0", + description: "`#[clap(action)]` is now the default and is no longer needed`".to_owned(), + }); self.action = Some(Action::Implicit(attr.name.clone())); } @@ -903,6 +917,11 @@ impl Item { } } + pub fn deprecations(&self) -> proc_macro2::TokenStream { + let deprecations = &self.deprecations; + quote!( #(#deprecations)* ) + } + pub fn next_display_order(&self) -> TokenStream { let next_display_order = self.next_display_order.as_ref().into_iter(); quote!( #(#next_display_order)* ) @@ -1155,6 +1174,39 @@ impl ToTokens for Method { } } +#[derive(Clone)] +pub struct Deprecation { + pub span: Span, + pub id: &'static str, + pub version: &'static str, + pub description: String, +} + +impl ToTokens for Deprecation { + fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) { + let tokens = if cfg!(feature = "deprecated") { + let Deprecation { + span, + id, + version, + description, + } = self; + let span = *span; + let id = Ident::new(id, span); + + quote_spanned!(span=> { + #[deprecated(since = #version, note = #description)] + fn #id() {} + #id(); + }) + } else { + quote!() + }; + + tokens.to_tokens(ts); + } +} + /// replace all `:` with `, ` when not inside the `<>` /// /// `"author1:author2:author3" => "author1, author2, author3"` From 7b0c76de31289bd209a266f5cb097336fdd7c671 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 31 Aug 2022 19:42:37 -0500 Subject: [PATCH 5/9] Revert "fix(derive): Remove structopt attribute support" This reverts commit 521a012c281d9086475fe503817f11748ea1d705. --- CHANGELOG.md | 1 - clap_derive/src/attr.rs | 2 +- clap_derive/src/lib.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a58396e1ee4..157e3974030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,7 +55,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - *(derive)* Changed the default for arguments from `parse` to `value_parser`., removing `parse` support - *(derive)* `subcommand_required(true).arg_required_else_help(true)` is set instead of `SubcommandRequiredElseHelp` (#3280) - *(derive)* Remove `arg_enum` attribute in favor of `value_enum` -- *(derive)* Remove `structopt()` attributes in favor of `clap()` ### Compatibility diff --git a/clap_derive/src/attr.rs b/clap_derive/src/attr.rs index aee3ee51633..5cecceed5e0 100644 --- a/clap_derive/src/attr.rs +++ b/clap_derive/src/attr.rs @@ -23,7 +23,7 @@ impl ClapAttr { pub fn parse_all(all_attrs: &[Attribute]) -> Vec { all_attrs .iter() - .filter(|attr| attr.path.is_ident("clap")) + .filter(|attr| attr.path.is_ident("clap") || attr.path.is_ident("structopt")) .flat_map(|attr| { attr.parse_args_with(Punctuated::::parse_terminated) .unwrap_or_abort() diff --git a/clap_derive/src/lib.rs b/clap_derive/src/lib.rs index 19b9665753f..5322a732fc0 100644 --- a/clap_derive/src/lib.rs +++ b/clap_derive/src/lib.rs @@ -42,7 +42,7 @@ pub fn value_enum(input: TokenStream) -> TokenStream { /// receiving an instance of `clap::ArgMatches` from conducting parsing, and then /// implementing a conversion code to instantiate an instance of the user /// context struct. -#[proc_macro_derive(Parser, attributes(clap))] +#[proc_macro_derive(Parser, attributes(clap, structopt))] #[proc_macro_error] pub fn parser(input: TokenStream) -> TokenStream { let input: DeriveInput = parse_macro_input!(input); From 0ae119fda906ee7967430e890c92164738bb64d8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 31 Aug 2022 19:49:30 -0500 Subject: [PATCH 6/9] fix(derive): Report deprecations for structopt attributes This is more of a test bed for adding new attributes and deprecating clap ones. --- clap_derive/src/attr.rs | 44 +++++++++++++++++++++++++++++--- clap_derive/src/item.rs | 31 +++++++++++++++++++++- clap_derive/src/utils/spanned.rs | 4 +++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/clap_derive/src/attr.rs b/clap_derive/src/attr.rs index 5cecceed5e0..f073d23f65f 100644 --- a/clap_derive/src/attr.rs +++ b/clap_derive/src/attr.rs @@ -5,6 +5,7 @@ use proc_macro_error::abort; use proc_macro_error::ResultExt; use quote::quote; use quote::ToTokens; +use syn::spanned::Spanned; use syn::{ parenthesized, parse::{Parse, ParseStream}, @@ -12,8 +13,11 @@ use syn::{ Attribute, Expr, Ident, LitStr, Token, }; +use crate::utils::Sp; + #[derive(Clone)] pub struct ClapAttr { + pub kind: Sp, pub name: Ident, pub magic: Option, pub value: Option, @@ -23,10 +27,24 @@ impl ClapAttr { pub fn parse_all(all_attrs: &[Attribute]) -> Vec { all_attrs .iter() - .filter(|attr| attr.path.is_ident("clap") || attr.path.is_ident("structopt")) - .flat_map(|attr| { + .filter_map(|attr| { + let kind = if attr.path.is_ident("clap") { + Some(Sp::new(AttrKind::Clap, attr.path.span())) + } else if attr.path.is_ident("structopt") { + Some(Sp::new(AttrKind::StructOpt, attr.path.span())) + } else { + None + }; + kind.map(|k| (k, attr)) + }) + .flat_map(|(k, attr)| { attr.parse_args_with(Punctuated::::parse_terminated) .unwrap_or_abort() + .into_iter() + .map(move |mut a| { + a.kind = k; + a + }) }) .collect() } @@ -113,7 +131,12 @@ impl Parse for ClapAttr { None }; - Ok(Self { name, magic, value }) + Ok(Self { + kind: Sp::new(AttrKind::Clap, name.span()), + name, + magic, + value, + }) } } @@ -166,3 +189,18 @@ impl ToTokens for AttrValue { } } } + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum AttrKind { + Clap, + StructOpt, +} + +impl AttrKind { + pub fn as_str(&self) -> &'static str { + match self { + Self::Clap => "clap", + Self::StructOpt => "structopt", + } + } +} diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index 03e3aa4e149..ab035fdfbc0 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -494,6 +494,18 @@ impl Item { } for attr in &parsed { + match attr.kind.get() { + AttrKind::Clap => {} + AttrKind::StructOpt => { + self.deprecations.push(Deprecation::attribute( + "4.0.0", + *attr.kind.get(), + AttrKind::Clap, + attr.kind.span(), + )); + } + } + if let Some(AttrValue::Call(tokens)) = &attr.value { // Force raw mode with method call syntax self.push_method(attr.name.clone(), quote!(#(#tokens),*)); @@ -541,7 +553,9 @@ impl Item { ); } - Some(MagicAttrName::ValueEnum) if attr.value.is_none() => self.is_enum = true, + Some(MagicAttrName::ValueEnum) if attr.value.is_none() => { + self.is_enum = true + } Some(MagicAttrName::VerbatimDocComment) if attr.value.is_none() => { self.verbatim_doc_comment = true @@ -1182,6 +1196,21 @@ pub struct Deprecation { pub description: String, } +impl Deprecation { + fn attribute(version: &'static str, old: AttrKind, new: AttrKind, span: Span) -> Self { + Self { + span, + id: "old_attribute", + version, + description: format!( + "Attribute `#[{}(...)]` has been deprecated in favor of `#[{}(...)]`", + old.as_str(), + new.as_str() + ), + } + } +} + impl ToTokens for Deprecation { fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) { let tokens = if cfg!(feature = "deprecated") { diff --git a/clap_derive/src/utils/spanned.rs b/clap_derive/src/utils/spanned.rs index 2d68d9a8e91..e064b53f037 100644 --- a/clap_derive/src/utils/spanned.rs +++ b/clap_derive/src/utils/spanned.rs @@ -23,6 +23,10 @@ impl Sp { } } + pub fn get(&self) -> &T { + &self.val + } + pub fn span(&self) -> Span { self.span } From 59a457889806b3dddd31d9ec5522225c40146c46 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 2 Sep 2022 11:48:45 -0500 Subject: [PATCH 7/9] refactor(derive): Track the item's Kind --- clap_derive/src/derives/args.rs | 15 +++++++++++ clap_derive/src/item.rs | 47 +++++++++++++++++++-------------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs index 0509805eedf..ae5172ec440 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -194,6 +194,11 @@ pub fn gen_augment( let args = fields.iter().filter_map(|(field, item)| { let kind = item.kind(); match &*kind { + Kind::Command(ty) + | Kind::Value(ty) => { + abort!(ty.span(), "Invalid attribute are for arguments") + } + Kind::Subcommand(_) | Kind::Skip(_) | Kind::FromGlobal(_) @@ -336,6 +341,11 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream { let kind = item.kind(); let arg_matches = format_ident!("__clap_arg_matches"); match &*kind { + Kind::Command(ty) + | Kind::Value(ty) => { + abort!(ty.span(), "Invalid attribute are for arguments") + } + Kind::ExternalSubcommand => { abort! { kind.span(), "`external_subcommand` can be used only on enum variants" @@ -404,6 +414,11 @@ pub fn gen_updater(fields: &[(&Field, Item)], use_self: bool) -> TokenStream { let arg_matches = format_ident!("__clap_arg_matches"); match &*kind { + Kind::Command(ty) + | Kind::Value(ty) => { + abort!(ty.span(), "Invalid attribute are for arguments") + } + Kind::ExternalSubcommand => { abort! { kind.span(), "`external_subcommand` can be used only on enum variants" diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index ab035fdfbc0..cb398855027 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -58,7 +58,8 @@ impl Item { let attrs = &input.attrs; let argument_casing = Sp::call_site(DEFAULT_CASING); let env_casing = Sp::call_site(DEFAULT_ENV_CASING); - Self::from_struct(span, attrs, name, argument_casing, env_casing) + let kind = Sp::new(Kind::Command(Sp::new(Ty::Other, span)), span); + Self::from_struct(attrs, name, argument_casing, env_casing, kind) } pub fn from_subcommand_enum(input: &DeriveInput, name: Name) -> Self { @@ -66,7 +67,8 @@ impl Item { let attrs = &input.attrs; let argument_casing = Sp::call_site(DEFAULT_CASING); let env_casing = Sp::call_site(DEFAULT_ENV_CASING); - Self::from_struct(span, attrs, name, argument_casing, env_casing) + let kind = Sp::new(Kind::Command(Sp::new(Ty::Other, span)), span); + Self::from_struct(attrs, name, argument_casing, env_casing, kind) } pub fn from_value_enum(input: &DeriveInput, name: Name) -> Self { @@ -74,17 +76,18 @@ impl Item { let attrs = &input.attrs; let argument_casing = Sp::call_site(DEFAULT_CASING); let env_casing = Sp::call_site(DEFAULT_ENV_CASING); - Self::from_struct(span, attrs, name, argument_casing, env_casing) + let kind = Sp::new(Kind::Value(Sp::new(Ty::Other, span)), span); + Self::from_struct(attrs, name, argument_casing, env_casing, kind) } fn from_struct( - span: Span, attrs: &[Attribute], name: Name, argument_casing: Sp, env_casing: Sp, + kind: Sp, ) -> Self { - let mut res = Self::new(span, name, None, argument_casing, env_casing); + let mut res = Self::new(name, None, argument_casing, env_casing, kind); res.push_attrs(attrs); res.push_doc_comment(attrs, "about"); @@ -103,7 +106,7 @@ impl Item { match &*res.kind { Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"), Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"), - Kind::Arg(_) => res, + Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => res, Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"), Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"), Kind::ExternalSubcommand => abort!( @@ -119,13 +122,9 @@ impl Item { env_casing: Sp, ) -> Self { let name = variant.ident.clone(); - let mut res = Self::new( - variant.span(), - Name::Derived(name), - None, - struct_casing, - env_casing, - ); + let span = variant.span(); + let kind = Sp::new(Kind::Command(Sp::new(Ty::Other, span)), span); + let mut res = Self::new(Name::Derived(name), None, struct_casing, env_casing, kind); res.push_attrs(&variant.attrs); res.push_doc_comment(&variant.attrs, "about"); @@ -210,7 +209,7 @@ impl Item { Kind::FromGlobal(_) => { abort!(res.kind.span(), "from_global is not supported on variants"); } - Kind::Arg(_) => (), + Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => (), } res @@ -221,12 +220,14 @@ impl Item { argument_casing: Sp, env_casing: Sp, ) -> Self { + let span = variant.span(); + let kind = Sp::new(Kind::Value(Sp::new(Ty::Other, span)), span); let mut res = Self::new( - variant.span(), Name::Derived(variant.ident.clone()), None, argument_casing, env_casing, + kind, ); res.push_attrs(&variant.attrs); res.push_doc_comment(&variant.attrs, "help"); @@ -246,7 +247,7 @@ impl Item { match &*res.kind { Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"), Kind::Skip(_) => res, - Kind::Arg(_) => res, + Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => res, Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"), Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"), Kind::ExternalSubcommand => abort!( @@ -262,12 +263,14 @@ impl Item { env_casing: Sp, ) -> Self { let name = field.ident.clone().unwrap(); + let span = field.span(); + let kind = Sp::new(Kind::Arg(Sp::new(Ty::Other, span)), span); let mut res = Self::new( - field.span(), Name::Derived(name), Some(field.ty.clone()), struct_casing, env_casing, + kind, ); res.push_attrs(&field.attrs); res.push_doc_comment(&field.attrs, "help"); @@ -354,7 +357,7 @@ impl Item { let ty = Ty::from_syn_ty(&field.ty); res.kind = Sp::new(Kind::FromGlobal(ty), orig_ty.span()); } - Kind::Arg(_) => { + Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => { let ty = Ty::from_syn_ty(&field.ty); match *ty { @@ -397,11 +400,11 @@ impl Item { } fn new( - default_span: Span, name: Name, ty: Option, casing: Sp, env_casing: Sp, + kind: Sp, ) -> Self { Self { name, @@ -418,7 +421,7 @@ impl Item { next_help_heading: None, is_enum: false, is_positional: true, - kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span), + kind, } } @@ -1117,6 +1120,8 @@ fn default_action(field_type: &Type, span: Span) -> Method { #[derive(Clone)] pub enum Kind { Arg(Sp), + Command(Sp), + Value(Sp), FromGlobal(Sp), Subcommand(Sp), Flatten, @@ -1128,6 +1133,8 @@ impl Kind { fn name(&self) -> Option<&'static str> { match self { Self::Arg(_) => None, + Self::Command(_) => None, + Self::Value(_) => None, Self::FromGlobal(_) => Some("from_global"), Self::Subcommand(_) => Some("subcommand"), Self::Flatten => Some("flatten"), From 97ce0c44f7bd8f699f9347543ebcb39204cd2aff Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 2 Sep 2022 12:51:25 -0500 Subject: [PATCH 8/9] refactor(derive): Generalize Kind based error checks --- clap_derive/src/item.rs | 81 ++++++++++++------- .../external_subcommand_misuse.stderr | 4 +- tests/derive_ui/struct_subcommand.stderr | 4 +- 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index cb398855027..55c93e74d3a 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -104,14 +104,16 @@ impl Item { ); } match &*res.kind { - Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"), - Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"), Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => res, - Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"), - Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"), + Kind::Subcommand(_) | Kind::Skip(_) | Kind::FromGlobal(_) | Kind::Flatten => abort!( + res.kind.span(), + "`{}` is only allowed on fields", + res.kind.name(), + ), Kind::ExternalSubcommand => abort!( res.kind.span(), - "external_subcommand is only allowed on fields" + "`{}` is only allowed on variants", + res.kind.name(), ), } } @@ -207,7 +209,11 @@ impl Item { } Kind::Skip(_) => (), Kind::FromGlobal(_) => { - abort!(res.kind.span(), "from_global is not supported on variants"); + abort!( + res.kind.span(), + "`{}` is only allowed on fields", + res.kind.name(), + ) } Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => (), } @@ -245,14 +251,17 @@ impl Item { ); } match &*res.kind { - Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"), - Kind::Skip(_) => res, Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => res, - Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"), - Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"), + Kind::Skip(_) => res, + Kind::Subcommand(_) | Kind::FromGlobal(_) | Kind::Flatten => abort!( + res.kind.span(), + "`{}` is only allowed on fields", + res.kind.name(), + ), Kind::ExternalSubcommand => abort!( res.kind.span(), - "external_subcommand is only allowed on fields" + "`{}` is only allowed on variants", + res.kind.name(), ), } } @@ -300,11 +309,11 @@ impl Item { res.doc_comment = vec![]; } - Kind::ExternalSubcommand => { - abort! { res.kind.span(), - "`external_subcommand` can be used only on enum variants" - } - } + Kind::ExternalSubcommand => abort!( + res.kind.span(), + "`{}` is only allowed on variants", + res.kind.name(), + ), Kind::Subcommand(_) => { if let Some(value_parser) = res.value_parser.as_ref() { @@ -886,10 +895,24 @@ impl Item { } fn set_kind(&mut self, kind: Sp) { - if let (Some(old), Some(new)) = (self.kind.name(), kind.name()) { - abort!(kind.span(), "`{}` cannot be used with `{}`", new, old); - } else { - self.kind = kind; + match (self.kind.get(), kind.get()) { + (Kind::Arg(_), Kind::FromGlobal(_)) + | (Kind::Arg(_), Kind::Subcommand(_)) + | (Kind::Arg(_), Kind::Flatten) + | (Kind::Arg(_), Kind::Skip(_)) + | (Kind::Command(_), Kind::Subcommand(_)) + | (Kind::Command(_), Kind::Flatten) + | (Kind::Command(_), Kind::Skip(_)) + | (Kind::Command(_), Kind::ExternalSubcommand) + | (Kind::Value(_), Kind::Skip(_)) => { + self.kind = kind; + } + + (_, _) => { + let old = self.kind.name(); + let new = kind.name(); + abort!(kind.span(), "`{}` cannot be used with `{}`", new, old); + } } } @@ -1130,16 +1153,16 @@ pub enum Kind { } impl Kind { - fn name(&self) -> Option<&'static str> { + fn name(&self) -> &'static str { match self { - Self::Arg(_) => None, - Self::Command(_) => None, - Self::Value(_) => None, - Self::FromGlobal(_) => Some("from_global"), - Self::Subcommand(_) => Some("subcommand"), - Self::Flatten => Some("flatten"), - Self::Skip(_) => Some("skip"), - Self::ExternalSubcommand => Some("external_subcommand"), + Self::Arg(_) => "arg", + Self::Command(_) => "command", + Self::Value(_) => "value", + Self::FromGlobal(_) => "from_global", + Self::Subcommand(_) => "subcommand", + Self::Flatten => "flatten", + Self::Skip(_) => "skip", + Self::ExternalSubcommand => "external_subcommand", } } } diff --git a/tests/derive_ui/external_subcommand_misuse.stderr b/tests/derive_ui/external_subcommand_misuse.stderr index 14540920b6c..c9c6ab3edf8 100644 --- a/tests/derive_ui/external_subcommand_misuse.stderr +++ b/tests/derive_ui/external_subcommand_misuse.stderr @@ -1,5 +1,5 @@ -error: `external_subcommand` can be used only on enum variants - --> $DIR/external_subcommand_misuse.rs:5:12 +error: `external_subcommand` cannot be used with `arg` + --> tests/derive_ui/external_subcommand_misuse.rs:5:12 | 5 | #[clap(external_subcommand)] | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/derive_ui/struct_subcommand.stderr b/tests/derive_ui/struct_subcommand.stderr index e8b806bb2f9..5c3f9e15f1b 100644 --- a/tests/derive_ui/struct_subcommand.stderr +++ b/tests/derive_ui/struct_subcommand.stderr @@ -1,5 +1,5 @@ -error: subcommand is only allowed on fields - --> $DIR/struct_subcommand.rs:12:24 +error: `subcommand` is only allowed on fields + --> tests/derive_ui/struct_subcommand.rs:12:24 | 12 | #[clap(name = "basic", subcommand)] | ^^^^^^^^^^ From dbdd449dc3553e065335b06d124d7aa4e6e1d7d2 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 2 Sep 2022 13:04:49 -0500 Subject: [PATCH 9/9] refactor(derive): Further generalize kind errors --- clap_derive/src/derives/args.rs | 40 ++++++++-------- clap_derive/src/derives/subcommand.rs | 7 +++ clap_derive/src/derives/value_enum.rs | 7 +++ clap_derive/src/item.rs | 61 ++++++------------------ tests/derive_ui/struct_subcommand.stderr | 2 +- 5 files changed, 50 insertions(+), 67 deletions(-) diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs index ae5172ec440..f057abe1032 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -72,6 +72,13 @@ pub fn gen_for_struct( generics: &Generics, fields: &[(&Field, Item)], ) -> TokenStream { + if !matches!(&*item.kind(), Kind::Command(_)) { + abort! { item.kind().span(), + "`{}` cannot be used with `command`", + item.kind().name(), + } + } + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let constructor = gen_constructor(fields); @@ -194,12 +201,9 @@ pub fn gen_augment( let args = fields.iter().filter_map(|(field, item)| { let kind = item.kind(); match &*kind { - Kind::Command(ty) - | Kind::Value(ty) => { - abort!(ty.span(), "Invalid attribute are for arguments") - } - - Kind::Subcommand(_) + Kind::Command(_) + | Kind::Value(_) + | Kind::Subcommand(_) | Kind::Skip(_) | Kind::FromGlobal(_) | Kind::ExternalSubcommand => None, @@ -341,14 +345,12 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream { let kind = item.kind(); let arg_matches = format_ident!("__clap_arg_matches"); match &*kind { - Kind::Command(ty) - | Kind::Value(ty) => { - abort!(ty.span(), "Invalid attribute are for arguments") - } - - Kind::ExternalSubcommand => { + Kind::Command(_) + | Kind::Value(_) + | Kind::ExternalSubcommand => { abort! { kind.span(), - "`external_subcommand` can be used only on enum variants" + "`{}` cannot be used with `arg`", + kind.name(), } } Kind::Subcommand(ty) => { @@ -414,14 +416,12 @@ pub fn gen_updater(fields: &[(&Field, Item)], use_self: bool) -> TokenStream { let arg_matches = format_ident!("__clap_arg_matches"); match &*kind { - Kind::Command(ty) - | Kind::Value(ty) => { - abort!(ty.span(), "Invalid attribute are for arguments") - } - - Kind::ExternalSubcommand => { + Kind::Command(_) + | Kind::Value(_) + | Kind::ExternalSubcommand => { abort! { kind.span(), - "`external_subcommand` can be used only on enum variants" + "`{}` cannot be used with `arg`", + kind.name(), } } Kind::Subcommand(ty) => { diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs index 772b63a0698..c6a13c21bc0 100644 --- a/clap_derive/src/derives/subcommand.rs +++ b/clap_derive/src/derives/subcommand.rs @@ -52,6 +52,13 @@ pub fn gen_for_enum( generics: &Generics, variants: &[(&Variant, Item)], ) -> TokenStream { + if !matches!(&*item.kind(), Kind::Command(_)) { + abort! { item.kind().span(), + "`{}` cannot be used with `command`", + item.kind().name(), + } + } + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let from_arg_matches = gen_from_arg_matches(variants); diff --git a/clap_derive/src/derives/value_enum.rs b/clap_derive/src/derives/value_enum.rs index 6a13156d0e4..f53b09dfcab 100644 --- a/clap_derive/src/derives/value_enum.rs +++ b/clap_derive/src/derives/value_enum.rs @@ -42,6 +42,13 @@ pub fn derive_value_enum(input: &DeriveInput) -> TokenStream { } pub fn gen_for_enum(item: &Item, item_name: &Ident, variants: &[(&Variant, Item)]) -> TokenStream { + if !matches!(&*item.kind(), Kind::Value(_)) { + abort! { item.kind().span(), + "`{}` cannot be used with `value`", + item.kind().name(), + } + } + let lits = lits(variants); let value_variants = gen_value_variants(&lits); let to_possible_value = gen_to_possible_value(item, &lits); diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index 55c93e74d3a..c1935ca599f 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -103,19 +103,8 @@ impl Item { "`action` attribute is only allowed on fields" ); } - match &*res.kind { - Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => res, - Kind::Subcommand(_) | Kind::Skip(_) | Kind::FromGlobal(_) | Kind::Flatten => abort!( - res.kind.span(), - "`{}` is only allowed on fields", - res.kind.name(), - ), - Kind::ExternalSubcommand => abort!( - res.kind.span(), - "`{}` is only allowed on variants", - res.kind.name(), - ), - } + + res } pub fn from_subcommand_variant( @@ -155,8 +144,6 @@ impl Item { res.doc_comment = vec![]; } - Kind::ExternalSubcommand => (), - Kind::Subcommand(_) => { if let Some(value_parser) = res.value_parser.as_ref() { abort!( @@ -207,15 +194,13 @@ impl Item { res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span()); } - Kind::Skip(_) => (), - Kind::FromGlobal(_) => { - abort!( - res.kind.span(), - "`{}` is only allowed on fields", - res.kind.name(), - ) - } - Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => (), + + Kind::ExternalSubcommand + | Kind::FromGlobal(_) + | Kind::Skip(_) + | Kind::Command(_) + | Kind::Value(_) + | Kind::Arg(_) => (), } res @@ -250,20 +235,8 @@ impl Item { "`action` attribute is only allowed on fields" ); } - match &*res.kind { - Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => res, - Kind::Skip(_) => res, - Kind::Subcommand(_) | Kind::FromGlobal(_) | Kind::Flatten => abort!( - res.kind.span(), - "`{}` is only allowed on fields", - res.kind.name(), - ), - Kind::ExternalSubcommand => abort!( - res.kind.span(), - "`{}` is only allowed on variants", - res.kind.name(), - ), - } + + res } pub fn from_args_field( @@ -309,12 +282,6 @@ impl Item { res.doc_comment = vec![]; } - Kind::ExternalSubcommand => abort!( - res.kind.span(), - "`{}` is only allowed on variants", - res.kind.name(), - ), - Kind::Subcommand(_) => { if let Some(value_parser) = res.value_parser.as_ref() { abort!( @@ -366,7 +333,7 @@ impl Item { let ty = Ty::from_syn_ty(&field.ty); res.kind = Sp::new(Kind::FromGlobal(ty), orig_ty.span()); } - Kind::Command(_) | Kind::Value(_) | Kind::Arg(_) => { + Kind::Arg(_) => { let ty = Ty::from_syn_ty(&field.ty); match *ty { @@ -403,6 +370,8 @@ impl Item { .unwrap_or_else(|| field.ty.span()), ); } + + Kind::Command(_) | Kind::Value(_) | Kind::ExternalSubcommand => {} } res @@ -1153,7 +1122,7 @@ pub enum Kind { } impl Kind { - fn name(&self) -> &'static str { + pub fn name(&self) -> &'static str { match self { Self::Arg(_) => "arg", Self::Command(_) => "command", diff --git a/tests/derive_ui/struct_subcommand.stderr b/tests/derive_ui/struct_subcommand.stderr index 5c3f9e15f1b..29290808f9b 100644 --- a/tests/derive_ui/struct_subcommand.stderr +++ b/tests/derive_ui/struct_subcommand.stderr @@ -1,4 +1,4 @@ -error: `subcommand` is only allowed on fields +error: `subcommand` cannot be used with `command` --> tests/derive_ui/struct_subcommand.rs:12:24 | 12 | #[clap(name = "basic", subcommand)]