Repository: dtolnay/syn (rust)

Issue:
Parse const underscore in traits
As seen in https://github.com/rust-lang/rust/blob/8aa9d2014f4e5258f83b907e8431c59a33acdae7/src/test/ui/parser/assoc-const-underscore-syntactic-pass.rs#L10.

```rust
pub trait A {
    const _: () = ();
}
```

Rustc accepts this syntactically but not semantically.

Pull request:
Parse const underscore in traits and impls
(test is based on [rust/src/test/ui/parser/assoc-const-underscore-syntactic-pass.rs](https://github.com/rust-lang/rust/blob/8aa9d2014f4e5258f83b907e8431c59a33acdae7/src/test/ui/parser/assoc-const-underscore-syntactic-pass.rs))

Closes #760

Patch:
diff --git a/src/item.rs b/src/item.rs
index e5ec3090..d48248fc 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -2220,7 +2220,7 @@ pub mod parsing {
             let mut item = if lookahead.peek(Token![const]) {
                 ahead.parse::<Token![const]>()?;
                 let lookahead = ahead.lookahead1();
-                if lookahead.peek(Ident) {
+                if lookahead.peek(Ident) || lookahead.peek(Token![_]) {
                     input.parse().map(TraitItem::Const)
                 } else if lookahead.peek(Token![async])
                     || lookahead.peek(Token![unsafe])
@@ -2274,7 +2274,14 @@ pub mod parsing {
             Ok(TraitItemConst {
                 attrs: input.call(Attribute::parse_outer)?,
                 const_token: input.parse()?,
-                ident: input.parse()?,
+                ident: {
+                    let lookahead = input.lookahead1();
+                    if lookahead.peek(Ident) || lookahead.peek(Token![_]) {
+                        input.call(Ident::parse_any)?
+                    } else {
+                        return Err(lookahead.error());
+                    }
+                },
                 colon_token: input.parse()?,
                 ty: input.parse()?,
                 default: {
@@ -2483,9 +2490,9 @@ pub mod parsing {
             let mut item = if lookahead.peek(Token![const]) {
                 let const_token: Token![const] = ahead.parse()?;
                 let lookahead = ahead.lookahead1();
-                if lookahead.peek(Ident) {
+                if lookahead.peek(Ident) || lookahead.peek(Token![_]) {
                     input.advance_to(&ahead);
-                    let ident: Ident = input.parse()?;
+                    let ident: Ident = input.call(Ident::parse_any)?;
                     let colon_token: Token![:] = input.parse()?;
                     let ty: Type = input.parse()?;
                     if let Some(eq_token) = input.parse()? {
@@ -2596,7 +2603,14 @@ pub mod parsing {
                 vis: input.parse()?,
                 defaultness: input.parse()?,
                 const_token: input.parse()?,
-                ident: input.parse()?,
+                ident: {
+                    let lookahead = input.lookahead1();
+                    if lookahead.peek(Ident) || lookahead.peek(Token![_]) {
+                        input.call(Ident::parse_any)?
+                    } else {
+                        return Err(lookahead.error());
+                    }
+                },
                 colon_token: input.parse()?,
                 ty: input.parse()?,
                 eq_token: input.parse()?,


Test patch:
diff --git a/tests/test_const_underscore.rs b/tests/test_const_underscore.rs
new file mode 100644
index 00000000..ce4478d9
--- /dev/null
+++ b/tests/test_const_underscore.rs
@@ -0,0 +1,103 @@
+#[macro_use]
+mod macros;
+
+use quote::quote;
+use syn::{ItemImpl, ItemTrait};
+
+#[test]
+fn test_const_underscore_in_trait() {
+    let input = quote! {
+        pub trait A {
+            const _: () = ();
+        }
+    };
+
+    snapshot!(input as ItemTrait, @r###"
+    ItemTrait {
+        vis: Visibility::Public,
+        ident: "A",
+        generics: Generics,
+        items: [
+            TraitItem::Const {
+                ident: "_",
+                ty: Type::Tuple,
+                default: Some(Expr::Tuple),
+            },
+        ],
+    }
+    "###);
+}
+
+#[test]
+fn test_const_underscore_in_trait_impl() {
+    let input = quote! {
+        impl A for () {
+            const _: () = ();
+        }
+    };
+
+    snapshot!(input as ItemImpl, @r###"
+    ItemImpl {
+        generics: Generics,
+        trait_: Some((
+            None,
+            Path {
+                segments: [
+                    PathSegment {
+                        ident: "A",
+                        arguments: None,
+                    },
+                ],
+            },
+        )),
+        self_ty: Type::Tuple,
+        items: [
+            ImplItem::Const {
+                vis: Inherited,
+                ident: "_",
+                ty: Type::Tuple,
+                expr: Expr::Tuple,
+            },
+        ],
+    }
+    "###);
+}
+
+#[test]
+fn test_const_underscore_in_inherent_impl() {
+    let input = quote! {
+        impl dyn A {
+            const _: () = ();
+        }
+    };
+
+    snapshot!(input as ItemImpl, @r###"
+    ItemImpl {
+        generics: Generics,
+        self_ty: Type::TraitObject {
+            dyn_token: Some,
+            bounds: [
+                Trait(TraitBound {
+                    modifier: None,
+                    path: Path {
+                        segments: [
+                            PathSegment {
+                                ident: "A",
+                                arguments: None,
+                            },
+                        ],
+                    },
+                }),
+            ],
+        },
+        items: [
+            ImplItem::Const {
+                vis: Inherited,
+                ident: "_",
+                ty: Type::Tuple,
+                expr: Expr::Tuple,
+            },
+        ],
+    }
+    "###);
+}