about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorChayim Refael Friedman <chayimfr@gmail.com>2024-09-17 23:46:37 +0300
committerChayim Refael Friedman <chayimfr@gmail.com>2024-09-18 13:53:11 +0300
commit25cae947e1e44f6f9694f90be0eff7da49594b67 (patch)
treeda3a1363f2d1cdaa8e5375314b22fe9f019f3d8a /src
parentee38991676679140d37bc9ecb1532683a7e9cff6 (diff)
downloadrust-25cae947e1e44f6f9694f90be0eff7da49594b67.tar.gz
rust-25cae947e1e44f6f9694f90be0eff7da49594b67.zip
Don't complete `;` when in closure return expression
Completing it will break syntax.
Diffstat (limited to 'src')
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs82
1 files changed, 58 insertions, 24 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
index 7e5e69665f1..332a4ebd8e3 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
@@ -6,7 +6,7 @@ use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
 use ide_db::{SnippetCap, SymbolKind};
 use itertools::Itertools;
 use stdx::{format_to, to_lower_snake_case};
-use syntax::{ast, format_smolstr, AstNode, Edition, SmolStr, SyntaxKind, ToSmolStr, T};
+use syntax::{ast, format_smolstr, match_ast, AstNode, Edition, SmolStr, SyntaxKind, ToSmolStr, T};
 
 use crate::{
     context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
@@ -278,31 +278,44 @@ pub(super) fn add_call_parens<'b>(
         (snippet, "(…)")
     };
     if ret_type.is_unit() && ctx.config.add_semicolon_to_unit {
-        let next_non_trivia_token =
-            std::iter::successors(ctx.token.next_token(), |it| it.next_token())
-                .find(|it| !it.kind().is_trivia());
-        let in_match_arm = ctx.token.parent_ancestors().try_for_each(|ancestor| {
-            if ast::MatchArm::can_cast(ancestor.kind()) {
-                ControlFlow::Break(true)
-            } else if matches!(ancestor.kind(), SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR) {
-                ControlFlow::Break(false)
-            } else {
-                ControlFlow::Continue(())
+        let inside_closure_ret = ctx.token.parent_ancestors().try_for_each(|ancestor| {
+            match_ast! {
+                match ancestor {
+                    ast::BlockExpr(_) => ControlFlow::Break(false),
+                    ast::ClosureExpr(_) => ControlFlow::Break(true),
+                    _ => ControlFlow::Continue(())
+                }
             }
         });
-        // FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node.
-        let in_match_arm = match in_match_arm {
-            ControlFlow::Continue(()) => false,
-            ControlFlow::Break(it) => it,
-        };
-        let complete_token = if in_match_arm { T![,] } else { T![;] };
-        if next_non_trivia_token.map(|it| it.kind()) != Some(complete_token) {
-            cov_mark::hit!(complete_semicolon);
-            let ch = if in_match_arm { ',' } else { ';' };
-            if snippet.ends_with("$0") {
-                snippet.insert(snippet.len() - "$0".len(), ch);
-            } else {
-                snippet.push(ch);
+
+        if inside_closure_ret != ControlFlow::Break(true) {
+            let next_non_trivia_token =
+                std::iter::successors(ctx.token.next_token(), |it| it.next_token())
+                    .find(|it| !it.kind().is_trivia());
+            let in_match_arm = ctx.token.parent_ancestors().try_for_each(|ancestor| {
+                if ast::MatchArm::can_cast(ancestor.kind()) {
+                    ControlFlow::Break(true)
+                } else if matches!(ancestor.kind(), SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR)
+                {
+                    ControlFlow::Break(false)
+                } else {
+                    ControlFlow::Continue(())
+                }
+            });
+            // FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node.
+            let in_match_arm = match in_match_arm {
+                ControlFlow::Continue(()) => false,
+                ControlFlow::Break(it) => it,
+            };
+            let complete_token = if in_match_arm { T![,] } else { T![;] };
+            if next_non_trivia_token.map(|it| it.kind()) != Some(complete_token) {
+                cov_mark::hit!(complete_semicolon);
+                let ch = if in_match_arm { ',' } else { ';' };
+                if snippet.ends_with("$0") {
+                    snippet.insert(snippet.len() - "$0".len(), ch);
+                } else {
+                    snippet.push(ch);
+                }
             }
         }
     }
@@ -889,4 +902,25 @@ fn bar() {
 "#,
         );
     }
+
+    #[test]
+    fn no_semicolon_in_closure_ret() {
+        check_edit(
+            r#"foo"#,
+            r#"
+fn foo() {}
+fn baz(_: impl FnOnce()) {}
+fn bar() {
+    baz(|| fo$0);
+}
+"#,
+            r#"
+fn foo() {}
+fn baz(_: impl FnOnce()) {}
+fn bar() {
+    baz(|| foo()$0);
+}
+"#,
+        );
+    }
 }