Repository: dtolnay/async-trait (Rust)

Issue:
Nested `_` and `..` patterns under 2018 edition can trigger rust_2021_compatibility warning
The code generated by async-trait should never exhibit edition sensitive behavior differences. The fact that async-trait involves the use of move closures internally is an implementation detail. If the behavior is sensitive to edition of the caller's crate, that is a bug in async-trait.

```rust
#![warn(rust_2021_compatibility)]

use async_trait::async_trait;

pub struct Droppable;

impl Drop for Droppable {
    fn drop(&mut self) {}
}

pub struct Tuple<T, U>(T, U);

#[async_trait]
pub trait Trait {
    async fn create(arg: Tuple<Droppable, i32>);
}

pub struct Struct;
```

```rust
#[async_trait]
impl Trait for Struct {
    async fn create(Tuple(_, _int): Tuple<Droppable, i32>) {}
}
```

```console
warning: changes to closure capture in Rust 2021 will affect drop order
  --> src/main.rs:22:60
   |
22 |     async fn create(Tuple(_, _int): Tuple<Droppable, i32>) {}
   |                     --------------                         ^-
   |                     |                                       |
   |                     |                                       in Rust 2018, `__arg0` is dropped here, but in Rust 2021, only `__arg0.1` will be dropped here as part of the closure
   |                     in Rust 2018, this closure captures all of `__arg0`, but in Rust 2021, it will only capture `__arg0.1`
   |
   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/disjoint-capture-in-closures.html>
note: the lint level is defined here
  --> src/main.rs:1:9
   |
1  | #![warn(rust_2021_compatibility)]
   |         ^^^^^^^^^^^^^^^^^^^^^^^
   = note: `#[warn(rust_2021_incompatible_closure_captures)]` implied by `#[warn(rust_2021_compatibility)]`
help: add a dummy let to cause `__arg0` to be fully captured
   |
22 |     async fn create(Tuple(_, _int): Tuple<Droppable, i32>) { let _ = &__arg0;}
   |                                                              ++++++++++++++++
```

```rust
#[async_trait]
impl Trait for Struct {
    async fn create(Tuple { 1: _int, .. }: Tuple<Droppable, i32>) {}
}
```

```console
warning: changes to closure capture in Rust 2021 will affect drop order
  --> src/main.rs:22:67
   |
22 |     async fn create(Tuple { 1: _int, .. }: Tuple<Droppable, i32>) {}
   |                     ---------------------                         ^-
   |                     |                                              |
   |                     |                                              in Rust 2018, `__arg0` is dropped here, but in Rust 2021, only `__arg0.1` will be dropped here as part of the closure
   |                     in Rust 2018, this closure captures all of `__arg0`, but in Rust 2021, it will only capture `__arg0.1`
   |
   = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/disjoint-capture-in-closures.html>
note: the lint level is defined here
  --> src/main.rs:1:9
   |
1  | #![warn(rust_2021_compatibility)]
   |         ^^^^^^^^^^^^^^^^^^^^^^^
   = note: `#[warn(rust_2021_incompatible_closure_captures)]` implied by `#[warn(rust_2021_compatibility)]`
help: add a dummy let to cause `__arg0` to be fully captured
   |
22 |     async fn create(Tuple { 1: _int, .. }: Tuple<Droppable, i32>) { let _ = &__arg0;}
   |                                                                     ++++++++++++++++
```


Discussion:

Patch:
diff --git a/src/expand.rs b/src/expand.rs
index 7747e68..c2cfacb 100644
--- a/src/expand.rs
+++ b/src/expand.rs
@@ -403,7 +403,10 @@ fn transform_block(context: Context, sig: &mut Signature, block: &mut Block) {
                     } else {
                         quote! {
                             #(#attrs)*
-                            let #pat = #ident;
+                            let #pat = {
+                                let #ident = #ident;
+                                #ident
+                            };
                         }
                     }
                 }


Test patch:
diff --git a/tests/test.rs b/tests/test.rs
index 211387a..2d7dc65 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -2,6 +2,7 @@
     async_trait_nightly_testing,
     feature(min_specialization, type_alias_impl_trait)
 )]
+#![deny(rust_2021_compatibility)]
 #![allow(
     clippy::let_unit_value,
     clippy::missing_panics_doc,
@@ -1523,3 +1524,35 @@ pub mod issue232 {
         async fn take_ref(&self, (_a, _b, _c): &(T, T, T)) {}
     }
 }
+
+// https://github.com/dtolnay/async-trait/issues/234
+pub mod issue234 {
+    use async_trait::async_trait;
+
+    pub struct Droppable;
+
+    impl Drop for Droppable {
+        fn drop(&mut self) {}
+    }
+
+    pub struct Tuple<T, U>(T, U);
+
+    #[async_trait]
+    pub trait Trait {
+        async fn f(arg: Tuple<Droppable, i32>);
+    }
+
+    pub struct UnderscorePattern;
+
+    #[async_trait]
+    impl Trait for UnderscorePattern {
+        async fn f(Tuple(_, _int): Tuple<Droppable, i32>) {}
+    }
+
+    pub struct DotDotPattern;
+
+    #[async_trait]
+    impl Trait for DotDotPattern {
+        async fn f(Tuple { 1: _int, .. }: Tuple<Droppable, i32>) {}
+    }
+}