Skip to content

Commit 4bdde69

Browse files
authored
add basic support for generic structs and enums (#483)
1 parent d16cfd2 commit 4bdde69

File tree

4 files changed

+286
-14
lines changed

4 files changed

+286
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# unreleased
2+
3+
* Add support for [generics in derive](https://github.com/TeXitoi/structopt/issues/128)
4+
15
# v0.3.21 (2020-11-30)
26

37
* Fixed [another breakage](https://github.com/TeXitoi/structopt/issues/447)

src/lib.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
//! - [Flattening subcommands](#flattening-subcommands)
5353
//! - [Flattening](#flattening)
5454
//! - [Custom string parsers](#custom-string-parsers)
55+
//! - [Generics](#generics)
5556
//!
5657
//!
5758
//!
@@ -1053,6 +1054,42 @@
10531054
//! In the `try_from_*` variants, the function will run twice on valid input:
10541055
//! once to validate, and once to parse. Hence, make sure the function is
10551056
//! side-effect-free.
1057+
//!
1058+
//! ## Generics
1059+
//!
1060+
//! Generic structs and enums can be used. They require explicit trait bounds
1061+
//! on any generic types that will be used by the `StructOpt` derive macro. In
1062+
//! some cases, associated types will require additional bounds. See the usage
1063+
//! of `FromStr` below for an example of this.
1064+
//!
1065+
//! ```
1066+
//! # use structopt::StructOpt;
1067+
//! use std::{fmt, str::FromStr};
1068+
//!
1069+
//! // a struct with single custom argument
1070+
//! #[derive(StructOpt)]
1071+
//! struct GenericArgs<T:FromStr> where <T as FromStr>::Err: fmt::Display + fmt::Debug {
1072+
//! generic_arg_1: String,
1073+
//! generic_arg_2: String,
1074+
//! custom_arg_1: T
1075+
//! }
1076+
//! ```
1077+
//!
1078+
//! or
1079+
//!
1080+
//! ```
1081+
//! # use structopt::StructOpt;
1082+
//! // a struct with multiple custom arguments in a substructure
1083+
//! #[derive(StructOpt)]
1084+
//! struct GenericArgs<T:StructOpt> {
1085+
//! generic_arg_1: String,
1086+
//! generic_arg_2: String,
1087+
//! #[structopt(flatten)]
1088+
//! custom_args: T
1089+
//! }
1090+
//! ```
1091+
1092+
10561093

10571094
// those mains are for a reason
10581095
#![allow(clippy::needless_doctest_main)]

structopt-derive/src/lib.rs

Lines changed: 108 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -559,10 +559,10 @@ fn gen_augment_clap_enum(
559559
}
560560
}
561561

562-
fn gen_from_clap_enum(name: &Ident) -> TokenStream {
562+
fn gen_from_clap_enum() -> TokenStream {
563563
quote! {
564564
fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
565-
<#name as ::structopt::StructOptInternal>::from_subcommand(matches.subcommand())
565+
<Self as ::structopt::StructOptInternal>::from_subcommand(matches.subcommand())
566566
.expect("structopt misuse: You likely tried to #[flatten] a struct \
567567
that contains #[subcommand]. This is forbidden.")
568568
}
@@ -736,9 +736,9 @@ fn gen_from_subcommand(
736736
}
737737

738738
#[cfg(feature = "paw")]
739-
fn gen_paw_impl(name: &Ident) -> TokenStream {
739+
fn gen_paw_impl(impl_generics: &ImplGenerics, name: &Ident, ty_generics: &TypeGenerics, where_clause: &TokenStream) -> TokenStream {
740740
quote! {
741-
impl ::structopt::paw::ParseArgs for #name {
741+
impl #impl_generics ::structopt::paw::ParseArgs for #name #ty_generics #where_clause {
742742
type Error = std::io::Error;
743743

744744
fn parse_args() -> std::result::Result<Self, Self::Error> {
@@ -748,19 +748,109 @@ fn gen_paw_impl(name: &Ident) -> TokenStream {
748748
}
749749
}
750750
#[cfg(not(feature = "paw"))]
751-
fn gen_paw_impl(_: &Ident) -> TokenStream {
751+
fn gen_paw_impl(_: &ImplGenerics, _: &Ident, _: &TypeGenerics, _: &TokenStream) -> TokenStream {
752752
TokenStream::new()
753753
}
754754

755+
fn split_structopt_generics_for_impl(generics: &Generics) -> (ImplGenerics, TypeGenerics, TokenStream) {
756+
use syn::{ token::Add, TypeParamBound::Trait };
757+
758+
fn path_ends_with(path: &Path, ident: &str) -> bool {
759+
path.segments.last().unwrap().ident == ident
760+
}
761+
762+
fn type_param_bounds_contains(bounds: &Punctuated<TypeParamBound, Add>, ident: &str) -> bool {
763+
for bound in bounds {
764+
if let Trait(bound) = bound {
765+
if path_ends_with(&bound.path, ident) {
766+
return true;
767+
}
768+
}
769+
}
770+
return false;
771+
}
772+
773+
struct TraitBoundAmendments{
774+
tokens: TokenStream,
775+
need_where: bool,
776+
need_comma: bool,
777+
}
778+
779+
impl TraitBoundAmendments {
780+
fn new(where_clause: Option<&WhereClause>) -> Self {
781+
let tokens = TokenStream::new();
782+
let (need_where,need_comma) = if let Some(where_clause) = where_clause {
783+
if where_clause.predicates.trailing_punct() {
784+
(false, false)
785+
} else {
786+
(false, true)
787+
}
788+
} else {
789+
(true, false)
790+
};
791+
Self{tokens, need_where, need_comma}
792+
}
793+
794+
fn add(&mut self, amendment: TokenStream) {
795+
if self.need_where {
796+
self.tokens.extend(quote!{ where });
797+
self.need_where = false;
798+
}
799+
if self.need_comma {
800+
self.tokens.extend(quote!{ , });
801+
}
802+
self.tokens.extend(amendment);
803+
self.need_comma = true;
804+
}
805+
806+
fn into_tokens(self) -> TokenStream {
807+
self.tokens
808+
}
809+
}
810+
811+
let mut trait_bound_amendments = TraitBoundAmendments::new(generics.where_clause.as_ref());
812+
813+
for param in &generics.params {
814+
if let GenericParam::Type(param) = param {
815+
let param_ident = &param.ident;
816+
if type_param_bounds_contains(&param.bounds, "StructOpt") {
817+
trait_bound_amendments.add(quote!{ #param_ident : ::structopt::StructOptInternal });
818+
}
819+
}
820+
}
821+
822+
if let Some(where_clause) = &generics.where_clause {
823+
for predicate in &where_clause.predicates {
824+
if let WherePredicate::Type(predicate) = predicate {
825+
let predicate_bounded_ty = &predicate.bounded_ty;
826+
if type_param_bounds_contains(&predicate.bounds, "StructOpt") {
827+
trait_bound_amendments.add(quote!{ #predicate_bounded_ty : ::structopt::StructOptInternal });
828+
}
829+
}
830+
}
831+
}
832+
833+
let trait_bound_amendments = trait_bound_amendments.into_tokens();
834+
835+
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
836+
837+
let where_clause = quote!{ #where_clause #trait_bound_amendments };
838+
839+
(impl_generics, ty_generics, where_clause)
840+
}
841+
755842
fn impl_structopt_for_struct(
756843
name: &Ident,
757844
fields: &Punctuated<Field, Comma>,
758845
attrs: &[Attribute],
846+
generics: &Generics,
759847
) -> TokenStream {
848+
let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics);
849+
760850
let basic_clap_app_gen = gen_clap_struct(attrs);
761851
let augment_clap = gen_augment_clap(fields, &basic_clap_app_gen.attrs);
762852
let from_clap = gen_from_clap(name, fields, &basic_clap_app_gen.attrs);
763-
let paw_impl = gen_paw_impl(name);
853+
let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause);
764854

765855
let clap_tokens = basic_clap_app_gen.tokens;
766856
quote! {
@@ -778,7 +868,7 @@ fn impl_structopt_for_struct(
778868
)]
779869
#[deny(clippy::correctness)]
780870
#[allow(dead_code, unreachable_code)]
781-
impl ::structopt::StructOpt for #name {
871+
impl #impl_generics ::structopt::StructOpt for #name #ty_generics #where_clause {
782872
#clap_tokens
783873
#from_clap
784874
}
@@ -797,7 +887,7 @@ fn impl_structopt_for_struct(
797887
)]
798888
#[deny(clippy::correctness)]
799889
#[allow(dead_code, unreachable_code)]
800-
impl ::structopt::StructOptInternal for #name {
890+
impl #impl_generics ::structopt::StructOptInternal for #name #ty_generics #where_clause {
801891
#augment_clap
802892
fn is_subcommand() -> bool { false }
803893
}
@@ -810,15 +900,19 @@ fn impl_structopt_for_enum(
810900
name: &Ident,
811901
variants: &Punctuated<Variant, Comma>,
812902
attrs: &[Attribute],
903+
generics: &Generics,
813904
) -> TokenStream {
905+
906+
let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics);
907+
814908
let basic_clap_app_gen = gen_clap_enum(attrs);
815909
let clap_tokens = basic_clap_app_gen.tokens;
816910
let attrs = basic_clap_app_gen.attrs;
817911

818912
let augment_clap = gen_augment_clap_enum(variants, &attrs);
819-
let from_clap = gen_from_clap_enum(name);
913+
let from_clap = gen_from_clap_enum();
820914
let from_subcommand = gen_from_subcommand(name, variants, &attrs);
821-
let paw_impl = gen_paw_impl(name);
915+
let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause);
822916

823917
quote! {
824918
#[allow(unknown_lints)]
@@ -834,7 +928,7 @@ fn impl_structopt_for_enum(
834928
clippy::cargo
835929
)]
836930
#[deny(clippy::correctness)]
837-
impl ::structopt::StructOpt for #name {
931+
impl #impl_generics ::structopt::StructOpt for #name #ty_generics #where_clause {
838932
#clap_tokens
839933
#from_clap
840934
}
@@ -853,7 +947,7 @@ fn impl_structopt_for_enum(
853947
)]
854948
#[deny(clippy::correctness)]
855949
#[allow(dead_code, unreachable_code)]
856-
impl ::structopt::StructOptInternal for #name {
950+
impl #impl_generics ::structopt::StructOptInternal for #name #ty_generics #where_clause {
857951
#augment_clap
858952
#from_subcommand
859953
fn is_subcommand() -> bool { true }
@@ -885,8 +979,8 @@ fn impl_structopt(input: &DeriveInput) -> TokenStream {
885979
Struct(DataStruct {
886980
fields: syn::Fields::Named(ref fields),
887981
..
888-
}) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs),
889-
Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs),
982+
}) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs, &input.generics),
983+
Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs, &input.generics),
890984
_ => abort_call_site!("structopt only supports non-tuple structs and enums"),
891985
}
892986
}

0 commit comments

Comments
 (0)