nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: dtolnay/cxx
Pull request #598 (2020-12-27 20:34:39)

Add checked and unchecked indexing to rust::Slice
```diff
  template <typename T>
  class Slice {
  public:
    ...
+   bool empty() const noexcept;
+   T &operator[](size_t n) const noexcept;
+   T &at(size_t n) const;
+   T &front() const noexcept;
+   T &back() const noexcept;
  };
```


Patch:
diff --git a/book/src/binding/slice.md b/book/src/binding/slice.md
index fc2b4daa..ef9450c0 100644
--- a/book/src/binding/slice.md
+++ b/book/src/binding/slice.md
@@ -25,6 +25,12 @@ public:
   T *data() const noexcept;
   size_t size() const noexcept;
   size_t length() const noexcept;
+  bool empty() const noexcept;
+
+  T &operator[](size_t n) const noexcept;
+  T &at(size_t n) const;
+  T &front() const noexcept;
+  T &back() const noexcept;
 
   class iterator;
   iterator begin() const noexcept;
diff --git a/gen/src/builtin.rs b/gen/src/builtin.rs
index 8ce89f80..d43038b0 100644
--- a/gen/src/builtin.rs
+++ b/gen/src/builtin.rs
@@ -64,6 +64,7 @@ pub(super) fn write(out: &mut OutFile) {
         include.iterator = true;
         include.type_traits = true;
         builtin.friend_impl = true;
+        builtin.panic = true;
     }
 
     if builtin.rust_box {
diff --git a/include/cxx.h b/include/cxx.h
index 566b4822..c1d452e3 100644
--- a/include/cxx.h
+++ b/include/cxx.h
@@ -156,6 +156,12 @@ public:
   T *data() const noexcept;
   std::size_t size() const noexcept;
   std::size_t length() const noexcept;
+  bool empty() const noexcept;
+
+  T &operator[](std::size_t n) const noexcept;
+  T &at(std::size_t n) const;
+  T &front() const noexcept;
+  T &back() const noexcept;
 
   // Important in order for System V ABI to pass in registers.
   Slice(const Slice<T> &) noexcept = default;
@@ -519,6 +525,37 @@ std::size_t Slice<T>::length() const noexcept {
   return this->len;
 }
 
+template <typename T>
+bool Slice<T>::empty() const noexcept {
+  return this->len == 0;
+}
+
+template <typename T>
+T &Slice<T>::operator[](std::size_t n) const noexcept {
+  assert(n < this->size());
+  return this->ptr[n];
+}
+
+template <typename T>
+T &Slice<T>::at(std::size_t n) const {
+  if (n >= this->size()) {
+    panic<std::out_of_range>("rust::Slice index out of range");
+  }
+  return (*this)[n];
+}
+
+template <typename T>
+T &Slice<T>::front() const noexcept {
+  assert(!this->empty());
+  return this->ptr[0];
+}
+
+template <typename T>
+T &Slice<T>::back() const noexcept {
+  assert(!this->empty());
+  return this->ptr[this->len - 1];
+}
+
 template <typename T>
 T &Slice<T>::iterator::operator*() const noexcept {
   return *this->pos;


Test patch:
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index e508494a..05ff4b87 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -274,7 +274,8 @@ void c_take_slice_char(rust::Slice<const char> s) {
 }
 
 void c_take_slice_shared(rust::Slice<const Shared> s) {
-  if (s.size() == 2 && s.data()->z == 2020 && (s.data() + 1)->z == 2021) {
+  if (s.size() == 2 && s.data()->z == 2020 && s[1].z == 2021 &&
+      s.at(1).z == 2021 && s.front().z == 2020 && s.back().z == 2021) {
     cxx_test_suite_set_correct();
   }
 }
Repository: dtolnay/cxx
Pull request #639 (2021-01-02 09:22:06)

Restrict MSVC instantiation workaround to return positions only
#637 in slightly more restricted form. Only *returning* un-instantiated class templates appears to be affected. For `rust::Slice` types that are only used in function arguments or struct fields, not function return values, MSVC does not need to be given the explicit template instantiation.


Patch:
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 489d85aa..97610a4f 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -5,7 +5,7 @@ use crate::gen::{builtin, include, Opt};
 use crate::syntax::atom::Atom::{self, *};
 use crate::syntax::instantiate::ImplKey;
 use crate::syntax::map::UnorderedMap as Map;
-use crate::syntax::set::UnorderedSet;
+use crate::syntax::set::{OrderedSet, UnorderedSet};
 use crate::syntax::symbol::Symbol;
 use crate::syntax::trivial::{self, TrivialReason};
 use crate::syntax::{
@@ -121,19 +121,24 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
 
     out.set_namespace(Default::default());
 
+    // MSVC workaround for "C linkage function cannot return C++ class" error.
+    // Apparently the compiler fails to perform implicit instantiations as part
+    // of an extern declaration. Instead we instantiate explicitly.
+    // See https://stackoverflow.com/a/57429504/6086311.
     out.next_section();
-    for ty in out.types {
-        // MSVC workaround for "C linkage function cannot return C++ class"
-        // error. Apparently the compiler fails to perform implicit
-        // instantiations as part of an extern declaration. Instead we
-        // instantiate explicitly.
-        // See https://stackoverflow.com/a/57429504/6086311.
-        if let Type::SliceRef(_) = ty {
-            write!(out, "template struct ");
-            write_type(out, ty);
-            writeln!(out, ";");
+    let mut slice_in_return_position = OrderedSet::new();
+    for api in apis {
+        if let Api::CxxFunction(efn) | Api::RustFunction(efn) = api {
+            if let Some(ty @ Type::SliceRef(_)) = &efn.ret {
+                slice_in_return_position.insert(ty);
+            }
         }
     }
+    for ty in &slice_in_return_position {
+        write!(out, "template struct ");
+        write_type(out, ty);
+        writeln!(out, ";");
+    }
 
     out.next_section();
     for api in apis {


Test patch:
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 3841cb2f..f043d817 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -828,6 +828,10 @@ std::unique_ptr<I> ns_c_return_unique_ptr_ns() {
 // > definition) of each of its non-inherited non-template members that has not
 // > been previously explicitly specialized in the translation unit.
 template struct rust::Box<tests::Shared>;
+template struct rust::Slice<const tests::Shared>;
+template struct rust::Slice<tests::Shared>;
+template struct rust::Slice<const tests::R>;
+template struct rust::Slice<tests::R>;
 template struct rust::Vec<uint8_t>;
 template struct rust::Vec<rust::String>;
 template struct rust::Vec<tests::Shared>;
Repository: dtolnay/cxx
Pull request #653 (2021-01-03 07:51:16)

Enable lifetimes on opaque Rust types
Follow-up to #608.


Patch:
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 11115b81..476012f3 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -677,11 +677,12 @@ fn expand_rust_type_import(ety: &ExternType) -> TokenStream {
 
 fn expand_rust_type_impl(ety: &ExternType) -> TokenStream {
     let ident = &ety.name.rust;
+    let generics = &ety.generics;
     let span = ident.span();
     let unsafe_impl = quote_spanned!(ety.type_token.span=> unsafe impl);
 
     let mut impls = quote_spanned! {span=>
-        #unsafe_impl ::cxx::private::RustType for #ident {}
+        #unsafe_impl #generics ::cxx::private::RustType for #ident #generics {}
     };
 
     for derive in &ety.derives {
@@ -689,7 +690,7 @@ fn expand_rust_type_impl(ety: &ExternType) -> TokenStream {
             let type_id = type_id(&ety.name);
             let span = derive.span;
             impls.extend(quote_spanned! {span=>
-                unsafe impl ::cxx::ExternType for #ident {
+                unsafe impl #generics ::cxx::ExternType for #ident #generics {
                     type Id = #type_id;
                     type Kind = ::cxx::kind::Opaque;
                 }
diff --git a/syntax/check.rs b/syntax/check.rs
index 9a8881f2..b9f6660e 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -335,13 +335,6 @@ fn check_api_type(cx: &mut Check, ety: &ExternType) {
         cx.error(derive, msg);
     }
 
-    if let Some(lifetime) = ety.generics.lifetimes.first() {
-        if ety.lang == Lang::Rust {
-            let msg = "extern Rust type with lifetimes is not supported yet";
-            cx.error(lifetime, msg);
-        }
-    }
-
     if !ety.bounds.is_empty() {
         let bounds = &ety.bounds;
         let span = quote!(#(#bounds)*);


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index ba28f500..5ac461c8 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -210,6 +210,11 @@ pub mod ffi {
         type Job = crate::module::ffi::Job;
     }
 
+    extern "Rust" {
+        #[derive(ExternType)]
+        type Reference<'a>;
+    }
+
     unsafe extern "C++" {
         type Borrow<'a>;
 
@@ -377,6 +382,8 @@ impl R {
     }
 }
 
+pub struct Reference<'a>(&'a String);
+
 impl ffi::Shared {
     fn r_method_on_shared(&self) -> String {
         "2020".to_owned()
Repository: dtolnay/cxx
Pull request #785 (2021-03-27 05:09:52)

Handle pinned reference argument pointing to improper ctype
Fixes https://github.com/google/autocxx/issues/319.


Patch:
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 042fdcb9..1ffd44f5 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -506,10 +506,18 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream {
                     false => quote!(::cxx::private::RustVec::from_ref(#var)),
                     true => quote!(::cxx::private::RustVec::from_mut(#var)),
                 },
-                inner if types.is_considered_improper_ctype(inner) => match ty.mutable {
-                    false => quote!(#var as *const #inner as *const ::std::ffi::c_void),
-                    true => quote!(#var as *mut #inner as *mut ::std::ffi::c_void),
-                },
+                inner if types.is_considered_improper_ctype(inner) => {
+                    let var = match ty.pinned {
+                        false => quote!(#var),
+                        true => quote!(::std::pin::Pin::into_inner_unchecked(#var)),
+                    };
+                    match ty.mutable {
+                        false => {
+                            quote!(#var as *const #inner as *const ::std::ffi::c_void)
+                        }
+                        true => quote!(#var as *mut #inner as *mut ::std::ffi::c_void),
+                    }
+                }
                 _ => quote!(#var),
             },
             Type::Ptr(ty) => {


Test patch:
diff --git a/tests/ffi/module.rs b/tests/ffi/module.rs
index cd9fc2b7..089a5c5f 100644
--- a/tests/ffi/module.rs
+++ b/tests/ffi/module.rs
@@ -32,6 +32,9 @@ pub mod ffi2 {
 
         fn c_take_trivial_ptr(d: UniquePtr<D>);
         fn c_take_trivial_ref(d: &D);
+        fn c_take_trivial_mut_ref(d: &mut D);
+        fn c_take_trivial_pin_ref(d: Pin<&D>);
+        fn c_take_trivial_pin_mut_ref(d: Pin<&mut D>);
         fn c_take_trivial_ref_method(self: &D);
         fn c_take_trivial_mut_ref_method(self: &mut D);
         fn c_take_trivial(d: D);
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 831097dc..a4d8c47b 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -604,6 +604,18 @@ void c_take_trivial_ref(const D &d) {
   }
 }
 
+void c_take_trivial_mut_ref(D &d) {
+  (void)d;
+}
+
+void c_take_trivial_pin_ref(const D &d) {
+  (void)d;
+}
+
+void c_take_trivial_pin_mut_ref(D &d) {
+  (void)d;
+}
+
 void D::c_take_trivial_ref_method() const {
   if (d == 30) {
     cxx_test_suite_set_correct();
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index eaa43616..a5d8bc03 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -184,6 +184,9 @@ size_t c_get_use_count(const std::weak_ptr<C> &weak) noexcept;
 
 void c_take_trivial_ptr(std::unique_ptr<D> d);
 void c_take_trivial_ref(const D &d);
+void c_take_trivial_mut_ref(D &d);
+void c_take_trivial_pin_ref(const D &d);
+void c_take_trivial_pin_mut_ref(D &d);
 void c_take_trivial(D d);
 
 void c_take_trivial_ns_ptr(std::unique_ptr<::G::G> g);
Repository: dtolnay/syn
Pull request #836 (2020-05-29 07:26:03)

Parse empty None-delimited group as inherited visibility
https://github.com/rust-lang/rust/issues/72622#issuecomment-635779472


Patch:
diff --git a/src/data.rs b/src/data.rs
index a7174c46..b217b8ca 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -316,6 +316,17 @@ pub mod parsing {
 
     impl Parse for Visibility {
         fn parse(input: ParseStream) -> Result<Self> {
+            // Recognize an empty None-delimited group, as produced by a $:vis
+            // matcher that matched no tokens.
+            if input.peek(token::Group) {
+                let ahead = input.fork();
+                let group = crate::group::parse_group(&ahead)?;
+                if group.content.is_empty() {
+                    input.advance_to(&ahead);
+                    return Ok(Visibility::Inherited);
+                }
+            }
+
             if input.peek(Token![pub]) {
                 Self::parse_pub(input)
             } else if input.peek(Token![crate]) {


Test patch:
diff --git a/tests/test_visibility.rs b/tests/test_visibility.rs
index 21f9adf2..c3d0ac7a 100644
--- a/tests/test_visibility.rs
+++ b/tests/test_visibility.rs
@@ -1,6 +1,10 @@
-use proc_macro2::TokenStream;
+#[macro_use]
+mod macros;
+
+use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
+use std::iter::FromIterator;
 use syn::parse::{Parse, ParseStream};
-use syn::{Result, Visibility};
+use syn::{DeriveInput, Result, Visibility};
 
 #[derive(Debug)]
 struct VisRest {
@@ -95,3 +99,47 @@ fn test_crate_path() {
 fn test_junk_after_in() {
     assert_vis_parse!("pub(in some::path @@garbage)", Err);
 }
+
+#[test]
+fn test_empty_group_vis() {
+    // mimics `struct S { $vis $field: () }` where $vis is empty
+    let tokens = TokenStream::from_iter(vec![
+        TokenTree::Ident(Ident::new("struct", Span::call_site())),
+        TokenTree::Ident(Ident::new("S", Span::call_site())),
+        TokenTree::Group(Group::new(
+            Delimiter::Brace,
+            TokenStream::from_iter(vec![
+                TokenTree::Group(Group::new(Delimiter::None, TokenStream::new())),
+                TokenTree::Group(Group::new(
+                    Delimiter::None,
+                    TokenStream::from_iter(vec![TokenTree::Ident(Ident::new(
+                        "f",
+                        Span::call_site(),
+                    ))]),
+                )),
+                TokenTree::Punct(Punct::new(':', Spacing::Alone)),
+                TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
+            ]),
+        )),
+    ]);
+
+    snapshot!(tokens as DeriveInput, @r###"
+    DeriveInput {
+        vis: Inherited,
+        ident: "S",
+        generics: Generics,
+        data: Data::Struct {
+            fields: Fields::Named {
+                named: [
+                    Field {
+                        vis: Inherited,
+                        ident: Some("f"),
+                        colon_token: Some,
+                        ty: Type::Tuple,
+                    },
+                ],
+            },
+        },
+    }
+    "###);
+}
Repository: dtolnay/syn
Pull request #838 (2020-05-29 23:10:24)

Parse macro call expr with None-delimited path
This would happen in the expression `$macro!()` where $macro:ident.

Fixes https://crater-reports.s3.amazonaws.com/pr-72622/master%237726070fa755f660b5da3f82f46e07d9c6866f69/reg/fourier-0.1.0/log.txt.


Patch:
diff --git a/src/expr.rs b/src/expr.rs
index ce87136e..6f14cbc3 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -1691,7 +1691,7 @@ pub(crate) mod parsing {
     // interactions, as they are fully contained.
     #[cfg(feature = "full")]
     fn atom_expr(input: ParseStream, allow_struct: AllowStruct) -> Result<Expr> {
-        if input.peek(token::Group) && !input.peek2(Token![::]) {
+        if input.peek(token::Group) && !input.peek2(Token![::]) && !input.peek2(Token![!]) {
             input.call(expr_group).map(Expr::Group)
         } else if input.peek(Lit) {
             input.parse().map(Expr::Lit)


Test patch:
diff --git a/tests/test_expr.rs b/tests/test_expr.rs
index b9b36e38..481ee3d5 100644
--- a/tests/test_expr.rs
+++ b/tests/test_expr.rs
@@ -115,3 +115,30 @@ fn test_macro_variable_func() {
     }
     "###);
 }
+
+#[test]
+fn test_macro_variable_macro() {
+    // mimics the token stream corresponding to `$macro!()`
+    let tokens = TokenStream::from_iter(vec![
+        TokenTree::Group(Group::new(Delimiter::None, quote! { m })),
+        TokenTree::Punct(Punct::new('!', Spacing::Alone)),
+        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new())),
+    ]);
+
+    snapshot!(tokens as Expr, @r###"
+    Expr::Macro {
+        mac: Macro {
+            path: Path {
+                segments: [
+                    PathSegment {
+                        ident: "m",
+                        arguments: None,
+                    },
+                ],
+            },
+            delimiter: Paren,
+            tokens: TokenStream(``),
+        },
+    }
+    "###);
+}
Repository: dtolnay/syn
Pull request #839 (2020-05-29 23:27:11)

Parse Type::Path containing None-delimited group segment
This would happen in the type `$ty<T>` or `$ty<'a>` where $ty:ty.

Fixes https://crater-reports.s3.amazonaws.com/pr-72622/master%237726070fa755f660b5da3f82f46e07d9c6866f69/reg/gc-sequence-0.2.0/log.txt.


Patch:
diff --git a/src/ty.rs b/src/ty.rs
index a0b3df84..8383bec4 100644
--- a/src/ty.rs
+++ b/src/ty.rs
@@ -430,7 +430,7 @@ pub mod parsing {
     }
 
     fn ambig_ty(input: ParseStream, allow_plus: bool) -> Result<Type> {
-        if input.peek(token::Group) && !input.peek2(Token![::]) {
+        if input.peek(token::Group) && !input.peek2(Token![::]) && !input.peek2(Token![<]) {
             return input.parse().map(Type::Group);
         }
 


Test patch:
diff --git a/tests/test_ty.rs b/tests/test_ty.rs
index ccf6e139..9cbdcd6b 100644
--- a/tests/test_ty.rs
+++ b/tests/test_ty.rs
@@ -1,3 +1,9 @@
+#[macro_use]
+mod macros;
+
+use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
+use quote::quote;
+use std::iter::FromIterator;
 use syn::Type;
 
 #[test]
@@ -8,3 +14,40 @@ fn test_mut_self() {
     syn::parse_str::<Type>("fn(mut self: mut self)").unwrap_err();
     syn::parse_str::<Type>("fn(mut self::T)").unwrap_err();
 }
+
+#[test]
+fn test_macro_variable_type() {
+    // mimics the token stream corresponding to `$ty<T>`
+    let tokens = TokenStream::from_iter(vec![
+        TokenTree::Group(Group::new(Delimiter::None, quote! { ty })),
+        TokenTree::Punct(Punct::new('<', Spacing::Alone)),
+        TokenTree::Ident(Ident::new("T", Span::call_site())),
+        TokenTree::Punct(Punct::new('>', Spacing::Alone)),
+    ]);
+
+    snapshot!(tokens as Type, @r###"
+    Type::Path {
+        path: Path {
+            segments: [
+                PathSegment {
+                    ident: "ty",
+                    arguments: PathArguments::AngleBracketed {
+                        args: [
+                            Type(Type::Path {
+                                path: Path {
+                                    segments: [
+                                        PathSegment {
+                                            ident: "T",
+                                            arguments: None,
+                                        },
+                                    ],
+                                },
+                            }),
+                        ],
+                    },
+                },
+            ],
+        },
+    }
+    "###);
+}
Repository: dtolnay/syn
Pull request #932 (2020-11-27 06:30:37)

Parse const blocks in range patterns
Remainder of #921.


Patch:
diff --git a/src/expr.rs b/src/expr.rs
index 1ee063d7..33639955 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -2490,7 +2490,7 @@ pub(crate) mod parsing {
     }
 
     #[cfg(feature = "full")]
-    fn expr_const(input: ParseStream) -> Result<TokenStream> {
+    pub(crate) fn expr_const(input: ParseStream) -> Result<TokenStream> {
         let begin = input.fork();
         input.parse::<Token![const]>()?;
 
diff --git a/src/pat.rs b/src/pat.rs
index 01a507c0..248da3dc 100644
--- a/src/pat.rs
+++ b/src/pat.rs
@@ -327,7 +327,8 @@ pub mod parsing {
                 input.call(pat_wild).map(Pat::Wild)
             } else if input.peek(Token![box]) {
                 input.call(pat_box).map(Pat::Box)
-            } else if input.peek(Token![-]) || lookahead.peek(Lit) {
+            } else if input.peek(Token![-]) || lookahead.peek(Lit) || lookahead.peek(Token![const])
+            {
                 pat_lit_or_range(input)
             } else if lookahead.peek(Token![ref])
                 || lookahead.peek(Token![mut])
@@ -655,6 +656,8 @@ pub mod parsing {
             || lookahead.peek(Token![crate])
         {
             Expr::Path(input.parse()?)
+        } else if lookahead.peek(Token![const]) {
+            Expr::Verbatim(input.call(expr::parsing::expr_const)?)
         } else {
             return Err(lookahead.error());
         };


Test patch:
diff --git a/tests/repo/mod.rs b/tests/repo/mod.rs
index b656838a..e40f71b9 100644
--- a/tests/repo/mod.rs
+++ b/tests/repo/mod.rs
@@ -12,9 +12,6 @@ const REVISION: &str = "9d78d1d02761b906038ba4d54c5f3427f920f5fb";
 
 #[rustfmt::skip]
 static EXCLUDE: &[&str] = &[
-    // TODO: const block in range patterns (#921)
-    "src/test/ui/inline-const/const-match-pat-range.rs",
-
     // Compile-fail expr parameter in const generic position: f::<1 + 2>()
     "src/test/ui/const-generics/closing-args-token.rs",
     "src/test/ui/const-generics/const-expression-parameter.rs",
Repository: dtolnay/syn
Pull request #1110 (2021-12-23 00:04:52)

Preserve attributes on let-else stmt
Previously the stmt attributes on something like:

```rust
#[allow(unused)]
let Some(chaenomeles) = value else { return };
```

would just be lost during parsing.


Patch:
diff --git a/src/stmt.rs b/src/stmt.rs
index bab913fe..3e2c71bd 100644
--- a/src/stmt.rs
+++ b/src/stmt.rs
@@ -50,7 +50,7 @@ ast_struct! {
 pub mod parsing {
     use super::*;
     use crate::parse::discouraged::Speculative;
-    use crate::parse::{Parse, ParseStream, Result};
+    use crate::parse::{Parse, ParseBuffer, ParseStream, Result};
     use proc_macro2::TokenStream;
 
     impl Block {
@@ -152,6 +152,7 @@ pub mod parsing {
     }
 
     fn parse_stmt(input: ParseStream, allow_nosemi: bool) -> Result<Stmt> {
+        let begin = input.fork();
         let mut attrs = input.call(Attribute::parse_outer)?;
 
         // brace-style macros; paren and bracket macros get parsed as
@@ -169,7 +170,7 @@ pub mod parsing {
         }
 
         if input.peek(Token![let]) {
-            stmt_local(input, attrs)
+            stmt_local(input, attrs, begin)
         } else if input.peek(Token![pub])
             || input.peek(Token![crate]) && !input.peek2(Token![::])
             || input.peek(Token![extern])
@@ -222,9 +223,7 @@ pub mod parsing {
         })))
     }
 
-    fn stmt_local(input: ParseStream, attrs: Vec<Attribute>) -> Result<Stmt> {
-        let begin = input.fork();
-
+    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)?;


Test patch:
diff --git a/tests/repo/mod.rs b/tests/repo/mod.rs
index 43f04258..57fc7d8a 100644
--- a/tests/repo/mod.rs
+++ b/tests/repo/mod.rs
@@ -14,9 +14,6 @@ const REVISION: &str = "e100ec5bc7cd768ec17d75448b29c9ab4a39272b";
 
 #[rustfmt::skip]
 static EXCLUDE: &[&str] = &[
-    // TODO: attributes on let-else stmt
-    "src/test/ui/let-else/let-else-allow-unused.rs",
-
     // TODO: impl ~const T {}
     // https://github.com/dtolnay/syn/issues/1051
     "src/test/ui/rfc-2632-const-trait-impl/syntax.rs",