Fix hygiene of arg_matches parameter

In the `Clap` derive macro, a function parameter named `arg_matches` is
generated using `quote!` - as a result, this parameter ends up with
call-site hygiene. However, `arg_matches` is written literally within
several `quote_spanned!` blocks, which generate an `arg_matches` token
with the hygiene of whatever span was passed to `quote_spanned!`.

If these two hygienes are different (for example, if the user invokes
the derive macro from a `macro_rules!` macro), then a usage of
`arg_matches` may not resolve to the `arg_matches` parameter definition.

This commit changes the generation of `arg_matches` identifiers to
always use `quote!`, ensuring that they will always be considered the
'same' identifier by Rust.
This commit is contained in:
Aaron Hill 2020-11-28 12:21:33 -05:00
parent 3c93f276b5
commit 6dc8353fe2
No known key found for this signature in database
GPG key ID: B4087E510E98B164

View file

@ -131,16 +131,20 @@ fn gen_parsers(
let flag = *attrs.parser().kind == ParserKind::FromFlag; let flag = *attrs.parser().kind == ParserKind::FromFlag;
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
let name = attrs.cased_name(); let name = attrs.cased_name();
// Use `quote!` to give this identifier the same hygiene
// as the `arg_matches` parameter definition. This
// allows us to refer to `arg_matches` within a `quote_spanned` block
let arg_matches = quote! { arg_matches };
let field_value = match **ty { let field_value = match **ty {
Ty::Bool => { Ty::Bool => {
if update.is_some() { if update.is_some() {
quote_spanned! { ty.span()=> quote_spanned! { ty.span()=>
*#field_name || arg_matches.is_present(#name) *#field_name || #arg_matches.is_present(#name)
} }
} else { } else {
quote_spanned! { ty.span()=> quote_spanned! { ty.span()=>
arg_matches.is_present(#name) #arg_matches.is_present(#name)
} }
} }
} }
@ -153,22 +157,22 @@ fn gen_parsers(
} }
quote_spanned! { ty.span()=> quote_spanned! { ty.span()=>
arg_matches.#value_of(#name) #arg_matches.#value_of(#name)
.map(#parse) .map(#parse)
} }
} }
Ty::OptionOption => quote_spanned! { ty.span()=> Ty::OptionOption => quote_spanned! { ty.span()=>
if arg_matches.is_present(#name) { if #arg_matches.is_present(#name) {
Some(arg_matches.#value_of(#name).map(#parse)) Some(#arg_matches.#value_of(#name).map(#parse))
} else { } else {
None None
} }
}, },
Ty::OptionVec => quote_spanned! { ty.span()=> Ty::OptionVec => quote_spanned! { ty.span()=>
if arg_matches.is_present(#name) { if #arg_matches.is_present(#name) {
Some(arg_matches.#values_of(#name) Some(#arg_matches.#values_of(#name)
.map(|v| v.map(#parse).collect()) .map(|v| v.map(#parse).collect())
.unwrap_or_else(Vec::new)) .unwrap_or_else(Vec::new))
} else { } else {
@ -184,18 +188,18 @@ fn gen_parsers(
} }
quote_spanned! { ty.span()=> quote_spanned! { ty.span()=>
arg_matches.#values_of(#name) #arg_matches.#values_of(#name)
.map(|v| v.map(#parse).collect()) .map(|v| v.map(#parse).collect())
.unwrap_or_else(Vec::new) .unwrap_or_else(Vec::new)
} }
} }
Ty::Other if occurrences => quote_spanned! { ty.span()=> Ty::Other if occurrences => quote_spanned! { ty.span()=>
#parse(arg_matches.#value_of(#name)) #parse(#arg_matches.#value_of(#name))
}, },
Ty::Other if flag => quote_spanned! { ty.span()=> Ty::Other if flag => quote_spanned! { ty.span()=>
#parse(arg_matches.is_present(#name)) #parse(#arg_matches.is_present(#name))
}, },
Ty::Other => { Ty::Other => {
@ -204,7 +208,7 @@ fn gen_parsers(
} }
quote_spanned! { ty.span()=> quote_spanned! { ty.span()=>
arg_matches.#value_of(#name) #arg_matches.#value_of(#name)
.map(#parse) .map(#parse)
.unwrap() .unwrap()
} }
@ -213,7 +217,7 @@ fn gen_parsers(
if let Some(access) = update { if let Some(access) = update {
quote_spanned! { field.span()=> quote_spanned! { field.span()=>
if arg_matches.is_present(#name) { if #arg_matches.is_present(#name) {
#access #access
*#field_name = #field_value *#field_name = #field_value
} }
@ -232,6 +236,7 @@ pub fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Att
); );
let field_name = field.ident.as_ref().unwrap(); let field_name = field.ident.as_ref().unwrap();
let kind = attrs.kind(); let kind = attrs.kind();
let arg_matches = quote! { arg_matches };
match &*kind { match &*kind {
Kind::ExternalSubcommand => { Kind::ExternalSubcommand => {
abort! { kind.span(), abort! { kind.span(),
@ -249,14 +254,14 @@ pub fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Att
}; };
quote_spanned! { kind.span()=> quote_spanned! { kind.span()=>
#field_name: { #field_name: {
<#subcmd_type as ::clap::Subcommand>::from_subcommand(arg_matches.subcommand()) <#subcmd_type as ::clap::Subcommand>::from_subcommand(#arg_matches.subcommand())
#unwrapper #unwrapper
} }
} }
} }
Kind::Flatten => quote_spanned! { kind.span()=> Kind::Flatten => quote_spanned! { kind.span()=>
#field_name: ::clap::FromArgMatches::from_arg_matches(arg_matches) #field_name: ::clap::FromArgMatches::from_arg_matches(#arg_matches)
}, },
Kind::Skip(val) => match val { Kind::Skip(val) => match val {
@ -295,6 +300,7 @@ pub fn gen_updater(
} else { } else {
quote!() quote!()
}; };
let arg_matches = quote! { arg_matches };
match &*kind { match &*kind {
Kind::ExternalSubcommand => { Kind::ExternalSubcommand => {
@ -329,7 +335,7 @@ pub fn gen_updater(
quote_spanned! { kind.span()=> quote_spanned! { kind.span()=>
{ {
let subcmd = arg_matches.subcommand(); let subcmd = #arg_matches.subcommand();
#access #access
#updater #updater
} }
@ -338,7 +344,7 @@ pub fn gen_updater(
Kind::Flatten => quote_spanned! { kind.span()=> { Kind::Flatten => quote_spanned! { kind.span()=> {
#access #access
::clap::FromArgMatches::update_from_arg_matches(#field_name, arg_matches); ::clap::FromArgMatches::update_from_arg_matches(#field_name, #arg_matches);
} }
}, },