Don't complete ; when in closure return expression

Completing it will break syntax.
This commit is contained in:
Chayim Refael Friedman 2024-09-17 23:46:37 +03:00
parent f4aca78c92
commit 7f023154f0

View file

@ -6,7 +6,7 @@ use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
use ide_db::{SnippetCap, SymbolKind}; use ide_db::{SnippetCap, SymbolKind};
use itertools::Itertools; use itertools::Itertools;
use stdx::{format_to, to_lower_snake_case}; 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::{ use crate::{
context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind}, context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
@ -278,31 +278,44 @@ pub(super) fn add_call_parens<'b>(
(snippet, "(…)") (snippet, "(…)")
}; };
if ret_type.is_unit() && ctx.config.add_semicolon_to_unit { if ret_type.is_unit() && ctx.config.add_semicolon_to_unit {
let next_non_trivia_token = let inside_closure_ret = ctx.token.parent_ancestors().try_for_each(|ancestor| {
std::iter::successors(ctx.token.next_token(), |it| it.next_token()) match_ast! {
.find(|it| !it.kind().is_trivia()); match ancestor {
let in_match_arm = ctx.token.parent_ancestors().try_for_each(|ancestor| { ast::BlockExpr(_) => ControlFlow::Break(false),
if ast::MatchArm::can_cast(ancestor.kind()) { ast::ClosureExpr(_) => ControlFlow::Break(true),
ControlFlow::Break(true) _ => ControlFlow::Continue(())
} 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 { if inside_closure_ret != ControlFlow::Break(true) {
ControlFlow::Continue(()) => false, let next_non_trivia_token =
ControlFlow::Break(it) => it, std::iter::successors(ctx.token.next_token(), |it| it.next_token())
}; .find(|it| !it.kind().is_trivia());
let complete_token = if in_match_arm { T![,] } else { T![;] }; let in_match_arm = ctx.token.parent_ancestors().try_for_each(|ancestor| {
if next_non_trivia_token.map(|it| it.kind()) != Some(complete_token) { if ast::MatchArm::can_cast(ancestor.kind()) {
cov_mark::hit!(complete_semicolon); ControlFlow::Break(true)
let ch = if in_match_arm { ',' } else { ';' }; } else if matches!(ancestor.kind(), SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR)
if snippet.ends_with("$0") { {
snippet.insert(snippet.len() - "$0".len(), ch); ControlFlow::Break(false)
} else { } else {
snippet.push(ch); 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);
}
} }
} }
} }
@ -886,6 +899,27 @@ fn bar() {
v => foo()$0, v => foo()$0,
} }
} }
"#,
);
}
#[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);
}
"#, "#,
); );
} }