Repository: dtolnay/syn (rust)

Issue:
Parse extern type with trait bound
As seen in https://github.com/rust-lang/rust/blob/8aa9d2014f4e5258f83b907e8431c59a33acdae7/src/test/ui/parser/foreign-ty-syntactic-pass.rs.

```rust
extern "C" {
    type A: Ord;
    type A<'a> where 'a: 'static;
    type A<T: Ord> where T: 'static;
    type A = u8;
    type A<'a: 'static, T: Ord + 'static>: Eq + PartialEq where T: 'static + Copy = Vec<u8>;
}
```

Rustc accepts this syntactically but not semantically.

Pull request:
Parse ForeignItem `type` with generics and bounds
As tested by https://github.com/rust-lang/rust/blob/1836e3b42a5b2f37fd79104eedbe8f48a5afdee6/src/test/ui/parser/foreign-ty-syntactic-pass.rs.

```rust
extern "C" {
    type A: Ord;
    type A<'a> where 'a: 'static;
    type A<T: Ord> where T: 'static;
    type A = u8;
    type A<'a: 'static, T: Ord + 'static>: Eq + PartialEq where T: 'static + Copy = Vec<u8>;
}
```

Closes #763. Required for https://github.com/dtolnay/cxx/issues/99.

Patch:
diff --git a/src/item.rs b/src/item.rs
index 83b82fdc..f700b57e 100644
--- a/src/item.rs
+++ b/src/item.rs
@@ -1155,7 +1155,7 @@ pub mod parsing {
 
     use crate::ext::IdentExt;
     use crate::parse::discouraged::Speculative;
-    use crate::parse::{Parse, ParseStream, Result};
+    use crate::parse::{Parse, ParseBuffer, ParseStream, Result};
     use crate::token::Brace;
     use proc_macro2::{Delimiter, Group, Punct, Spacing, TokenTree};
     use std::iter::{self, FromIterator};
@@ -1745,6 +1745,7 @@ pub mod parsing {
 
     impl Parse for ForeignItem {
         fn parse(input: ParseStream) -> Result<Self> {
+            let begin = input.fork();
             let mut attrs = input.call(Attribute::parse_outer)?;
             let ahead = input.fork();
             let vis: Visibility = ahead.parse()?;
@@ -1755,7 +1756,7 @@ pub mod parsing {
             } else if lookahead.peek(Token![static]) {
                 input.parse().map(ForeignItem::Static)
             } else if lookahead.peek(Token![type]) {
-                input.parse().map(ForeignItem::Type)
+                parse_flexible_foreign_item_type(begin, input)
             } else if vis.is_inherited()
                 && (lookahead.peek(Ident)
                     || lookahead.peek(Token![self])
@@ -1769,17 +1770,16 @@ pub mod parsing {
                 Err(lookahead.error())
             }?;
 
-            {
-                let item_attrs = match &mut item {
-                    ForeignItem::Fn(item) => &mut item.attrs,
-                    ForeignItem::Static(item) => &mut item.attrs,
-                    ForeignItem::Type(item) => &mut item.attrs,
-                    ForeignItem::Macro(item) => &mut item.attrs,
-                    ForeignItem::Verbatim(_) | ForeignItem::__Nonexhaustive => unreachable!(),
-                };
-                attrs.extend(item_attrs.drain(..));
-                *item_attrs = attrs;
-            }
+            let item_attrs = match &mut item {
+                ForeignItem::Fn(item) => &mut item.attrs,
+                ForeignItem::Static(item) => &mut item.attrs,
+                ForeignItem::Type(item) => &mut item.attrs,
+                ForeignItem::Macro(item) => &mut item.attrs,
+                ForeignItem::Verbatim(_) => return Ok(item),
+                ForeignItem::__Nonexhaustive => unreachable!(),
+            };
+            attrs.extend(item_attrs.drain(..));
+            *item_attrs = attrs;
 
             Ok(item)
         }
@@ -1853,6 +1853,55 @@ pub mod parsing {
         }
     }
 
+    fn parse_flexible_foreign_item_type(
+        begin: ParseBuffer,
+        input: ParseStream,
+    ) -> Result<ForeignItem> {
+        let mut extra = false;
+
+        let vis: Visibility = input.parse()?;
+        let type_token: Token![type] = input.parse()?;
+        let ident: Ident = input.parse()?;
+        if input.peek(Token![<]) {
+            extra = true;
+            input.parse::<Generics>()?;
+        }
+        if input.parse::<Option<Token![:]>>()?.is_some() {
+            extra = true;
+            loop {
+                input.parse::<TypeParamBound>()?;
+                if input.peek(Token![where]) || input.peek(Token![;]) {
+                    break;
+                }
+                input.parse::<Token![+]>()?;
+                if input.peek(Token![where]) || input.peek(Token![;]) {
+                    break;
+                }
+            }
+        }
+        if input.peek(Token![where]) {
+            extra = true;
+            input.parse::<WhereClause>()?;
+        }
+        if input.parse::<Option<Token![=]>>()?.is_some() {
+            extra = true;
+            input.parse::<Type>()?;
+        }
+        let semi_token: Token![;] = input.parse()?;
+
+        if extra {
+            Ok(ForeignItem::Verbatim(verbatim::between(begin, input)))
+        } else {
+            Ok(ForeignItem::Type(ForeignItemType {
+                attrs: Vec::new(),
+                vis,
+                type_token,
+                ident,
+                semi_token,
+            }))
+        }
+    }
+
     impl Parse for ForeignItemMacro {
         fn parse(input: ParseStream) -> Result<Self> {
             let attrs = input.call(Attribute::parse_outer)?;


Test patch:
diff --git a/tests/repo/mod.rs b/tests/repo/mod.rs
index 57f7048a..eb900bc1 100644
--- a/tests/repo/mod.rs
+++ b/tests/repo/mod.rs
@@ -81,10 +81,6 @@ pub fn base_dir_filter(entry: &DirEntry) -> bool {
         // https://github.com/dtolnay/syn/issues/762
         "test/ui/parser/foreign-static-syntactic-pass.rs" |
 
-        // TODO: extern type with bound: `extern { type A: Ord; }`
-        // https://github.com/dtolnay/syn/issues/763
-        "test/ui/parser/foreign-ty-syntactic-pass.rs" |
-
         // TODO: top level const/static without value: `const X: u8;`
         // https://github.com/dtolnay/syn/issues/764
         "test/ui/parser/item-free-const-no-body-syntactic-pass.rs" |