Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(derive)!: Compile-error on nested subcommands #2586

Merged
merged 2 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions clap_derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,6 @@ pub struct Attrs {
kind: Sp<Kind>,
}

/// Output for the gen_xxx() methods were we need more than a simple stream of tokens.
///
/// The output of a generation method is not only the stream of new tokens but also the attribute
/// information of the current element. These attribute information may contain valuable information
/// for any kind of child arguments.
pub type GenOutput = (TokenStream, Attrs);

impl Method {
pub fn new(name: Ident, args: TokenStream) -> Self {
Method { name, args }
Expand Down
112 changes: 95 additions & 17 deletions clap_derive/src/derives/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,98 @@
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
epage marked this conversation as resolved.
Show resolved Hide resolved
use proc_macro2::TokenStream;
use proc_macro_error::abort;
use quote::{quote, quote_spanned};
use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Field, Ident, Type};
pksunkara marked this conversation as resolved.
Show resolved Hide resolved

use crate::{
attrs::{Attrs, Kind, ParserKind},
attrs::{Attrs, Kind, Name, ParserKind, DEFAULT_CASING, DEFAULT_ENV_CASING},
dummies,
utils::{sub_type, subty_if_name, Sp, Ty},
};

use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_error::{abort, abort_call_site};
use quote::{quote, quote_spanned};
use syn::{
punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataStruct,
DeriveInput, Field, Fields, Type,
};

pub fn derive_args(input: &DeriveInput) -> TokenStream {
let ident = &input.ident;

dummies::args(ident);

match input.data {
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
..
}) => gen_for_struct(ident, &fields.named, &input.attrs),
Data::Struct(DataStruct {
fields: Fields::Unit,
..
}) => gen_for_struct(ident, &Punctuated::<Field, Comma>::new(), &input.attrs),
_ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"),
}
}

pub fn gen_for_struct(
struct_name: &Ident,
fields: &Punctuated<Field, Comma>,
attrs: &[Attribute],
) -> TokenStream {
let from_arg_matches = gen_from_arg_matches_for_struct(struct_name, fields, attrs);

let attrs = Attrs::from_struct(
Span::call_site(),
attrs,
Name::Derived(struct_name.clone()),
Sp::call_site(DEFAULT_CASING),
Sp::call_site(DEFAULT_ENV_CASING),
);
let app_var = Ident::new("app", Span::call_site());
let augmentation = gen_augment(fields, &app_var, &attrs, false);
let augmentation_update = gen_augment(fields, &app_var, &attrs, true);

quote! {
#from_arg_matches

#[allow(dead_code, unreachable_code, unused_variables)]
#[allow(
clippy::style,
clippy::complexity,
clippy::pedantic,
clippy::restriction,
clippy::perf,
clippy::deprecated,
clippy::nursery,
clippy::cargo
)]
#[deny(clippy::correctness)]
impl clap::Args for #struct_name {
fn augment_args<'b>(#app_var: clap::App<'b>) -> clap::App<'b> {
#augmentation
}
fn augment_args_for_update<'b>(#app_var: clap::App<'b>) -> clap::App<'b> {
#augmentation_update
}
}
}
}

