Skip to content

Commit 1d2c84d

Browse files
committed
Add break value completion support
```rust fn foo() -> i32 { loop { $0 } } ``` **Before this PR**: ```rust fn foo() -> i32 { loop { break; } } ``` **After this PR**: ```rust fn foo() -> i32 { loop { break $0; } } ```
1 parent 4794fb9 commit 1d2c84d

File tree

6 files changed

+72
-25
lines changed

6 files changed

+72
-25
lines changed

src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use syntax::ast;
99
use crate::{
1010
CompletionContext, Completions,
1111
completions::record::add_default_update,
12-
context::{BreakableKind, PathCompletionCtx, PathExprCtx, Qualified},
12+
context::{PathCompletionCtx, PathExprCtx, Qualified},
1313
};
1414

1515
struct PathCallback<'a, F> {
@@ -57,7 +57,6 @@ pub(crate) fn complete_expr_path(
5757

5858
let &PathExprCtx {
5959
in_block_expr,
60-
in_breakable,
6160
after_if_expr,
6261
before_else_kw,
6362
in_condition,
@@ -68,6 +67,7 @@ pub(crate) fn complete_expr_path(
6867
after_amp,
6968
ref is_func_update,
7069
ref innermost_ret_ty,
70+
ref innermost_breakable_ty,
7171
ref impl_,
7272
in_match_guard,
7373
..
@@ -405,14 +405,21 @@ pub(crate) fn complete_expr_path(
405405
add_keyword("mut", "mut ");
406406
}
407407

408-
if in_breakable != BreakableKind::None {
408+
if let Some(loop_ty) = innermost_breakable_ty {
409409
if in_block_expr {
410410
add_keyword("continue", "continue;");
411-
add_keyword("break", "break;");
412411
} else {
413412
add_keyword("continue", "continue");
414-
add_keyword("break", "break");
415413
}
414+
add_keyword(
415+
"break",
416+
match (loop_ty.is_unit(), in_block_expr) {
417+
(true, true) => "break;",
418+
(true, false) => "break",
419+
(false, true) => "break $0;",
420+
(false, false) => "break $0",
421+
},
422+
);
416423
}
417424

418425
if let Some(ret_ty) = innermost_ret_ty {

src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ pub(crate) fn complete_postfix(
291291
)
292292
.add_to(acc, ctx.db);
293293

294-
if let BreakableKind::Block | BreakableKind::Loop = expr_ctx.in_breakable {
294+
if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable {
295295
postfix_snippet(
296296
"break",
297297
"break expr",

src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,7 @@ fn complete_fields(
135135
receiver: None,
136136
receiver_ty: None,
137137
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false },
138-
ctx: DotAccessExprCtx {
139-
in_block_expr: false,
140-
in_breakable: crate::context::BreakableKind::None,
141-
},
138+
ctx: DotAccessExprCtx { in_block_expr: false, in_breakable: None },
142139
},
143140
None,
144141
field,

src/tools/rust-analyzer/crates/ide-completion/src/context.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ pub(crate) struct AttrCtx {
143143
#[derive(Debug, PartialEq, Eq)]
144144
pub(crate) struct PathExprCtx<'db> {
145145
pub(crate) in_block_expr: bool,
146-
pub(crate) in_breakable: BreakableKind,
146+
pub(crate) in_breakable: Option<BreakableKind>,
147147
pub(crate) after_if_expr: bool,
148148
pub(crate) before_else_kw: bool,
149149
/// Whether this expression is the direct condition of an if or while expression
@@ -157,6 +157,7 @@ pub(crate) struct PathExprCtx<'db> {
157157
pub(crate) is_func_update: Option<ast::RecordExpr>,
158158
pub(crate) self_param: Option<hir::SelfParam>,
159159
pub(crate) innermost_ret_ty: Option<hir::Type<'db>>,
160+
pub(crate) innermost_breakable_ty: Option<hir::Type<'db>>,
160161
pub(crate) impl_: Option<ast::Impl>,
161162
/// Whether this expression occurs in match arm guard position: before the
162163
/// fat arrow token
@@ -412,12 +413,11 @@ pub(crate) enum DotAccessKind {
412413
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413414
pub(crate) struct DotAccessExprCtx {
414415
pub(crate) in_block_expr: bool,
415-
pub(crate) in_breakable: BreakableKind,
416+
pub(crate) in_breakable: Option<BreakableKind>,
416417
}
417418

418419
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
419420
pub(crate) enum BreakableKind {
420-
None,
421421
Loop,
422422
For,
423423
While,

src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ fn classify_name_ref<'db>(
926926
receiver_ty,
927927
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
928928
receiver,
929-
ctx: DotAccessExprCtx { in_block_expr: is_in_block(field.syntax()), in_breakable: is_in_breakable(field.syntax()) }
929+
ctx: DotAccessExprCtx { in_block_expr: is_in_block(field.syntax()), in_breakable: is_in_breakable(field.syntax()).unzip().0 }
930930
});
931931
return Some(make_res(kind));
932932
},
@@ -941,7 +941,7 @@ fn classify_name_ref<'db>(
941941
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
942942
kind: DotAccessKind::Method { has_parens },
943943
receiver,
944-
ctx: DotAccessExprCtx { in_block_expr: is_in_block(method.syntax()), in_breakable: is_in_breakable(method.syntax()) }
944+
ctx: DotAccessExprCtx { in_block_expr: is_in_block(method.syntax()), in_breakable: is_in_breakable(method.syntax()).unzip().0 }
945945
});
946946
return Some(make_res(kind));
947947
},
@@ -1229,7 +1229,7 @@ fn classify_name_ref<'db>(
12291229
let make_path_kind_expr = |expr: ast::Expr| {
12301230
let it = expr.syntax();
12311231
let in_block_expr = is_in_block(it);
1232-
let in_loop_body = is_in_breakable(it);
1232+
let (in_loop_body, innermost_breakable) = is_in_breakable(it).unzip();
12331233
let after_if_expr = after_if_expr(it.clone());
12341234
let ref_expr_parent =
12351235
path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast);
@@ -1283,6 +1283,11 @@ fn classify_name_ref<'db>(
12831283
None => (None, None),
12841284
}
12851285
};
1286+
let innermost_breakable_ty = innermost_breakable
1287+
.and_then(ast::Expr::cast)
1288+
.and_then(|expr| find_node_in_file_compensated(sema, original_file, &expr))
1289+
.and_then(|expr| sema.type_of_expr(&expr))
1290+
.map(|ty| if ty.original.is_never() { ty.adjusted() } else { ty.original() });
12861291
let is_func_update = func_update_record(it);
12871292
let in_condition = is_in_condition(&expr);
12881293
let after_incomplete_let = after_incomplete_let(it.clone()).is_some();
@@ -1316,6 +1321,7 @@ fn classify_name_ref<'db>(
13161321
after_amp,
13171322
is_func_update,
13181323
innermost_ret_ty,
1324+
innermost_breakable_ty,
13191325
self_param,
13201326
in_value,
13211327
incomplete_let,
@@ -1865,24 +1871,22 @@ fn is_in_token_of_for_loop(path: &ast::Path) -> bool {
18651871
.unwrap_or(false)
18661872
}
18671873

1868-
fn is_in_breakable(node: &SyntaxNode) -> BreakableKind {
1874+
fn is_in_breakable(node: &SyntaxNode) -> Option<(BreakableKind, SyntaxNode)> {
18691875
node.ancestors()
18701876
.take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR)
18711877
.find_map(|it| {
18721878
let (breakable, loop_body) = match_ast! {
18731879
match it {
1874-
ast::ForExpr(it) => (BreakableKind::For, it.loop_body()),
1875-
ast::WhileExpr(it) => (BreakableKind::While, it.loop_body()),
1876-
ast::LoopExpr(it) => (BreakableKind::Loop, it.loop_body()),
1877-
ast::BlockExpr(it) => return it.label().map(|_| BreakableKind::Block),
1880+
ast::ForExpr(it) => (BreakableKind::For, it.loop_body()?),
1881+
ast::WhileExpr(it) => (BreakableKind::While, it.loop_body()?),
1882+
ast::LoopExpr(it) => (BreakableKind::Loop, it.loop_body()?),
1883+
ast::BlockExpr(it) => return it.label().map(|_| (BreakableKind::Block, it.syntax().clone())),
18781884
_ => return None,
18791885
}
18801886
};
1881-
loop_body
1882-
.filter(|it| it.syntax().text_range().contains_range(node.text_range()))
1883-
.map(|_| breakable)
1887+
loop_body.syntax().text_range().contains_range(node.text_range())
1888+
.then_some((breakable, it))
18841889
})
1885-
.unwrap_or(BreakableKind::None)
18861890
}
18871891

18881892
fn is_in_block(node: &SyntaxNode) -> bool {

src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,45 @@ fn return_value_no_block() {
10901090
);
10911091
}
10921092

1093+
#[test]
1094+
fn break_unit_block() {
1095+
check_edit("break", r#"fn f() { loop { break; $0 } }"#, r#"fn f() { loop { break; break; } }"#);
1096+
check_edit("break", r#"fn f() { loop { $0 } }"#, r#"fn f() { loop { break; } }"#);
1097+
}
1098+
1099+
#[test]
1100+
fn break_unit_no_block() {
1101+
check_edit(
1102+
"break",
1103+
r#"fn f() { loop { break; match () { () => $0 } } }"#,
1104+
r#"fn f() { loop { break; match () { () => break } } }"#,
1105+
);
1106+
1107+
check_edit(
1108+
"break",
1109+
r#"fn f() { loop { match () { () => $0 } } }"#,
1110+
r#"fn f() { loop { match () { () => break } } }"#,
1111+
);
1112+
}
1113+
1114+
#[test]
1115+
fn break_value_block() {
1116+
check_edit(
1117+
"break",
1118+
r#"fn f() -> i32 { loop { $0 } }"#,
1119+
r#"fn f() -> i32 { loop { break $0; } }"#,
1120+
);
1121+
}
1122+
1123+
#[test]
1124+
fn break_value_no_block() {
1125+
check_edit(
1126+
"break",
1127+
r#"fn f() -> i32 { loop { match () { () => $0 } } }"#,
1128+
r#"fn f() -> i32 { loop { match () { () => break $0 } } }"#,
1129+
);
1130+
}
1131+
10931132
#[test]
10941133
fn else_completion_after_if() {
10951134
check(

0 commit comments

Comments
 (0)