From 2ba552a87f8a058d61b6fa07286847acde9c9fbd Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Wed, 23 Jun 2021 22:44:30 -0700 Subject: [PATCH 1/9] replace structure identifier with self when implementing `from_clap` --- structopt-derive/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index b818386..36f60b0 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -559,10 +559,10 @@ fn gen_augment_clap_enum( } } -fn gen_from_clap_enum(name: &Ident) -> TokenStream { +fn gen_from_clap_enum() -> TokenStream { quote! { fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self { - <#name as ::structopt::StructOptInternal>::from_subcommand(matches.subcommand()) + ::from_subcommand(matches.subcommand()) .expect("structopt misuse: You likely tried to #[flatten] a struct \ that contains #[subcommand]. This is forbidden.") } @@ -816,7 +816,7 @@ fn impl_structopt_for_enum( let attrs = basic_clap_app_gen.attrs; let augment_clap = gen_augment_clap_enum(variants, &attrs); - let from_clap = gen_from_clap_enum(name); + let from_clap = gen_from_clap_enum(); let from_subcommand = gen_from_subcommand(name, variants, &attrs); let paw_impl = gen_paw_impl(name); From c1742943949f1730479bf0e69d0accbc1c6080b3 Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Wed, 23 Jun 2021 22:45:06 -0700 Subject: [PATCH 2/9] propagate generics when deriving `StructOpt` --- structopt-derive/src/lib.rs | 90 ++++++++++++++++++++++++++++++++----- tests/generics.rs | 86 +++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 tests/generics.rs diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 36f60b0..87f1fe5 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -736,9 +736,9 @@ fn gen_from_subcommand( } #[cfg(feature = "paw")] -fn gen_paw_impl(name: &Ident) -> TokenStream { +fn gen_paw_impl(impl_generics: &ImplGenerics, name: &Ident, ty_generics: &TypeGenerics, where_clause: &TokenStream) -> TokenStream { quote! { - impl ::structopt::paw::ParseArgs for #name { + impl #impl_generics ::structopt::paw::ParseArgs for #name #ty_generics #where_clause { type Error = std::io::Error; fn parse_args() -> std::result::Result { @@ -748,19 +748,83 @@ fn gen_paw_impl(name: &Ident) -> TokenStream { } } #[cfg(not(feature = "paw"))] -fn gen_paw_impl(_: &Ident) -> TokenStream { +fn gen_paw_impl(_: &ImplGenerics, _: &Ident, _: &TypeGenerics, _: &TokenStream) -> TokenStream { TokenStream::new() } +fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, TypeGenerics, TokenStream) { + use syn::{ token::Add, TypeParamBound::Trait }; + + fn path_is_structop(path: &Path) -> bool { + path.segments.last().unwrap().ident == "StructOpt" + } + + fn type_param_bounds_contains_structop(bounds: &Punctuated) -> bool { + for bound in bounds { + if let Trait(bound) = bound { + if path_is_structop(&bound.path) { + return true; + } + } + } + return false; + } + + let mut trait_bound_amendments = TokenStream::new(); + + for param in &generics.params { + if let GenericParam::Type(param) = param { + let param_ident = ¶m.ident; + if type_param_bounds_contains_structop(¶m.bounds) { + if !trait_bound_amendments.is_empty() { + trait_bound_amendments.extend(quote!{ , }); + } + trait_bound_amendments.extend(quote!{ #param_ident : ::structopt::StructOptInternal }); + } + } + } + + if let Some(where_clause) = &generics.where_clause { + for predicate in &where_clause.predicates { + if let WherePredicate::Type(predicate) = predicate { + let predicate_bounded_ty = &predicate.bounded_ty; + if type_param_bounds_contains_structop(&predicate.bounds) { + if !trait_bound_amendments.is_empty() { + trait_bound_amendments.extend(quote!{ , }); + } + trait_bound_amendments.extend(quote!{ #predicate_bounded_ty : ::structopt::StructOptInternal }); + } + } + } + } + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let where_clause = if !trait_bound_amendments.is_empty() { + if let Some(where_clause) = where_clause { + quote!{ #where_clause, #trait_bound_amendments } + } else { + quote!{ where #trait_bound_amendments } + } + } else { + quote!{ #where_clause } + }; + + (impl_generics, ty_generics, where_clause) +} + fn impl_structopt_for_struct( name: &Ident, fields: &Punctuated, attrs: &[Attribute], + generics: &Generics, ) -> TokenStream { + let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics); + let basic_clap_app_gen = gen_clap_struct(attrs); let augment_clap = gen_augment_clap(fields, &basic_clap_app_gen.attrs); let from_clap = gen_from_clap(name, fields, &basic_clap_app_gen.attrs); - let paw_impl = gen_paw_impl(name); + let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause); let clap_tokens = basic_clap_app_gen.tokens; quote! { @@ -778,7 +842,7 @@ fn impl_structopt_for_struct( )] #[deny(clippy::correctness)] #[allow(dead_code, unreachable_code)] - impl ::structopt::StructOpt for #name { + impl #impl_generics ::structopt::StructOpt for #name #ty_generics #where_clause { #clap_tokens #from_clap } @@ -797,7 +861,7 @@ fn impl_structopt_for_struct( )] #[deny(clippy::correctness)] #[allow(dead_code, unreachable_code)] - impl ::structopt::StructOptInternal for #name { + impl #impl_generics ::structopt::StructOptInternal for #name #ty_generics #where_clause { #augment_clap fn is_subcommand() -> bool { false } } @@ -810,7 +874,11 @@ fn impl_structopt_for_enum( name: &Ident, variants: &Punctuated, attrs: &[Attribute], + generics: &Generics, ) -> TokenStream { + + let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics); + let basic_clap_app_gen = gen_clap_enum(attrs); let clap_tokens = basic_clap_app_gen.tokens; let attrs = basic_clap_app_gen.attrs; @@ -818,7 +886,7 @@ fn impl_structopt_for_enum( let augment_clap = gen_augment_clap_enum(variants, &attrs); let from_clap = gen_from_clap_enum(); let from_subcommand = gen_from_subcommand(name, variants, &attrs); - let paw_impl = gen_paw_impl(name); + let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause); quote! { #[allow(unknown_lints)] @@ -834,7 +902,7 @@ fn impl_structopt_for_enum( clippy::cargo )] #[deny(clippy::correctness)] - impl ::structopt::StructOpt for #name { + impl #impl_generics ::structopt::StructOpt for #name #ty_generics #where_clause { #clap_tokens #from_clap } @@ -853,7 +921,7 @@ fn impl_structopt_for_enum( )] #[deny(clippy::correctness)] #[allow(dead_code, unreachable_code)] - impl ::structopt::StructOptInternal for #name { + impl #impl_generics ::structopt::StructOptInternal for #name #ty_generics #where_clause { #augment_clap #from_subcommand fn is_subcommand() -> bool { true } @@ -885,8 +953,8 @@ fn impl_structopt(input: &DeriveInput) -> TokenStream { Struct(DataStruct { fields: syn::Fields::Named(ref fields), .. - }) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs), - Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs), + }) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs, &input.generics), + Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs, &input.generics), _ => abort_call_site!("structopt only supports non-tuple structs and enums"), } } diff --git a/tests/generics.rs b/tests/generics.rs new file mode 100644 index 0000000..9a44d3b --- /dev/null +++ b/tests/generics.rs @@ -0,0 +1,86 @@ + +use structopt::StructOpt; + +#[test] +fn generic_struct_flatten() { + + #[derive(StructOpt,PartialEq,Debug)] + struct Inner{ + pub answer: isize + } + + #[derive(StructOpt,PartialEq,Debug)] + struct Outer{ + #[structopt(flatten)] + pub inner: T + } + + assert_eq!( + Outer{inner: Inner{ answer: 42 }}, + Outer::from_iter(&[ "--answer", "42" ]) + ) +} + +#[test] +fn generic_struct_flatten_w_where_clause() { + + #[derive(StructOpt,PartialEq,Debug)] + struct Inner{ + pub answer: isize + } + + #[derive(StructOpt,PartialEq,Debug)] + struct Outer where T:StructOpt { + #[structopt(flatten)] + pub inner: T + } + + assert_eq!( + Outer{inner: Inner{ answer: 42 }}, + Outer::from_iter(&[ "--answer", "42" ]) + ) +} + +#[test] +fn generic_enum() { + + #[derive(StructOpt,PartialEq,Debug)] + struct Inner{ + pub answer: isize + } + + #[derive(StructOpt,PartialEq,Debug)] + enum GenericEnum { + + Start(T), + Stop, + } + + assert_eq!( + GenericEnum::Start(Inner{answer: 42}), + GenericEnum::from_iter(&[ "test", "start", "42" ]) + ) + +} + +#[test] +fn generic_enum_w_where_clause() { + + #[derive(StructOpt,PartialEq,Debug)] + struct Inner{ + pub answer: isize + } + + #[derive(StructOpt,PartialEq,Debug)] + enum GenericEnum where T: StructOpt { + + Start(T), + Stop, + } + + assert_eq!( + GenericEnum::Start(Inner{answer: 42}), + GenericEnum::from_iter(&[ "test", "start", "42" ]) + ) + +} From c37d9c863b0eb1f420e125d556a0b4dec33e447c Mon Sep 17 00:00:00 2001 From: Nathan Jeffords <68177109+njeffords@users.noreply.github.com> Date: Thu, 24 Jun 2021 06:47:02 -0700 Subject: [PATCH 3/9] fix typo in new function names Co-authored-by: Guillaume P. --- structopt-derive/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 87f1fe5..1466130 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -755,14 +755,14 @@ fn gen_paw_impl(_: &ImplGenerics, _: &Ident, _: &TypeGenerics, _: &TokenStream) fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, TypeGenerics, TokenStream) { use syn::{ token::Add, TypeParamBound::Trait }; - fn path_is_structop(path: &Path) -> bool { + fn path_is_structopt(path: &Path) -> bool { path.segments.last().unwrap().ident == "StructOpt" } - fn type_param_bounds_contains_structop(bounds: &Punctuated) -> bool { + fn type_param_bounds_contains_structopt(bounds: &Punctuated) -> bool { for bound in bounds { if let Trait(bound) = bound { - if path_is_structop(&bound.path) { + if path_is_structopt(&bound.path) { return true; } } @@ -775,7 +775,7 @@ fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, Type for param in &generics.params { if let GenericParam::Type(param) = param { let param_ident = ¶m.ident; - if type_param_bounds_contains_structop(¶m.bounds) { + if type_param_bounds_contains_structopt(¶m.bounds) { if !trait_bound_amendments.is_empty() { trait_bound_amendments.extend(quote!{ , }); } @@ -788,7 +788,7 @@ fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, Type for predicate in &where_clause.predicates { if let WherePredicate::Type(predicate) = predicate { let predicate_bounded_ty = &predicate.bounded_ty; - if type_param_bounds_contains_structop(&predicate.bounds) { + if type_param_bounds_contains_structopt(&predicate.bounds) { if !trait_bound_amendments.is_empty() { trait_bound_amendments.extend(quote!{ , }); } From 154614469873ac4b0a0b9f39ae604ceca0451954 Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Thu, 24 Jun 2021 07:33:41 -0700 Subject: [PATCH 4/9] add `::Err: Display + Debug` when `FromStr` bound is present --- structopt-derive/src/lib.rs | 44 ++++++++++++++++++++++++++----------- tests/generics.rs | 17 ++++++++++++++ 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 1466130..ee45930 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -755,14 +755,14 @@ fn gen_paw_impl(_: &ImplGenerics, _: &Ident, _: &TypeGenerics, _: &TokenStream) fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, TypeGenerics, TokenStream) { use syn::{ token::Add, TypeParamBound::Trait }; - fn path_is_structopt(path: &Path) -> bool { - path.segments.last().unwrap().ident == "StructOpt" + fn path_ends_with(path: &Path, ident: &str) -> bool { + path.segments.last().unwrap().ident == ident } - fn type_param_bounds_contains_structopt(bounds: &Punctuated) -> bool { + fn type_param_bounds_contains(bounds: &Punctuated, ident: &str) -> bool { for bound in bounds { if let Trait(bound) = bound { - if path_is_structopt(&bound.path) { + if path_ends_with(&bound.path, ident) { return true; } } @@ -770,17 +770,33 @@ fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, Type return false; } - let mut trait_bound_amendments = TokenStream::new(); + struct TraitBoundAmendments(TokenStream); + impl TraitBoundAmendments { + fn new() -> Self { Self(TokenStream::new()) } + + fn extend(&mut self, amendment: TokenStream) { + if !self.0.is_empty() { + self.0.extend(quote!{ , }); + } + self.0.extend(amendment); + } + + fn into_tokens(self) -> TokenStream { + self.0 + } + } + + let mut trait_bound_amendments = TraitBoundAmendments::new(); for param in &generics.params { if let GenericParam::Type(param) = param { let param_ident = ¶m.ident; - if type_param_bounds_contains_structopt(¶m.bounds) { - if !trait_bound_amendments.is_empty() { - trait_bound_amendments.extend(quote!{ , }); - } + if type_param_bounds_contains(¶m.bounds, "StructOpt") { trait_bound_amendments.extend(quote!{ #param_ident : ::structopt::StructOptInternal }); } + if type_param_bounds_contains(¶m.bounds, "FromStr") { + trait_bound_amendments.extend(quote!{ < #param_ident as ::std::str::FromStr>::Err : ::std::fmt::Display + ::std::fmt::Debug }); + } } } @@ -788,16 +804,18 @@ fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, Type for predicate in &where_clause.predicates { if let WherePredicate::Type(predicate) = predicate { let predicate_bounded_ty = &predicate.bounded_ty; - if type_param_bounds_contains_structopt(&predicate.bounds) { - if !trait_bound_amendments.is_empty() { - trait_bound_amendments.extend(quote!{ , }); - } + if type_param_bounds_contains(&predicate.bounds, "StructOpt") { trait_bound_amendments.extend(quote!{ #predicate_bounded_ty : ::structopt::StructOptInternal }); } + if type_param_bounds_contains(&predicate.bounds, "FromStr") { + trait_bound_amendments.extend(quote!{ < #predicate_bounded_ty as ::std::str::FromStr>::Err : ::std::fmt::Display + ::std::fmt::Debug }); + } } } } + let trait_bound_amendments = trait_bound_amendments.into_tokens(); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let where_clause = if !trait_bound_amendments.is_empty() { diff --git a/tests/generics.rs b/tests/generics.rs index 9a44d3b..905c390 100644 --- a/tests/generics.rs +++ b/tests/generics.rs @@ -84,3 +84,20 @@ fn generic_enum_w_where_clause() { ) } + +#[test] +fn generic_w_fromstr_trait_bound() { + + use std::str::FromStr; + + #[derive(StructOpt,PartialEq,Debug)] + struct Opt where T:FromStr + { + answer: T + } + + assert_eq!( + Opt::{answer:42}, + Opt::::from_iter([& "--answer", "42" ]) + ) +} From 6a5cbedeb62ebb1df33af0e7fc5f71e6b9e214c6 Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Thu, 24 Jun 2021 07:34:05 -0700 Subject: [PATCH 5/9] add test for generic with no bounds --- tests/generics.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/generics.rs b/tests/generics.rs index 905c390..a16d99d 100644 --- a/tests/generics.rs +++ b/tests/generics.rs @@ -101,3 +101,21 @@ fn generic_w_fromstr_trait_bound() { Opt::::from_iter([& "--answer", "42" ]) ) } + +#[test] +fn generic_wo_trait_bound() { + + use std::time::Duration; + + #[derive(StructOpt,PartialEq,Debug)] + struct Opt { + answer: isize, + #[structopt(skip)] + took: Option + } + + assert_eq!( + Opt::{answer:42,took:None}, + Opt::::from_iter([& "--answer", "42" ]) + ) +} From acc18d5f2d6765211cff7cdc7b63d659a9743832 Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Thu, 24 Jun 2021 07:50:52 -0700 Subject: [PATCH 6/9] rework generic trait bounds amendments to handle trailing comma in where clause --- structopt-derive/src/lib.rs | 56 +++++++++++++++++++++++-------------- tests/generics.rs | 16 +++++++++++ 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index ee45930..948c134 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -770,32 +770,54 @@ fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, Type return false; } - struct TraitBoundAmendments(TokenStream); + struct TraitBoundAmendments{ + tokens: TokenStream, + need_where: bool, + need_comma: bool, + } + impl TraitBoundAmendments { - fn new() -> Self { Self(TokenStream::new()) } + fn new(where_clause: Option<&WhereClause>) -> Self { + let tokens = TokenStream::new(); + let (need_where,need_comma) = if let Some(where_clause) = where_clause { + if where_clause.predicates.trailing_punct() { + (false, false) + } else { + (false, true) + } + } else { + (true, false) + }; + Self{tokens, need_where, need_comma} + } - fn extend(&mut self, amendment: TokenStream) { - if !self.0.is_empty() { - self.0.extend(quote!{ , }); + fn add(&mut self, amendment: TokenStream) { + if self.need_where { + self.tokens.extend(quote!{ where }); + self.need_where = false; + } + if self.need_comma { + self.tokens.extend(quote!{ , }); } - self.0.extend(amendment); + self.tokens.extend(amendment); + self.need_comma = true; } fn into_tokens(self) -> TokenStream { - self.0 + self.tokens } } - let mut trait_bound_amendments = TraitBoundAmendments::new(); + let mut trait_bound_amendments = TraitBoundAmendments::new(generics.where_clause.as_ref()); for param in &generics.params { if let GenericParam::Type(param) = param { let param_ident = ¶m.ident; if type_param_bounds_contains(¶m.bounds, "StructOpt") { - trait_bound_amendments.extend(quote!{ #param_ident : ::structopt::StructOptInternal }); + trait_bound_amendments.add(quote!{ #param_ident : ::structopt::StructOptInternal }); } if type_param_bounds_contains(¶m.bounds, "FromStr") { - trait_bound_amendments.extend(quote!{ < #param_ident as ::std::str::FromStr>::Err : ::std::fmt::Display + ::std::fmt::Debug }); + trait_bound_amendments.add(quote!{ < #param_ident as ::std::str::FromStr>::Err : ::std::fmt::Display + ::std::fmt::Debug }); } } } @@ -805,10 +827,10 @@ fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, Type if let WherePredicate::Type(predicate) = predicate { let predicate_bounded_ty = &predicate.bounded_ty; if type_param_bounds_contains(&predicate.bounds, "StructOpt") { - trait_bound_amendments.extend(quote!{ #predicate_bounded_ty : ::structopt::StructOptInternal }); + trait_bound_amendments.add(quote!{ #predicate_bounded_ty : ::structopt::StructOptInternal }); } if type_param_bounds_contains(&predicate.bounds, "FromStr") { - trait_bound_amendments.extend(quote!{ < #predicate_bounded_ty as ::std::str::FromStr>::Err : ::std::fmt::Display + ::std::fmt::Debug }); + trait_bound_amendments.add(quote!{ < #predicate_bounded_ty as ::std::str::FromStr>::Err : ::std::fmt::Display + ::std::fmt::Debug }); } } } @@ -818,15 +840,7 @@ fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, Type let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let where_clause = if !trait_bound_amendments.is_empty() { - if let Some(where_clause) = where_clause { - quote!{ #where_clause, #trait_bound_amendments } - } else { - quote!{ where #trait_bound_amendments } - } - } else { - quote!{ #where_clause } - }; + let where_clause = quote!{ #where_clause #trait_bound_amendments }; (impl_generics, ty_generics, where_clause) } diff --git a/tests/generics.rs b/tests/generics.rs index a16d99d..890852b 100644 --- a/tests/generics.rs +++ b/tests/generics.rs @@ -119,3 +119,19 @@ fn generic_wo_trait_bound() { Opt::::from_iter([& "--answer", "42" ]) ) } + +#[test] +fn generic_where_clause_w_trailing_comma() { + + use std::str::FromStr; + + #[derive(StructOpt,PartialEq,Debug)] + struct Opt where T:FromStr, { + pub answer: T + } + + assert_eq!( + Opt::{answer:42}, + Opt::::from_iter(&[ "--answer", "42" ]) + ) +} From 4aebb8fd810dd031c924d5268e9339282bd3acd1 Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Thu, 24 Jun 2021 07:51:08 -0700 Subject: [PATCH 7/9] update change log --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 650afea..4734c9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# unreleased + +* Add support for [generics in derive](https://github.com/TeXitoi/structopt/issues/128) + # v0.3.21 (2020-11-30) * Fixed [another breakage](https://github.com/TeXitoi/structopt/issues/447) From 4413c821112cdbce08395dc6a6e0cf55b8748e7d Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Thu, 24 Jun 2021 09:21:15 -0700 Subject: [PATCH 8/9] add some basic documentation on generics support --- src/lib.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index fb4ad85..c8109d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,7 @@ //! - [Flattening subcommands](#flattening-subcommands) //! - [Flattening](#flattening) //! - [Custom string parsers](#custom-string-parsers) +//! - [Generics](#generics) //! //! //! @@ -1053,6 +1054,40 @@ //! In the `try_from_*` variants, the function will run twice on valid input: //! once to validate, and once to parse. Hence, make sure the function is //! side-effect-free. +//! +//! ## Generics +//! +//! Generic structs and enums can be used. They require explicit trait bounds +//! any generic types that will be used by the `StructOpt` derive macro. +//! +//! ``` +//! # use structopt::StructOpt; +//! use std::str::FromStr; +//! +//! // a struct with single custom argument +//! #[derive(StructOpt)] +//! struct GenericArgs { +//! generic_arg_1: String, +//! generic_arg_2: String, +//! custom_arg_1: T +//! } +//! ``` +//! +//! or +//! +//! ``` +//! # use structopt::StructOpt; +//! // a struct with multiple custom arguments in a substructure +//! #[derive(StructOpt)] +//! struct GenericArgs { +//! generic_arg_1: String, +//! generic_arg_2: String, +//! #[structopt(flatten)] +//! custom_args: T +//! } +//! ``` + + // those mains are for a reason #![allow(clippy::needless_doctest_main)] From 42d3d7e924f0321c071ea5a84e5e733763c8a3e7 Mon Sep 17 00:00:00 2001 From: Nathan Jeffords Date: Wed, 30 Jun 2021 17:56:02 -0700 Subject: [PATCH 9/9] remove automatic type bound amendment for `FromStr::Err` --- src/lib.rs | 8 +++++--- structopt-derive/src/lib.rs | 6 ------ tests/generics.rs | 8 ++++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c8109d3..b6e6b73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1058,15 +1058,17 @@ //! ## Generics //! //! Generic structs and enums can be used. They require explicit trait bounds -//! any generic types that will be used by the `StructOpt` derive macro. +//! on any generic types that will be used by the `StructOpt` derive macro. In +//! some cases, associated types will require additional bounds. See the usage +//! of `FromStr` below for an example of this. //! //! ``` //! # use structopt::StructOpt; -//! use std::str::FromStr; +//! use std::{fmt, str::FromStr}; //! //! // a struct with single custom argument //! #[derive(StructOpt)] -//! struct GenericArgs { +//! struct GenericArgs where ::Err: fmt::Display + fmt::Debug { //! generic_arg_1: String, //! generic_arg_2: String, //! custom_arg_1: T diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 948c134..cf4dbba 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -816,9 +816,6 @@ fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, Type if type_param_bounds_contains(¶m.bounds, "StructOpt") { trait_bound_amendments.add(quote!{ #param_ident : ::structopt::StructOptInternal }); } - if type_param_bounds_contains(¶m.bounds, "FromStr") { - trait_bound_amendments.add(quote!{ < #param_ident as ::std::str::FromStr>::Err : ::std::fmt::Display + ::std::fmt::Debug }); - } } } @@ -829,9 +826,6 @@ fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, Type if type_param_bounds_contains(&predicate.bounds, "StructOpt") { trait_bound_amendments.add(quote!{ #predicate_bounded_ty : ::structopt::StructOptInternal }); } - if type_param_bounds_contains(&predicate.bounds, "FromStr") { - trait_bound_amendments.add(quote!{ < #predicate_bounded_ty as ::std::str::FromStr>::Err : ::std::fmt::Display + ::std::fmt::Debug }); - } } } } diff --git a/tests/generics.rs b/tests/generics.rs index 890852b..896f98a 100644 --- a/tests/generics.rs +++ b/tests/generics.rs @@ -88,10 +88,10 @@ fn generic_enum_w_where_clause() { #[test] fn generic_w_fromstr_trait_bound() { - use std::str::FromStr; + use std::{fmt, str::FromStr}; #[derive(StructOpt,PartialEq,Debug)] - struct Opt where T:FromStr + struct Opt where T:FromStr, ::Err: fmt::Debug + fmt::Display { answer: T } @@ -123,10 +123,10 @@ fn generic_wo_trait_bound() { #[test] fn generic_where_clause_w_trailing_comma() { - use std::str::FromStr; + use std::{fmt, str::FromStr}; #[derive(StructOpt,PartialEq,Debug)] - struct Opt where T:FromStr, { + struct Opt where T:FromStr, ::Err: fmt::Debug + fmt::Display { pub answer: T }