Repository: rust-analyzer/rust-analyzer (rust)

Issue:
Unable to parse `unsafe async fn`
I am running into a reproducible bug with ra that says `Syntax Error: expected existential, fn, trait or impl` right in between the `async` and `unsafe`. Running the check manually via `cargo` seems to report no errors.

```rust
struct Foo;

impl Foo {
	pub async unsafe fn bar() {}
}
```

should reproduce it.

Reference sha `d92a15c1637860a669970142abe37d1daf5f2c73`

Pull request:
Update async unsafe fn ordering in parser
As of rust-lang/rust#61319 the correct order for functions that are both unsafe and async is: `async unsafe fn` and not `unsafe async fn`.

This commit updates the parser tests to reflect this, and corrects parsing behavior to accept the correct ordering.

Fixes #3025

Patch:
diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs
index ba08a8223b..dac6280ad8 100644
--- a/crates/ra_assists/src/assists/apply_demorgan.rs
+++ b/crates/ra_assists/src/assists/apply_demorgan.rs
@@ -31,14 +31,12 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> {
     if !cursor_in_range {
         return None;
     }
-
     let lhs = expr.lhs()?;
     let lhs_range = lhs.syntax().text_range();
-    let not_lhs = invert_boolean_expression(lhs);
-
     let rhs = expr.rhs()?;
     let rhs_range = rhs.syntax().text_range();
-    let not_rhs = invert_boolean_expression(rhs);
+    let not_lhs = invert_boolean_expression(&lhs)?;
+    let not_rhs = invert_boolean_expression(&rhs)?;
 
     ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| {
         edit.target(op_range);
@@ -79,12 +77,12 @@ mod tests {
     }
 
     #[test]
-    fn demorgan_general_case() {
-        check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x &&<|> !x) }")
+    fn demorgan_doesnt_apply_with_cursor_not_on_op() {
+        check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }")
     }
 
     #[test]
