From 1a8f8f7958ff3724049231a9865d1c85a08a96e9 Mon Sep 17 00:00:00 2001 From: CreepySkeleton Date: Mon, 12 Oct 2020 17:49:34 +0300 Subject: [PATCH 1/3] Fix #439 If memory serves, a patch like this was submitted to clap_derive, but not to structopt. Oops. --- structopt-derive/src/ty.rs | 21 +++++++++++++++++++++ tests/regressions.rs | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests/regressions.rs diff --git a/structopt-derive/src/ty.rs b/structopt-derive/src/ty.rs index 89d8b00a..ad3acd9d 100644 --- a/structopt-derive/src/ty.rs +++ b/structopt-derive/src/ty.rs @@ -63,6 +63,8 @@ fn subty_if(ty: &syn::Type, f: F) -> Option<&syn::Type> where F: FnOnce(&PathSegment) -> bool, { + let ty = strip_group(ty); + only_last_segment(ty) .filter(|segment| f(segment)) .and_then(|segment| { @@ -85,6 +87,8 @@ pub fn subty_if_name<'a>(ty: &'a syn::Type, name: &str) -> Option<&'a syn::Type> } pub fn is_simple_ty(ty: &syn::Type, name: &str) -> bool { + let ty = strip_group(ty); + only_last_segment(ty) .map(|segment| { if let PathArguments::None = segment.arguments { @@ -96,6 +100,23 @@ pub fn is_simple_ty(ty: &syn::Type, name: &str) -> bool { .unwrap_or(false) } +// If the struct is placed inside of a macro_rules! declaration, +// in some circumstances, the tokens inside will be enclosed +// in `proc_macro::Group` delimited by invisible `proc_macro::Delimiter::None`. +// +// In syn speak, this is encoded via `*::Group` variants. We don't really care about +// that, so let's just strip it. +// +// Details: https://doc.rust-lang.org/proc_macro/enum.Delimiter.html#variant.None +// See also: https://github.com/TeXitoi/structopt/issues/439 +fn strip_group(mut ty: &syn::Type) -> &syn::Type { + while let Type::Group(group) = ty { + ty = &*group.elem; + } + + ty +} + fn is_generic_ty(ty: &syn::Type, name: &str) -> bool { subty_if_name(ty, name).is_some() } diff --git a/tests/regressions.rs b/tests/regressions.rs new file mode 100644 index 00000000..9b1a93a4 --- /dev/null +++ b/tests/regressions.rs @@ -0,0 +1,25 @@ +use structopt::StructOpt; + +mod utils; +use utils::*; + +#[test] +fn invisible_group_issue_439() { + macro_rules! m { + ($bool:ty) => { + #[derive(Debug, StructOpt)] + struct Opts { + #[structopt(long = "x")] + x: $bool + } + }; + } + + m!(bool); + + let help = get_long_help::(); + + assert!(help.contains("--x")); + assert!(!help.contains("--x ")); + Opts::from_iter_safe(&["test", "--x"]).unwrap(); +} From 89dc8efc66fa783e22e79ed9814d0869477eaf5a Mon Sep 17 00:00:00 2001 From: CreepySkeleton Date: Mon, 12 Oct 2020 17:58:48 +0300 Subject: [PATCH 2/3] cargo fmt & clippy --- structopt-derive/src/lib.rs | 2 ++ tests/regressions.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 6cc415be..b99bd355 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -11,6 +11,8 @@ //! for the usage of `#[derive(StructOpt)]`. #![allow(clippy::large_enum_variant)] +// FIXME: remove when and if our MSRV hits 1.42 +#![allow(clippy::match_like_matches_macro)] #![forbid(unsafe_code)] extern crate proc_macro; diff --git a/tests/regressions.rs b/tests/regressions.rs index 9b1a93a4..876e7edd 100644 --- a/tests/regressions.rs +++ b/tests/regressions.rs @@ -10,7 +10,7 @@ fn invisible_group_issue_439() { #[derive(Debug, StructOpt)] struct Opts { #[structopt(long = "x")] - x: $bool + x: $bool, } }; } From 4d91947d28d46cfc044325e7743e6ff3f83c6050 Mon Sep 17 00:00:00 2001 From: CreepySkeleton Date: Mon, 12 Oct 2020 17:52:28 +0300 Subject: [PATCH 3/3] v0.3.20 --- CHANGELOG.md | 5 +++++ Cargo.toml | 4 ++-- structopt-derive/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e84e85a1..ccdde687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# v0.3.20 (2020-10-12) + +* Fixed [a breakage](https://github.com/TeXitoi/structopt/issues/439) + when the struct is placed inside a `macro_rules!` macro. + # v0.3.19 (2020-10-08) * Added [StructOpt::from_args_safe](https://docs.rs/structopt/0.3/structopt/trait.StructOpt.html#tymethod.from_args_safe) as a shortcut for `StructOpt::from_iter_safe(std::env::args_os())`. diff --git a/Cargo.toml b/Cargo.toml index 993e018f..7c4efaf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.3.19" +version = "0.3.20" edition = "2018" authors = ["Guillaume Pinot ", "others"] description = "Parse command line argument by defining a struct." @@ -28,7 +28,7 @@ travis-ci = { repository = "TeXitoi/structopt" } [dependencies] clap = { version = "2.33", default-features = false } -structopt-derive = { path = "structopt-derive", version = "=0.4.12" } +structopt-derive = { path = "structopt-derive", version = "=0.4.13" } lazy_static = "1.4.0" paw_dep = { version = "1", optional = true, package = "paw" } diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index 7f76ae65..176b34a7 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.4.12" +version = "0.4.13" edition = "2018" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate."