Skip to content

Commit aadb552

Browse files
committed
Work-around various issues with nightly-2020-05-30
Tested with: - nightly-2020-05-30 - nightly-2020-04-30 - 1.40.0 - 1.37.0 This commit fixes non-trivial patterns causing an error saying "unexpected token". This is done by parenthesizing `$p`. This might have something to do with an input pattern being wrapped by a `None`-delimited group and I suspect this might have been overlooked by the crater run <rust-lang/rust#72622>, but I'm not sure. This commit also implements a work-around for the issue <dtolnay/proc-macro-hack#46> by capturing the input expression by `$($in:tt)*`. The first work-around might have revealed this issue.
1 parent 3cd45e7 commit aadb552

File tree

2 files changed

+120
-91
lines changed

2 files changed

+120
-91
lines changed

inner/src/lib.rs

+76-60
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,21 @@ use syn::{
88
parse::{Parse, ParseStream},
99
parse_macro_input,
1010
spanned::Spanned,
11-
Error, Ident, LitStr, Pat, PatIdent, Result,
11+
Error, Expr, Ident, LitStr, Pat, PatIdent, Result, Token,
1212
};
1313

1414
struct MacroInput {
1515
use_std: bool,
1616
pat: Pat,
17+
in_value: Expr,
1718
}
1819

