Skip to content

Commit 00037c1

Browse files
committed
Auto merge of #18132 - ChayimFriedman2:fix-closure-semi, r=Veykril
fix: Don't complete `;` when in closure return expression Completing it will break syntax. Fixes #18130.
2 parents d4689f1 + c9758da commit 00037c1

File tree

2 files changed

+100
-32
lines changed

2 files changed

+100
-32
lines changed

crates/ide-completion/src/context.rs

+60-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod analysis;
44
#[cfg(test)]
55
mod tests;
66

7-
use std::iter;
7+
use std::{iter, ops::ControlFlow};
88

99
use hir::{
1010
HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo,
@@ -15,7 +15,7 @@ use ide_db::{
1515
};
1616
use syntax::{
1717
ast::{self, AttrKind, NameOrNameRef},
18-
AstNode, Edition, SmolStr,
18+
match_ast, AstNode, Edition, SmolStr,
1919
SyntaxKind::{self, *},
2020
SyntaxToken, TextRange, TextSize, T,
2121
};
@@ -457,6 +457,16 @@ pub(crate) struct CompletionContext<'a> {
457457
///
458458
/// Here depth will be 2
459459
pub(crate) depth_from_crate_root: usize,
460+
461+
/// Whether and how to complete semicolon for unit-returning functions.
462+
pub(crate) complete_semicolon: CompleteSemicolon,
463+
}
464+
465+
#[derive(Debug)]
466+
pub(crate) enum CompleteSemicolon {
467+
DoNotComplete,
468+
CompleteSemi,
469+
CompleteComma,
460470
}
461471

462472
impl CompletionContext<'_> {
@@ -735,6 +745,53 @@ impl<'a> CompletionContext<'a> {
735745

736746
let depth_from_crate_root = iter::successors(module.parent(db), |m| m.parent(db)).count();
737747

748+
let complete_semicolon = if config.add_semicolon_to_unit {
749+
let inside_closure_ret = token.parent_ancestors().try_for_each(|ancestor| {
750+
match_ast! {
751+
match ancestor {
752+
ast::BlockExpr(_) => ControlFlow::Break(false),
753+
ast::ClosureExpr(_) => ControlFlow::Break(true),
754+
_ => ControlFlow::Continue(())
755+
}
756+
}
757+
});
758+
759+
if inside_closure_ret == ControlFlow::Break(true) {
760+
CompleteSemicolon::DoNotComplete
761+
} else {
762+
let next_non_trivia_token =
763+
std::iter::successors(token.next_token(), |it| it.next_token())
764+
.find(|it| !it.kind().is_trivia());
765+
let in_match_arm = token.parent_ancestors().try_for_each(|ancestor| {
766+
if ast::MatchArm::can_cast(ancestor.kind()) {
767+
ControlFlow::Break(true)
768+
} else if matches!(
769+
ancestor.kind(),
770+
SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR
771+
) {
772+
ControlFlow::Break(false)
773+
} else {
774+
ControlFlow::Continue(())
775+
}
776+
});
777+
// FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node.
778+
let in_match_arm = match in_match_arm {
779+
ControlFlow::Continue(()) => false,
780+
ControlFlow::Break(it) => it,
781+
};
782+
let complete_token = if in_match_arm { T![,] } else { T![;] };
783+
if next_non_trivia_token.map(|it| it.kind()) == Some(complete_token) {
784+
CompleteSemicolon::DoNotComplete
785+
} else if in_match_arm {
786+
CompleteSemicolon::CompleteComma
787+
} else {
788+
CompleteSemicolon::CompleteSemi
789+
}
790+
}
791+
} else {
792+
CompleteSemicolon::DoNotComplete
793+
};
794+
738795
let ctx = CompletionContext {
739796
sema,
740797
scope,
@@ -752,6 +809,7 @@ impl<'a> CompletionContext<'a> {
752809
qualifier_ctx,
753810
locals,
754811
depth_from_crate_root,
812+
complete_semicolon,
755813
};
756814
Some((ctx, analysis))
757815
}

crates/ide-completion/src/render/function.rs

+40-30
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
//! Renderer for function calls.
22
3-
use std::ops::ControlFlow;
4-
53
use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
64
use ide_db::{SnippetCap, SymbolKind};
75
use itertools::Itertools;
86
use stdx::{format_to, to_lower_snake_case};
9-
use syntax::{ast, format_smolstr, AstNode, Edition, SmolStr, SyntaxKind, ToSmolStr, T};
7+
use syntax::{format_smolstr, AstNode, Edition, SmolStr, ToSmolStr};
108

119
use crate::{
12-
context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
10+
context::{
11+
CompleteSemicolon, CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind,
12+
},
1313
item::{
1414
Builder, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevanceFn,
1515
CompletionRelevanceReturnType, CompletionRelevanceTraitInfo,
@@ -277,32 +277,21 @@ pub(super) fn add_call_parens<'b>(
277277

278278
(snippet, "(…)")
279279
};
280-
if ret_type.is_unit() && ctx.config.add_semicolon_to_unit {
281-
let next_non_trivia_token =
282-
std::iter::successors(ctx.token.next_token(), |it| it.next_token())
283-
.find(|it| !it.kind().is_trivia());
284-
let in_match_arm = ctx.token.parent_ancestors().try_for_each(|ancestor| {
285-
if ast::MatchArm::can_cast(ancestor.kind()) {
286-
ControlFlow::Break(true)
287-
} else if matches!(ancestor.kind(), SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR) {
288-
ControlFlow::Break(false)
289-
} else {
290-
ControlFlow::Continue(())
291-
}
292-
});
293-
// FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node.
294-
let in_match_arm = match in_match_arm {
295-
ControlFlow::Continue(()) => false,
296-
ControlFlow::Break(it) => it,
297-
};
298-
let complete_token = if in_match_arm { T![,] } else { T![;] };
299-
if next_non_trivia_token.map(|it| it.kind()) != Some(complete_token) {
300-
cov_mark::hit!(complete_semicolon);
301-
let ch = if in_match_arm { ',' } else { ';' };
302-
if snippet.ends_with("$0") {
303-
snippet.insert(snippet.len() - "$0".len(), ch);
304-
} else {
305-
snippet.push(ch);
280+
if ret_type.is_unit() {
281+
match ctx.complete_semicolon {
282+
CompleteSemicolon::DoNotComplete => {}
283+
CompleteSemicolon::CompleteSemi | CompleteSemicolon::CompleteComma => {
284+
cov_mark::hit!(complete_semicolon);
285+
let ch = if matches!(ctx.complete_semicolon, CompleteSemicolon::CompleteComma) {
286+
','
287+
} else {
288+
';'
289+
};
290+
if snippet.ends_with("$0") {
291+
snippet.insert(snippet.len() - "$0".len(), ch);
292+
} else {
293+
snippet.push(ch);
294+
}
306295
}
307296
}
308297
}
@@ -886,6 +875,27 @@ fn bar() {
886875
v => foo()$0,
887876
}
888877
}
878+
"#,
879+
);
880+
}
881+
882+
#[test]
883+
fn no_semicolon_in_closure_ret() {
884+
check_edit(
885+
r#"foo"#,
886+
r#"
887+
fn foo() {}
888+
fn baz(_: impl FnOnce()) {}
889+
fn bar() {
890+
baz(|| fo$0);
891+
}
892+
"#,
893+
r#"
894+
fn foo() {}
895+
fn baz(_: impl FnOnce()) {}
896+
fn bar() {
897+
baz(|| foo()$0);
898+
}
889899
"#,
890900
);
891901
}

0 commit comments

Comments
 (0)