-    fn demorgan_doesnt_apply_with_cursor_not_on_op() {
-        check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }")
+    fn demorgan_doesnt_apply_when_operands_arent_negated_already() {
+        check_assist_not_applicable(apply_demorgan, "fn f() { x ||<|> x }")
     }
 }
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs
index 8f30dc5860..3169be2b94 100644
--- a/crates/ra_assists/src/assists/early_return.rs
+++ b/crates/ra_assists/src/assists/early_return.rs
@@ -10,7 +10,6 @@ use ra_syntax::{
 
 use crate::{
     assist_ctx::{Assist, AssistCtx},
-    assists::invert_if::invert_boolean_expression,
     AssistId,
 };
 
@@ -100,13 +99,9 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
         let new_block = match if_let_pat {
             None => {
                 // If.
-                let new_expr = {
-                    let then_branch =
-                        make::block_expr(once(make::expr_stmt(early_expression).into()), None);
-                    let cond = invert_boolean_expression(cond_expr);
-                    let e = make::expr_if(cond, then_branch);
-                    if_indent_level.increase_indent(e)
-                };
+                let early_expression = &(early_expression.syntax().to_string() + ";");
+                let new_expr = if_indent_level
+                    .increase_indent(make::if_expression(cond_expr, early_expression));
                 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
             }
             Some((path, bound_ident)) => {
diff --git a/crates/ra_assists/src/assists/invert_if.rs b/crates/ra_assists/src/assists/invert_if.rs
index 983392f21e..694c3642c8 100644
--- a/crates/ra_assists/src/assists/invert_if.rs
+++ b/crates/ra_assists/src/assists/invert_if.rs
@@ -1,4 +1,4 @@
-use ra_syntax::ast::{self, make, AstNode};
+use ra_syntax::ast::{self, AstNode};
 use ra_syntax::T;
 
 use crate::{Assist, AssistCtx, AssistId};
@@ -35,8 +35,8 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
     let then_node = expr.then_branch()?.syntax().clone();
 
     if let ast::ElseBranch::Block(else_block) = expr.else_branch()? {
+        let flip_cond = invert_boolean_expression(&cond)?;
         let cond_range = cond.syntax().text_range();
-        let flip_cond = invert_boolean_expression(cond);
         let else_node = else_block.syntax();
         let else_range = else_node.text_range();
         let then_range = then_node.text_range();
@@ -51,23 +51,16 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
     None
 }
 
-pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
-    if let Some(expr) = invert_special_case(&expr) {
-        return expr;
-    }
-    make::expr_prefix(T![!], expr)
-}
-
-pub(crate) fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
+pub(crate) fn invert_boolean_expression(expr: &ast::Expr) -> Option<ast::Expr> {
     match expr {
         ast::Expr::BinExpr(bin) => match bin.op_kind()? {
             ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
-            ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
             _ => None,
         },
-        ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => pe.expr(),
-        // FIXME:
-        // ast::Expr::Literal(true | false )
+        ast::Expr::PrefixExpr(pe) => match pe.op_kind()? {
+            ast::PrefixOp::Not => pe.expr(),
+            _ => None,
+        },
         _ => None,
     }
 }
@@ -97,16 +90,12 @@ mod tests {
     }
 
     #[test]
-    fn invert_if_general_case() {
-        check_assist(
-            invert_if,
-            "fn f() { i<|>f cond { 3 * 2 } else { 1 } }",
-            "fn f() { i<|>f !cond { 1 } else { 3 * 2 } }",
-        )
+    fn invert_if_doesnt_apply_with_cursor_not_on_if() {
+        check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }")
     }
 
     #[test]
-    fn invert_if_doesnt_apply_with_cursor_not_on_if() {
-        check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }")
+    fn invert_if_doesnt_apply_without_negated() {
+        check_assist_not_applicable(invert_if, "fn f() { i<|>f cond { 3 * 2 } else { 1 } }")
     }
 }
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index ceff82fda9..12961ba377 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -635,13 +635,12 @@ fn on_check_task(
 
         CheckTask::AddDiagnostic { url, diagnostic, fixes } => {
             let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?;
-            let file_id = match world_state.vfs.read().path2file(&path) {
-                Some(file) => FileId(file.0),
-                None => {
-                    log::error!("File with cargo diagnostic not found in VFS: {}", path.display());
-                    return Ok(());
-                }
-            };
+            let file_id = world_state
+                .vfs
+                .read()
+                .path2file(&path)
+                .map(|it| FileId(it.0))
+                .ok_or_else(|| format!("unknown file: {}", path.to_string_lossy()))?;
 
             task_sender
                 .send(Task::Diagnostic(DiagnosticTask::AddCheck(file_id, diagnostic, fixes)))?;
diff --git a/crates/ra_parser/src/grammar/items.rs b/crates/ra_parser/src/grammar/items.rs
index 6e23d9b72d..a88206fa8f 100644
--- a/crates/ra_parser/src/grammar/items.rs
+++ b/crates/ra_parser/src/grammar/items.rs
@@ -91,13 +91,6 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul
     // modifiers
     has_mods |= p.eat(T![const]);
 
-    // test_err unsafe_block_in_mod
-    // fn foo(){} unsafe { } fn bar(){}
-    if p.at(T![unsafe]) && p.nth(1) != T!['{'] {
-        p.eat(T![unsafe]);
-        has_mods = true;
-    }
-
     // test_err async_without_semicolon
     // fn foo() { let _ = async {} }
     if p.at(T![async]) && p.nth(1) != T!['{'] && p.nth(1) != T![move] && p.nth(1) != T![|] {
@@ -105,6 +98,13 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul
         has_mods = true;
     }
 
+    // test_err unsafe_block_in_mod
+    // fn foo(){} unsafe { } fn bar(){}
+    if p.at(T![unsafe]) && p.nth(1) != T!['{'] {
+        p.eat(T![unsafe]);
+        has_mods = true;
+    }
+
     if p.at(T![extern]) {
         has_mods = true;
         abi(p);
@@ -157,11 +157,11 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul
         // unsafe fn foo() {}
 
         // test combined_fns
-        // unsafe async fn foo() {}
+        // async unsafe fn foo() {}
         // const unsafe fn bar() {}
 
         // test_err wrong_order_fns
-        // async unsafe fn foo() {}
+        // unsafe async fn foo() {}
         // unsafe const fn bar() {}
         T![fn] => {
             fn_def(p, flavor);
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 862eb11728..02966a3ffc 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -33,21 +33,6 @@ pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordF
     }
 }
 
-pub fn block_expr(
-    stmts: impl IntoIterator<Item = ast::Stmt>,
-    tail_expr: Option<ast::Expr>,
-) -> ast::BlockExpr {
-    let mut text = "{\n".to_string();
-    for stmt in stmts.into_iter() {
-        text += &format!("    {}\n", stmt.syntax());
-    }
-    if let Some(tail_expr) = tail_expr {
-        text += &format!("    {}\n", tail_expr.syntax())
-    }
-    text += "}";
-    ast_from_text(&format!("fn f() {}", text))
-}
-
 pub fn block_from_expr(e: ast::Expr) -> ast::Block {
     return from_text(&format!("{{ {} }}", e.syntax()));
 
@@ -77,13 +62,6 @@ pub fn expr_return() -> ast::Expr {
 pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
     expr_from_text(&format!("match {} {}", expr.syntax(), match_arm_list.syntax()))
 }
-pub fn expr_if(condition: ast::Expr, then_branch: ast::BlockExpr) -> ast::Expr {
-    expr_from_text(&format!("if {} {}", condition.syntax(), then_branch.syntax()))
-}
-pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
-    let token = token(op);
-    expr_from_text(&format!("{}{}", token, expr.syntax()))
-}
 fn expr_from_text(text: &str) -> ast::Expr {
     ast_from_text(&format!("const C: () = {};", text))
 }
@@ -180,6 +158,14 @@ pub fn where_clause(preds: impl IntoIterator<Item = ast::WherePred>) -> ast::Whe
     }
 }
 
+pub fn if_expression(condition: ast::Expr, statement: &str) -> ast::IfExpr {
+    ast_from_text(&format!(
+        "fn f() {{ if !{} {{\n    {}\n}}\n}}",
+        condition.syntax().text(),
+        statement
+    ))
+}
+
 pub fn let_stmt(pattern: ast::Pat, initializer: Option<ast::Expr>) -> ast::LetStmt {
     let text = match initializer {
         Some(it) => format!("let {} = {};", pattern.syntax(), it.syntax()),
@@ -187,9 +173,6 @@ pub fn let_stmt(pattern: ast::Pat, initializer: Option<ast::Expr>) -> ast::LetSt
     };
     ast_from_text(&format!("fn f() {{ {} }}", text))
 }
-pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt {
-    ast_from_text(&format!("fn f() {{ {}; }}", expr.syntax()))
-}
 
 pub fn token(kind: SyntaxKind) -> SyntaxToken {
     tokens::SOURCE_FILE
@@ -220,7 +203,7 @@ pub mod tokens {
     use once_cell::sync::Lazy;
 
     pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> =
-        Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2, !true)\n;"));
+        Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2)\n;"));
 
     pub fn comma() -> SyntaxToken {
         SOURCE_FILE


Test patch:
diff --git a/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rs b/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rs
index 16edee95d7..731e58013b 100644
--- a/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rs
+++ b/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rs
@@ -1,2 +1,2 @@
-async unsafe fn foo() {}
+unsafe async fn foo() {}
 unsafe const fn bar() {}
diff --git a/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.txt b/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.txt
index 2ea6a566d9..289193b9e7 100644
--- a/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.txt
+++ b/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.txt
@@ -1,9 +1,9 @@
 SOURCE_FILE@[0; 50)
-  ERROR@[0; 5)
-    ASYNC_KW@[0; 5) "async"
-  WHITESPACE@[5; 6) " "
-  FN_DEF@[6; 24)
-    UNSAFE_KW@[6; 12) "unsafe"
+  ERROR@[0; 6)
+    UNSAFE_KW@[0; 6) "unsafe"
+  WHITESPACE@[6; 7) " "
+  FN_DEF@[7; 24)
+    ASYNC_KW@[7; 12) "async"
     WHITESPACE@[12; 13) " "
     FN_KW@[13; 15) "fn"
     WHITESPACE@[15; 16) " "
@@ -37,5 +37,5 @@ SOURCE_FILE@[0; 50)
         L_CURLY@[47; 48) "{"
         R_CURLY@[48; 49) "}"
   WHITESPACE@[49; 50) "\n"
-error 5: expected existential, fn, trait or impl
+error 6: expected existential, fn, trait or impl
 error 31: expected existential, fn, trait or impl
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0128_combined_fns.rs b/crates/ra_syntax/test_data/parser/inline/ok/0128_combined_fns.rs
index 46af91b827..1262871453 100644
--- a/crates/ra_syntax/test_data/parser/inline/ok/0128_combined_fns.rs
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0128_combined_fns.rs
@@ -1,2 +1,2 @@
-unsafe async fn foo() {}
+async unsafe fn foo() {}
 const unsafe fn bar() {}
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0128_combined_fns.txt b/crates/ra_syntax/test_data/parser/inline/ok/0128_combined_fns.txt
index cae75c41d3..8a972cdb25 100644
--- a/crates/ra_syntax/test_data/parser/inline/ok/0128_combined_fns.txt
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0128_combined_fns.txt
@@ -1,8 +1,8 @@
 SOURCE_FILE@[0; 50)
   FN_DEF@[0; 24)
-    UNSAFE_KW@[0; 6) "unsafe"
-    WHITESPACE@[6; 7) " "
-    ASYNC_KW@[7; 12) "async"
+    ASYNC_KW@[0; 5) "async"
+    WHITESPACE@[5; 6) " "
+    UNSAFE_KW@[6; 12) "unsafe"
     WHITESPACE@[12; 13) " "
     FN_KW@[13; 15) "fn"
     WHITESPACE@[15; 16) " "