Repository: dtolnay/syn
Pull request #1264 (2022-12-13 00:42:31)

Revamp Pat parsing into 3 associated functions


Patch:
diff --git a/src/expr.rs b/src/expr.rs
index 6d7d9806..e5278763 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -2146,7 +2146,7 @@ pub(crate) mod parsing {
             Ok(ExprLet {
                 attrs: Vec::new(),
                 let_token: input.parse()?,
-                pat: pat::parsing::multi_pat_with_leading_vert(input)?,
+                pat: Pat::parse_multi_with_leading_vert(input)?,
                 eq_token: input.parse()?,
                 expr: Box::new({
                     let allow_struct = AllowStruct(false);
@@ -2206,7 +2206,7 @@ pub(crate) mod parsing {
             let label: Option<Label> = input.parse()?;
             let for_token: Token![for] = input.parse()?;
 
-            let pat = pat::parsing::multi_pat_with_leading_vert(input)?;
+            let pat = Pat::parse_multi_with_leading_vert(input)?;
 
             let in_token: Token![in] = input.parse()?;
             let expr: Expr = input.call(Expr::parse_without_eager_brace)?;
@@ -2503,7 +2503,7 @@ pub(crate) mod parsing {
     #[cfg(feature = "full")]
     fn closure_arg(input: ParseStream) -> Result<Pat> {
         let attrs = input.call(Attribute::parse_outer)?;
-        let mut pat: Pat = input.parse()?;
+        let mut pat = Pat::parse_single(input)?;
 
         if input.peek(Token![:]) {
             Ok(Pat::Type(PatType {
@@ -2844,7 +2844,7 @@ pub(crate) mod parsing {
             let requires_comma;
             Ok(Arm {
                 attrs: input.call(Attribute::parse_outer)?,
-                pat: pat::parsing::multi_pat_with_leading_vert(input)?,
+                pat: Pat::parse_multi_with_leading_vert(input)?,
                 guard: {
                     if input.peek(Token![if]) {
                         let if_token: Token![if] = input.parse()?;
diff --git a/src/item.rs b/src/item.rs
index 76bd5170..6887c11d 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -1668,7 +1668,7 @@ pub mod parsing {
 
         Ok(PatType {
             attrs: Vec::new(),
-            pat: Box::new(pat::parsing::multi_pat(input)?),
+            pat: Box::new(Pat::parse_single(input)?),
             colon_token: input.parse()?,
             ty: Box::new(match input.parse::<Option<Token![...]>>()? {
                 Some(dot3) => Type::Verbatim(variadic_to_tokens(&dot3)),
diff --git a/src/parse_quote.rs b/src/parse_quote.rs
index 8deae4e5..33fafb78 100644
--- a/src/parse_quote.rs
+++ b/src/parse_quote.rs
@@ -140,7 +140,7 @@ use crate::punctuated::Punctuated;
 #[cfg(any(feature = "full", feature = "derive"))]
 use crate::{attr, Attribute};
 #[cfg(feature = "full")]
-use crate::{Block, Stmt};
+use crate::{Block, Pat, Stmt};
 
 #[cfg(any(feature = "full", feature = "derive"))]
 impl ParseQuote for Attribute {
@@ -153,6 +153,13 @@ impl ParseQuote for Attribute {
     }
 }
 
+#[cfg(feature = "full")]
+impl ParseQuote for Pat {
+    fn parse(input: ParseStream) -> Result<Self> {
+        Pat::parse_multi_with_leading_vert(input)
+    }
+}
+
 impl<T: Parse, P: Parse> ParseQuote for Punctuated<T, P> {
     fn parse(input: ParseStream) -> Result<Self> {
         Self::parse_terminated(input)
diff --git a/src/pat.rs b/src/pat.rs
index b279186a..ecb19060 100644
--- a/src/pat.rs
+++ b/src/pat.rs
@@ -312,12 +312,36 @@ ast_struct! {
 pub mod parsing {
     use super::*;
     use crate::ext::IdentExt;
-    use crate::parse::{Parse, ParseBuffer, ParseStream, Result};
+    use crate::parse::{ParseBuffer, ParseStream, Result};
     use crate::path;
 
     #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
-    impl Parse for Pat {
-        fn parse(input: ParseStream) -> Result<Self> {
+    impl Pat {
+        /// Parse a pattern that does _not_ involve `|` at the top level.
+        ///
+        /// This parser matches the behavior of the `$:pat_param` macro_rules
+        /// matcher, and on editions prior to Rust 2021, the behavior of
+        /// `$:pat`.
+        ///
+        /// In Rust syntax, some examples of where this syntax would occur are
+        /// in the argument pattern of functions and closures. Patterns using
+        /// `|` are not allowed to occur in these positions.
+        ///
+        /// ```compile_fail
+        /// fn f(Some(_) | None: Option<T>) {
+        ///     let _ = |Some(_) | None: Option<T>| {};
+        ///     //       ^^^^^^^^^^^^^^^^^^^^^^^^^??? :(
+        /// }
+        /// ```
+        ///
+        /// ```console
+        /// error: top-level or-patterns are not allowed in function parameters
+        ///  --> src/main.rs:1:6
+        ///   |
+        /// 1 | fn f(Some(_) | None: Option<T>) {
+        ///   |      ^^^^^^^^^^^^^^ help: wrap the pattern in parentheses: `(Some(_) | None)`
+        /// ```
+        pub fn parse_single(input: ParseStream) -> Result<Self> {
             let begin = input.fork();
             let lookahead = input.lookahead1();
             if {
@@ -367,6 +391,80 @@ pub mod parsing {
                 Err(lookahead.error())
             }
         }
+
+        /// Parse a pattern, possibly involving `|`, but not a leading `|`.
+        pub fn parse_multi(input: ParseStream) -> Result<Self> {
+            multi_pat_impl(input, None)
+        }
+
+        /// Parse a pattern, possibly involving `|`, possibly including a
+        /// leading `|`.
+        ///
+        /// This parser matches the behavior of the Rust 2021 edition's `$:pat`
+        /// macro_rules matcher.
+        ///
+        /// In Rust syntax, an example of where this syntax would occur is in
+        /// the pattern of a `match` arm, where the language permits an optional
+        /// leading `|`, although it is not idiomatic to write one there in
+        /// handwritten code.
+        ///
+        /// ```
+        /// # let wat = None;
+        /// match wat {
+        ///     |None |Some(false) => {}
+        ///     |Some(true) => {}
+        /// }
+        /// ```
+        ///
+        /// The compiler accepts it only to facilitate some situations in
+        /// macro-generated code where a macro author might need to write:
+        ///
+        /// ```
+        /// # macro_rules! doc {
+        /// #     ($value:expr, ($($conditions1:pat),*), ($($conditions2:pat),*), $then:expr) => {
+        /// match $value {
+        ///     $(| $conditions1)* $(| $conditions2)* => $then
+        /// }
+        /// #     };
+        /// # }
+        /// #
+        /// # doc!(true, (true), (false), {});
+        /// # doc!(true, (), (true, false), {});
+        /// # doc!(true, (true, false), (), {});
+        /// ```
+        ///
+        /// Expressing the same thing correctly in the case that either one (but
+        /// not both) of `$conditions1` and `$conditions2` might be empty,
+        /// without leading `|`, is complex.
+        ///
+        /// Use [`Pat::parse_multi`] instead if you are not intending to support
+        /// macro-generated macro input.
+        pub fn parse_multi_with_leading_vert(input: ParseStream) -> Result<Self> {
+            let leading_vert: Option<Token![|]> = input.parse()?;
+            multi_pat_impl(input, leading_vert)
+        }
+    }
+
+    fn multi_pat_impl(input: ParseStream, leading_vert: Option<Token![|]>) -> Result<Pat> {
+        let mut pat = Pat::parse_single(input)?;
+        if leading_vert.is_some()
+            || input.peek(Token![|]) && !input.peek(Token![||]) && !input.peek(Token![|=])
+        {
+            let mut cases = Punctuated::new();
+            cases.push_value(pat);
+            while input.peek(Token![|]) && !input.peek(Token![||]) && !input.peek(Token![|=]) {
+                let punct = input.parse()?;
+                cases.push_punct(punct);
+                let pat = Pat::parse_single(input)?;
+                cases.push_value(pat);
+            }
+            pat = Pat::Or(PatOr {
+                attrs: Vec::new(),
+                leading_vert,
+                cases,
+            });
+        }
+        Ok(pat)
     }
 
     fn pat_path_or_macro_or_struct_or_range(input: ParseStream) -> Result<Pat> {
@@ -435,7 +533,7 @@ pub mod parsing {
         Ok(PatBox {
             attrs: Vec::new(),
             box_token: input.parse()?,
-            pat: input.parse()?,
+            pat: Box::new(Pat::parse_single(input)?),
         })
     }
 
@@ -448,7 +546,7 @@ pub mod parsing {
             subpat: {
                 if input.peek(Token![@]) {
                     let at_token: Token![@] = input.parse()?;
-                    let subpat: Pat = input.parse()?;
+                    let subpat = Pat::parse_single(input)?;
                     Some((at_token, Box::new(subpat)))
                 } else {
                     None
@@ -521,7 +619,7 @@ pub mod parsing {
                 attrs: Vec::new(),
                 member,
                 colon_token: input.parse()?,
-                pat: Box::new(multi_pat_with_leading_vert(input)?),
+                pat: Box::new(Pat::parse_multi_with_leading_vert(input)?),
             });
         }
 
@@ -600,7 +698,7 @@ pub mod parsing {
 
         let mut elems = Punctuated::new();
         while !content.is_empty() {
-            let value = multi_pat_with_leading_vert(&content)?;
+            let value = Pat::parse_multi_with_leading_vert(&content)?;
             elems.push_value(value);
             if content.is_empty() {
                 break;
@@ -621,7 +719,7 @@ pub mod parsing {
             attrs: Vec::new(),
             and_token: input.parse()?,
             mutability: input.parse()?,
-            pat: input.parse()?,
+            pat: Box::new(Pat::parse_single(input)?),
         })
     }
 
@@ -699,7 +797,7 @@ pub mod parsing {
 
         let mut elems = Punctuated::new();
         while !content.is_empty() {
-            let value = multi_pat_with_leading_vert(&content)?;
+            let value = Pat::parse_multi_with_leading_vert(&content)?;
             elems.push_value(value);
             if content.is_empty() {
                 break;
@@ -726,37 +824,6 @@ pub mod parsing {
 
         Ok(verbatim::between(begin, input))
     }
-
-    pub fn multi_pat(input: ParseStream) -> Result<Pat> {
-        multi_pat_impl(input, None)
-    }
-
-    pub fn multi_pat_with_leading_vert(input: ParseStream) -> Result<Pat> {
-        let leading_vert: Option<Token![|]> = input.parse()?;
-        multi_pat_impl(input, leading_vert)
-    }
-
-    fn multi_pat_impl(input: ParseStream, leading_vert: Option<Token![|]>) -> Result<Pat> {
-        let mut pat: Pat = input.parse()?;
-        if leading_vert.is_some()
-            || input.peek(Token![|]) && !input.peek(Token![||]) && !input.peek(Token![|=])
-        {
-            let mut cases = Punctuated::new();
-            cases.push_value(pat);
-            while input.peek(Token![|]) && !input.peek(Token![||]) && !input.peek(Token![|=]) {
-                let punct = input.parse()?;
-                cases.push_punct(punct);
-                let pat: Pat = input.parse()?;
-                cases.push_value(pat);
-            }
-            pat = Pat::Or(PatOr {
-                attrs: Vec::new(),
-                leading_vert,
-                cases,
-            });
-        }
-        Ok(pat)
-    }
 }
 
 #[cfg(feature = "printing")]
diff --git a/src/stmt.rs b/src/stmt.rs
index 2a62b918..068c8197 100644
--- a/src/stmt.rs
+++ b/src/stmt.rs
@@ -227,7 +227,7 @@ pub mod parsing {
     fn stmt_local(input: ParseStream, attrs: Vec<Attribute>, begin: ParseBuffer) -> Result<Stmt> {
         let let_token: Token![let] = input.parse()?;
 
-        let mut pat: Pat = pat::parsing::multi_pat_with_leading_vert(input)?;
+        let mut pat = Pat::parse_single(input)?;
         if input.peek(Token![:]) {
             let colon_token: Token![:] = input.parse()?;
             let ty: Type = input.parse()?;


Test patch:
diff --git a/tests/test_pat.rs b/tests/test_pat.rs
index 695e4736..c1e51273 100644
--- a/tests/test_pat.rs
+++ b/tests/test_pat.rs
@@ -4,11 +4,12 @@ mod macros;
 use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
 use quote::quote;
 use std::iter::FromIterator;
+use syn::parse::Parser;
 use syn::{Item, Pat, Stmt};
 
 #[test]
 fn test_pat_ident() {
-    match syn::parse2(quote!(self)).unwrap() {
+    match Pat::parse_single.parse2(quote!(self)).unwrap() {
         Pat::Ident(_) => (),
         value => panic!("expected PatIdent, got {:?}", value),
     }
@@ -16,7 +17,7 @@ fn test_pat_ident() {
 
 #[test]
 fn test_pat_path() {
-    match syn::parse2(quote!(self::CONST)).unwrap() {
+    match Pat::parse_single.parse2(quote!(self::CONST)).unwrap() {
         Pat::Path(_) => (),
         value => panic!("expected PatPath, got {:?}", value),
     }
@@ -30,7 +31,7 @@ fn test_leading_vert() {
     syn::parse_str::<Item>("fn fun1(| A: E) {}").unwrap_err();
     syn::parse_str::<Item>("fn fun2(|| A: E) {}").unwrap_err();
 
-    syn::parse_str::<Stmt>("let | () = ();").unwrap();
+    syn::parse_str::<Stmt>("let | () = ();").unwrap_err();
     syn::parse_str::<Stmt>("let (| A): E;").unwrap();
     syn::parse_str::<Stmt>("let (|| A): (E);").unwrap_err();
     syn::parse_str::<Stmt>("let (| A,): (E,);").unwrap();
@@ -46,8 +47,9 @@ fn test_leading_vert() {
 fn test_group() {
     let group = Group::new(Delimiter::None, quote!(Some(_)));
     let tokens = TokenStream::from_iter(vec![TokenTree::Group(group)]);
+    let pat = Pat::parse_single.parse2(tokens).unwrap();
 
-    snapshot!(tokens as Pat, @r###"
+    snapshot!(pat, @r###"
     Pat::TupleStruct {
         path: Path {
             segments: [