pub fn gen_from_arg_matches_for_struct(
struct_name: &Ident,
fields: &Punctuated<Field, Comma>,
parent_attribute: &Attrs,
attrs: &[Attribute],
) -> TokenStream {
let constructor = gen_constructor(fields, parent_attribute);
let updater = gen_updater(fields, parent_attribute, true);
let attrs = Attrs::from_struct(
Span::call_site(),
attrs,
Name::Derived(struct_name.clone()),
Sp::call_site(DEFAULT_CASING),
Sp::call_site(DEFAULT_ENV_CASING),
);

let constructor = gen_constructor(fields, &attrs);
let updater = gen_updater(fields, &attrs, true);

quote! {
#[allow(dead_code, unreachable_code, unused_variables)]
Expand All @@ -43,8 +118,9 @@ pub fn gen_from_arg_matches_for_struct(
)]
#[deny(clippy::correctness)]
impl clap::FromArgMatches for #struct_name {
fn from_arg_matches(arg_matches: &clap::ArgMatches) -> Self {
#struct_name #constructor
fn from_arg_matches(arg_matches: &clap::ArgMatches) -> Option<Self> {
let v = #struct_name #constructor;
Some(v)
}

fn update_from_arg_matches(&mut self, arg_matches: &clap::ArgMatches) {
Expand Down Expand Up @@ -123,7 +199,7 @@ pub fn gen_augment(
Kind::Flatten => {
let ty = &field.ty;
Some(quote_spanned! { kind.span()=>
let #app_var = <#ty as clap::IntoApp>::augment_clap(#app_var);
let #app_var = <#ty as clap::Args>::augment_args(#app_var);
})
}
Kind::Arg(ty) => {
Expand Down Expand Up @@ -289,22 +365,24 @@ pub fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Att
};
quote_spanned! { kind.span()=>
#field_name: {
<#subcmd_type as clap::Subcommand>::from_subcommand(#arg_matches.subcommand())
<#subcmd_type as clap::FromArgMatches>::from_arg_matches(#arg_matches)
#unwrapper
}
}
}

Kind::Flatten => quote_spanned! { kind.span()=>
#field_name: clap::FromArgMatches::from_arg_matches(#arg_matches)
#field_name: clap::FromArgMatches::from_arg_matches(#arg_matches).unwrap()
},

Kind::Skip(val) => match val {
None => quote_spanned!(kind.span()=> #field_name: Default::default()),
Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
},

Kind::Arg(ty) | Kind::FromGlobal(ty) => gen_parsers(&attrs, ty, field_name, field, None),
Kind::Arg(ty) | Kind::FromGlobal(ty) => {
gen_parsers(&attrs, ty, field_name, field, None)
}
}
});

Expand Down Expand Up @@ -350,16 +428,16 @@ pub fn gen_updater(
};

let updater = quote_spanned! { ty.span()=>
<#subcmd_type as clap::Subcommand>::update_from_subcommand(#field_name, #arg_matches.subcommand());
<#subcmd_type as clap::FromArgMatches>::update_from_arg_matches(#field_name, #arg_matches);
};

let updater = match **ty {
Ty::Option => quote_spanned! { kind.span()=>
if let Some(#field_name) = #field_name.as_mut() {
#updater
} else {
*#field_name = <#subcmd_type as clap::Subcommand>::from_subcommand(
#arg_matches.subcommand()
*#field_name = <#subcmd_type as clap::FromArgMatches>::from_arg_matches(
#arg_matches
)
}
},
Expand Down
8 changes: 3 additions & 5 deletions clap_derive/src/derives/clap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,25 @@ fn gen_for_struct(
fields: &Punctuated<Field, Comma>,
attrs: &[Attribute],
) -> TokenStream {
let (into_app, attrs) = into_app::gen_for_struct(name, fields, attrs);
let from_arg_matches = args::gen_from_arg_matches_for_struct(name, fields, &attrs);
let into_app = into_app::gen_for_struct(name, attrs);
let args = args::gen_for_struct(name, fields, attrs);

quote! {
impl clap::Clap for #name {}

#into_app
#from_arg_matches
#args
}
}

fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
let into_app = into_app::gen_for_enum(name, attrs);
let from_arg_matches = subcommand::gen_from_arg_matches_for_enum(name);
let subcommand = subcommand::gen_for_enum(name, attrs, e);

quote! {
impl clap::Clap for #name {}

#into_app
#from_arg_matches
#subcommand
}
}
91 changes: 26 additions & 65 deletions clap_derive/src/derives/into_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,10 @@ use std::env;
use proc_macro2::{Span, TokenStream};
use proc_macro_error::abort_call_site;
use quote::quote;
use syn::{
punctuated::Punctuated, token::Comma, Attribute, Data, DataStruct, DeriveInput, Field, Fields,
Ident,
};
use syn::{Attribute, Data, DataStruct, DeriveInput, Fields, Ident};

use crate::{
attrs::{Attrs, GenOutput, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
derives::args,
attrs::{Attrs, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
dummies,
utils::Sp,
};
Expand All @@ -36,25 +32,29 @@ pub fn derive_into_app(input: &DeriveInput) -> TokenStream {

match input.data {
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
fields: Fields::Named(_),
..
}) => gen_for_struct(ident, &fields.named, &input.attrs).0,
}) => gen_for_struct(ident, &input.attrs),
Data::Struct(DataStruct {
fields: Fields::Unit,
..
}) => gen_for_struct(ident, &Punctuated::<Field, Comma>::new(), &input.attrs).0,
}) => gen_for_struct(ident, &input.attrs),
Data::Enum(_) => gen_for_enum(ident, &input.attrs),
_ => abort_call_site!("`#[derive(IntoApp)]` only supports non-tuple structs and enums"),
}
}