1920
impl Parse for MacroInput {
2021
fn parse(input: ParseStream) -> Result<Self> {
2122
let std_mode: Ident = input.parse()?;
2223
let pat = input.parse()?;
24+
input.parse::<Token![=]>()?;
25+
let in_value = input.parse()?;
2326

2427
Ok(Self {
2528
use_std: if std_mode == "std" {
@@ -30,79 +33,92 @@ impl Parse for MacroInput {
3033
return Err(Error::new_spanned(std_mode, ""));
3134
},
3235
pat,
36+
in_value,
3337
})
3438
}
3539
}
3640

3741
#[proc_macro_hack::proc_macro_hack]
3842
#[proc_macro_error::proc_macro_error]
39-
pub fn collect_captures(input: TokenStream) -> TokenStream {
40-
let MacroInput { use_std, pat } = parse_macro_input!(input);
43+
pub fn implicit_try_match_inner(input: TokenStream) -> TokenStream {
44+
let MacroInput {
45+
use_std,
46+
pat,
47+
in_value,
48+
} = parse_macro_input!(input);
4149

4250
let mut idents = Vec::new();
4351
collect_pat_ident(&pat, &mut idents);
4452

45-
// Check if bound variables are all like `_0`, `_1`, in which case
46-
// collect them in a tuple
47-
if let Some(tokens) = check_tuple_captures(&idents) {
48-
return tokens.into();
49-
}
50-
51-
// Decide what to do based on the number of bound variables
52-
let output = match &idents[..] {
53-
[] => {
54-
quote! {()}
55-
}
56-
[single] => {
57-
quote! {#single}
58-
}
59-
multi => {
60-
// `var1`, `var2`, ...
61-
let idents: Vec<_> = multi.iter().map(|p| p.ident.clone()).collect();
62-
63-
// `_M_0`, `_M_1`, ...
64-
let ty_params: Vec<_> = (0..idents.len())
65-
.map(|i| Ident::new(&format!("_M_{}", i), Span::call_site()))
66-
.collect();
67-
68-
// `"var1"`, `"var2"`, ...
69-
let ident_strs: Vec<_> = idents
70-
.iter()
71-
.map(|i| LitStr::new(&format!("{}", i), i.span()))
72-
.collect();
73-
74-
let ty_name = Ident::new("__Match", Span::call_site());
75-
76-
let debug_impl = if use_std {
77-
let fmt = quote! { ::std::fmt };
78-
quote! {
79-
impl<#(#ty_params),*> #fmt::Debug for #ty_name<#(#ty_params),*>
80-
where
81-
#(#ty_params: #fmt::Debug),*
82-
{
83-
fn fmt(&self, f: &mut #fmt::Formatter<'_>) -> #fmt::Result {
84-
f.debug_struct("<anonymous>")
85-
#(.field(#ident_strs, &self.#idents))*
86-
.finish()
87-
}
88-
}
53+
let success_output =
54+
// Check if bound variables are all like `_0`, `_1`, in which case
55+
// collect them in a tuple
56+
if let Some(tokens) = check_tuple_captures(&idents) {
57+
tokens
58+
} else {
59+
// Decide what to do based on the number of bound variables
60+
match &idents[..] {
61+
[] => {
62+
quote! {()}
8963
}
90-
} else {
91-
quote! {}
92-
};
93-
94-
quote! {{
95-
#[derive(Clone, Copy)]
96-
struct #ty_name<#(#ty_params),*> {
97-
#(
98-
#idents: #ty_params
99-
),*
64+
[single] => {
65+
quote! {#single}
10066
}
67+
multi => {
68+
// `var1`, `var2`, ...
69+
let idents: Vec<_> = multi.iter().map(|p| p.ident.clone()).collect();
70+
71+
// `_M_0`, `_M_1`, ...
72+
let ty_params: Vec<_> = (0..idents.len())
73+
.map(|i| Ident::new(&format!("_M_{}", i), Span::call_site()))
74+
.collect();
75+
76+
// `"var1"`, `"var2"`, ...
77+
let ident_strs: Vec<_> = idents
78+
.iter()
79+
.map(|i| LitStr::new(&format!("{}", i), i.span()))
80+
.collect();
81+
82+
let ty_name = Ident::new("__Match", Span::call_site());
83+
84+
let debug_impl = if use_std {
85+
let fmt = quote! { ::std::fmt };
86+
quote! {
87+
impl<#(#ty_params),*> #fmt::Debug for #ty_name<#(#ty_params),*>
88+
where
89+
#(#ty_params: #fmt::Debug),*
90+
{
91+
fn fmt(&self, f: &mut #fmt::Formatter<'_>) -> #fmt::Result {
92+
f.debug_struct("<anonymous>")
93+
#(.field(#ident_strs, &self.#idents))*
94+
.finish()
95+
}
96+
}
97+
}
98+
} else {
99+
quote! {}
100+
};
101+
102+
quote! {{
103+
#[derive(Clone, Copy)]
104+
struct #ty_name<#(#ty_params),*> {
105+
#(
106+
#idents: #ty_params
107+
),*
108+
}
101109

102-
#debug_impl
110+
#debug_impl
111+
112+
#ty_name { #(#idents),* }
113+
}}
114+
}
115+
}
116+
};
103117

104-
#ty_name { #(#idents),* }
105-
}}
118+
let output = quote! {
119+
match #in_value {
120+
#pat => ::core::result::Result::Ok(#success_output),
121+
in_value => ::core::result::Result::Err(in_value),
106122
}
107123
};
108124

src/lib.rs

+44-31
Original file line numberDiff line numberDiff line change
@@ -104,56 +104,69 @@
104104
/// See [the crate-level documentation](index.html) for examples.
105105
#[macro_export]
106106
macro_rules! try_match {
107-
($p:pat = $in:expr) => {
108-
match $in {
109-
$p => ::core::result::Result::Ok($crate::collect_captures_outer!($p)),
110-
in_value => ::core::result::Result::Err(in_value),
111-
}
112-
};
113107
($p:pat = $in:expr => $out:expr) => {
114108
match $in {
115109
$p => ::core::result::Result::Ok($out),
116110
in_value => ::core::result::Result::Err(in_value),
117111
}
118112
};
119-
}
120113

121-
/// Given a pattern, creates an expression that includes bound variables.
122-
///
123-
/// - If there are no bound variables, it generates `()`.
124-
/// - If there is exactly one bound variables `var`, it generates `var`.
125-
/// - If there are multiple bound variables `var1, var2, ...`, it generates
126-
/// `SomeType { var1, var2 }`.
127-
///
128-
#[cfg(feature = "std")]
129-
#[doc(hidden)]
130-
#[macro_export]
131-
macro_rules! collect_captures_outer {
132-
($p:pat) => { $crate::collect_captures!(std $p) };
114+
// Using `$($in:tt)*` in place of `$in:expr` is a work-around for
115+
// <https://github.com/dtolnay/proc-macro-hack/issues/46>, which is
116+
// originally caused by <https://github.com/rust-lang/rust/issues/43081>
117+
($p:pat = $($in:tt)*) => {
118+
// `$p` needs to be parenthesized for it to work on nightly-2020-05-30
119+
// and syn 1.0.29
120+
$crate::implicit_try_match!(($p) = $($in)*)
121+
};
133122
}
134123

135-
#[cfg(not(feature = "std"))]
136-
#[doc(hidden)]
124+
#[cfg(all(feature = "std", feature = "implicit_map"))]
137125
#[macro_export]
138-
macro_rules! collect_captures_outer {
139-
($p:pat) => { $crate::collect_captures!(no_std $p) };
126+
#[doc(hidden)]
127+
macro_rules! implicit_try_match {
128+
($p:pat = $($in:tt)*) => {
129+
// `$p` needs to be parenthesized for it to work on nightly-2020-05-30
130+
// and syn 1.0.29
131+
$crate::implicit_try_match_inner!(std($p) = $($in)*)
132+
};
140133
}
141134

142-
/// `#[proc_macro_hack]` makes it possible to use this procedural macro in
143-
/// expression position without relying on an unstable rustc feature, but with
144-
/// some restrictions. See `proc_macro_hack`'s documentation for more.
145-
#[cfg(feature = "implicit_map")]
146-
#[proc_macro_hack::proc_macro_hack]
135+
#[cfg(all(not(feature = "std"), feature = "implicit_map"))]
136+
#[macro_export]
147137
#[doc(hidden)]
148-
pub use try_match_inner::collect_captures;
138+
macro_rules! implicit_try_match {
139+
($p:pat = $($in:tt)*) => {
140+
$crate::implicit_try_match_inner!(no_std($p) = $($in)*)
141+
};
142+
}
149143

150144
#[cfg(not(feature = "implicit_map"))]
151-
#[macro_export]
152-
macro_rules! collect_captures {
145+
macro_rules! implicit_try_match {
153146
($p:pat) => {
154147
compile_error!(
155148
"can't use the implicit mapping form of `try_match!` because \
156149
the feature `implicit_map` is disabled"
157150
)
158151
};
159152
}
153+
154+
/// Pattern: `(std|no_std) $p:pat`
155+
///
156+
/// The produced expression evaluates to `Ok(_)` using bound variables on a
157+
/// successful match on the given value.
158+
///
159+
/// - If there are no bound variables, it generates `()`.
160+
/// - If there is exactly one bound variables `var`, it generates `var`.
161+
/// - If there are multiple bound variables `var1, var2, ...`, it generates
162+
/// `SomeType { var1, var2 }`.
163+
///
164+
/// Otherwise, the expression evaluates to `Err($in)`.
165+
///
166+
/// `#[proc_macro_hack]` makes it possible to use this procedural macro in
167+
/// expression position without relying on an unstable rustc feature, but with
168+
/// some restrictions. See `proc_macro_hack`'s documentation for more.
169+
#[cfg(feature = "implicit_map")]
170+
#[proc_macro_hack::proc_macro_hack]
171+
#[doc(hidden)]
172+
pub use try_match_inner::implicit_try_match_inner;

0 commit comments

Comments
 (0)