Repository: rust-lang/rustfmt (rust)

Issue:
Rustfmt breaks static async closure
```rust
#![feature(async_closure)]

macro_rules! expr {
    ($e:expr) => {};
}

expr!(static async || {});
```

As of current master (7b8303d47968a0e1fcad4b143e0ff4b5e512f37b), `cargo run --bin rustfmt -- repro.rs --emit=stdout --edition=2021` creates the following diff:

```diff
- expr!(static async || {});
+ expr!(async static || {});
```

which fails to compile:

```console
error: expected one of `move`, `|`, or `||`, found keyword `static`
 --> repro.rs:7:13
  |
4 |     ($e:expr) => {};
  |      ------- while parsing argument for this `expr` macro fragment
...
7 | expr!(async static || {});
  |             ^^^^^^ expected one of `move`, `|`, or `||`
```

Pull request:
Fix static async closure qualifier order
Fixes #5149.

Previously rustfmt would produce `async static || ...` which is not valid syntax. The ordering that rustc accepts is `static async || ...`.

Patch:
diff --git a/src/closures.rs b/src/closures.rs
index 34d73a77..e688db1c 100644
--- a/src/closures.rs
+++ b/src/closures.rs
@@ -236,21 +236,21 @@ fn rewrite_closure_fn_decl(
     context: &RewriteContext<'_>,
     shape: Shape,
 ) -> Option<(String, usize)> {
-    let is_async = if asyncness.is_async() { "async " } else { "" };
-    let mover = if capture == ast::CaptureBy::Value {
-        "move "
+    let immovable = if movability == ast::Movability::Static {
+        "static "
     } else {
         ""
     };
-    let immovable = if movability == ast::Movability::Static {
-        "static "
+    let is_async = if asyncness.is_async() { "async " } else { "" };
+    let mover = if capture == ast::CaptureBy::Value {
+        "move "
     } else {
         ""
     };
     // 4 = "|| {".len(), which is overconservative when the closure consists of
     // a single expression.
     let nested_shape = shape
-        .shrink_left(is_async.len() + mover.len() + immovable.len())?
+        .shrink_left(immovable.len() + is_async.len() + mover.len())?
         .sub_width(4)?;
 
     // 1 = |
@@ -288,7 +288,7 @@ fn rewrite_closure_fn_decl(
         .tactic(tactic)
         .preserve_newline(true);
     let list_str = write_list(&item_vec, &fmt)?;
-    let mut prefix = format!("{}{}{}|{}|", is_async, immovable, mover, list_str);
+    let mut prefix = format!("{}{}{}|{}|", immovable, is_async, mover, list_str);
 
     if !ret_str.is_empty() {
         if prefix.contains('\n') {


Test patch:
diff --git a/tests/source/async_block.rs b/tests/source/async_block.rs
index 3de51a08..18cb4fb5 100644
--- a/tests/source/async_block.rs
+++ b/tests/source/async_block.rs
@@ -32,4 +32,20 @@ fn baz() {
             Ok(())
         },
     );
+
+    spawn(
+        a,
+        static async || {
+            action();
+            Ok(())
+        },
+    );
+
+    spawn(
+        a,
+        static async move || {
+            action();
+            Ok(())
+        },
+    );
 }
diff --git a/tests/target/async_block.rs b/tests/target/async_block.rs
index 258dd937..137d849c 100644
--- a/tests/target/async_block.rs
+++ b/tests/target/async_block.rs
@@ -22,4 +22,14 @@ fn baz() {
         action();
         Ok(())
     });
+
+    spawn(a, static async || {
+        action();
+        Ok(())
+    });
+
+    spawn(a, static async move || {
+        action();
+        Ok(())
+    });
 }