pub fn gen_for_struct(
struct_name: &Ident,
fields: &Punctuated<Field, Comma>,
attrs: &[Attribute],
) -> GenOutput {
let (into_app, attrs) = gen_into_app_fn(attrs);
let augment_clap = gen_augment_clap_fn(fields, &attrs);
pub fn gen_for_struct(struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();

let attrs = Attrs::from_struct(
Span::call_site(),
attrs,
Name::Assigned(quote!(#app_name)),
Sp::call_site(DEFAULT_CASING),
Sp::call_site(DEFAULT_ENV_CASING),
);
let name = attrs.cased_name();

let tokens = quote! {
#[allow(dead_code, unreachable_code, unused_variables)]
Expand All @@ -70,12 +70,19 @@ pub fn gen_for_struct(
)]
#[deny(clippy::correctness)]
impl clap::IntoApp for #struct_name {
#into_app
#augment_clap
fn into_app<'b>() -> clap::App<'b> {
let app = clap::App::new(#name);
<#struct_name as clap::Args>::augment_args(app)
}

fn into_app_for_update<'b>() -> clap::App<'b> {
let app = clap::App::new(#name);
<#struct_name as clap::Args>::augment_args_for_update(app)
}
}
};

(tokens, attrs)
tokens
}

pub fn gen_for_enum(enum_name: &Ident, attrs: &[Attribute]) -> TokenStream {
Expand Down Expand Up @@ -107,59 +114,13 @@ pub fn gen_for_enum(enum_name: &Ident, attrs: &[Attribute]) -> TokenStream {
fn into_app<'b>() -> clap::App<'b> {
let app = clap::App::new(#name)
.setting(clap::AppSettings::SubcommandRequiredElseHelp);
<#enum_name as clap::IntoApp>::augment_clap(app)
}

fn augment_clap<'b>(app: clap::App<'b>) -> clap::App<'b> {
<#enum_name as clap::Subcommand>::augment_subcommands(app)
}

fn into_app_for_update<'b>() -> clap::App<'b> {
let app = clap::App::new(#name);
<#enum_name as clap::IntoApp>::augment_clap_for_update(app)
}

fn augment_clap_for_update<'b>(app: clap::App<'b>) -> clap::App<'b> {
<#enum_name as clap::Subcommand>::augment_subcommands_for_update(app)
}
}
}
}

fn gen_into_app_fn(attrs: &[Attribute]) -> GenOutput {
let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();

let attrs = Attrs::from_struct(
Span::call_site(),
attrs,
Name::Assigned(quote!(#app_name)),
Sp::call_site(DEFAULT_CASING),
Sp::call_site(DEFAULT_ENV_CASING),
);
let name = attrs.cased_name();

let tokens = quote! {
fn into_app<'b>() -> clap::App<'b> {
Self::augment_clap(clap::App::new(#name))
}
fn into_app_for_update<'b>() -> clap::App<'b> {
Self::augment_clap_for_update(clap::App::new(#name))
}
};

(tokens, attrs)
}

fn gen_augment_clap_fn(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream {
let app_var = Ident::new("app", Span::call_site());
let augmentation = args::gen_augment(fields, &app_var, parent_attribute, false);
let augmentation_update = args::gen_augment(fields, &app_var, parent_attribute, true);
quote! {
fn augment_clap<'b>(#app_var: clap::App<'b>) -> clap::App<'b> {
#augmentation
}
fn augment_clap_for_update<'b>(#app_var: clap::App<'b>) -> clap::App<'b> {
#augmentation_update
}
}
}
2 changes: 1 addition & 1 deletion clap_derive/src/derives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ mod subcommand;

pub use self::clap::derive_clap;
pub use arg_enum::derive_arg_enum;
// pub use from_arg_matches::derive_from_arg_matches;
pub use args::derive_args;
pub use into_app::derive_into_app;
pub use subcommand::derive_subcommand;
Loading