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;
}
}
```
This commit is contained in:
parent
4794fb9562
commit
1d2c84d57e
6 changed files with 72 additions and 25 deletions
|
|
@ -9,7 +9,7 @@ use syntax::ast;
|
|||
use crate::{
|
||||
CompletionContext, Completions,
|
||||
completions::record::add_default_update,
|
||||
context::{BreakableKind, PathCompletionCtx, PathExprCtx, Qualified},
|
||||
context::{PathCompletionCtx, PathExprCtx, Qualified},
|
||||
};
|
||||
|
||||
struct PathCallback<'a, F> {
|
||||
|
|
@ -57,7 +57,6 @@ pub(crate) fn complete_expr_path(
|
|||
|
||||
let &PathExprCtx {
|
||||
in_block_expr,
|
||||
in_breakable,
|
||||
after_if_expr,
|
||||
before_else_kw,
|
||||
in_condition,
|
||||
|
|
@ -68,6 +67,7 @@ pub(crate) fn complete_expr_path(
|
|||
after_amp,
|
||||
ref is_func_update,
|
||||
ref innermost_ret_ty,
|
||||
ref innermost_breakable_ty,
|
||||
ref impl_,
|
||||
in_match_guard,
|
||||
..
|
||||
|
|
@ -405,14 +405,21 @@ pub(crate) fn complete_expr_path(
|
|||
add_keyword("mut", "mut ");
|
||||
}
|
||||
|
||||
if in_breakable != BreakableKind::None {
|
||||
if let Some(loop_ty) = innermost_breakable_ty {
|
||||
if in_block_expr {
|
||||
add_keyword("continue", "continue;");
|
||||
add_keyword("break", "break;");
|
||||
} else {
|
||||
add_keyword("continue", "continue");
|
||||
add_keyword("break", "break");
|
||||
}
|
||||
add_keyword(
|
||||
"break",
|
||||
match (loop_ty.is_unit(), in_block_expr) {
|
||||
(true, true) => "break;",
|
||||
(true, false) => "break",
|
||||
(false, true) => "break $0;",
|
||||
(false, false) => "break $0",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(ret_ty) = innermost_ret_ty {
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ pub(crate) fn complete_postfix(
|
|||
)
|
||||
.add_to(acc, ctx.db);
|
||||
|
||||
if let BreakableKind::Block | BreakableKind::Loop = expr_ctx.in_breakable {
|
||||
if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable {
|
||||
postfix_snippet(
|
||||
"break",
|
||||
"break expr",
|
||||
|
|
|
|||
|
|
@ -135,10 +135,7 @@ fn complete_fields(
|
|||
receiver: None,
|
||||
receiver_ty: None,
|
||||
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false },
|
||||
ctx: DotAccessExprCtx {
|
||||
in_block_expr: false,
|
||||
in_breakable: crate::context::BreakableKind::None,
|
||||
},
|
||||
ctx: DotAccessExprCtx { in_block_expr: false, in_breakable: None },
|
||||
},
|
||||
None,
|
||||
field,
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ pub(crate) struct AttrCtx {
|
|||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct PathExprCtx<'db> {
|
||||
pub(crate) in_block_expr: bool,
|
||||
pub(crate) in_breakable: BreakableKind,
|
||||
pub(crate) in_breakable: Option<BreakableKind>,
|
||||
pub(crate) after_if_expr: bool,
|
||||
pub(crate) before_else_kw: bool,
|
||||
/// Whether this expression is the direct condition of an if or while expression
|
||||
|
|
@ -157,6 +157,7 @@ pub(crate) struct PathExprCtx<'db> {
|
|||
pub(crate) is_func_update: Option<ast::RecordExpr>,
|
||||
pub(crate) self_param: Option<hir::SelfParam>,
|
||||
pub(crate) innermost_ret_ty: Option<hir::Type<'db>>,
|
||||
pub(crate) innermost_breakable_ty: Option<hir::Type<'db>>,
|
||||
pub(crate) impl_: Option<ast::Impl>,
|
||||
/// Whether this expression occurs in match arm guard position: before the
|
||||
/// fat arrow token
|
||||
|
|
@ -412,12 +413,11 @@ pub(crate) enum DotAccessKind {
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct DotAccessExprCtx {
|
||||
pub(crate) in_block_expr: bool,
|
||||
pub(crate) in_breakable: BreakableKind,
|
||||
pub(crate) in_breakable: Option<BreakableKind>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum BreakableKind {
|
||||
None,
|
||||
Loop,
|
||||
For,
|
||||
While,
|
||||
|
|
|
|||
|
|
@ -926,7 +926,7 @@ fn classify_name_ref<'db>(
|
|||
receiver_ty,
|
||||
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
|
||||
receiver,
|
||||
ctx: DotAccessExprCtx { in_block_expr: is_in_block(field.syntax()), in_breakable: is_in_breakable(field.syntax()) }
|
||||
ctx: DotAccessExprCtx { in_block_expr: is_in_block(field.syntax()), in_breakable: is_in_breakable(field.syntax()).unzip().0 }
|
||||
});
|
||||
return Some(make_res(kind));
|
||||
},
|
||||
|
|
@ -941,7 +941,7 @@ fn classify_name_ref<'db>(
|
|||
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
|
||||
kind: DotAccessKind::Method { has_parens },
|
||||
receiver,
|
||||
ctx: DotAccessExprCtx { in_block_expr: is_in_block(method.syntax()), in_breakable: is_in_breakable(method.syntax()) }
|
||||
ctx: DotAccessExprCtx { in_block_expr: is_in_block(method.syntax()), in_breakable: is_in_breakable(method.syntax()).unzip().0 }
|
||||
});
|
||||
return Some(make_res(kind));
|
||||
},
|
||||
|
|
@ -1229,7 +1229,7 @@ fn classify_name_ref<'db>(
|
|||
let make_path_kind_expr = |expr: ast::Expr| {
|
||||
let it = expr.syntax();
|
||||
let in_block_expr = is_in_block(it);
|
||||
let in_loop_body = is_in_breakable(it);
|
||||
let (in_loop_body, innermost_breakable) = is_in_breakable(it).unzip();
|
||||
let after_if_expr = after_if_expr(it.clone());
|
||||
let ref_expr_parent =
|
||||
path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast);
|
||||
|
|
@ -1283,6 +1283,11 @@ fn classify_name_ref<'db>(
|
|||
None => (None, None),
|
||||
}
|
||||
};
|
||||
let innermost_breakable_ty = innermost_breakable
|
||||
.and_then(ast::Expr::cast)
|
||||
.and_then(|expr| find_node_in_file_compensated(sema, original_file, &expr))
|
||||
.and_then(|expr| sema.type_of_expr(&expr))
|
||||
.map(|ty| if ty.original.is_never() { ty.adjusted() } else { ty.original() });
|
||||
let is_func_update = func_update_record(it);
|
||||
let in_condition = is_in_condition(&expr);
|
||||
let after_incomplete_let = after_incomplete_let(it.clone()).is_some();
|
||||
|
|
@ -1316,6 +1321,7 @@ fn classify_name_ref<'db>(
|
|||
after_amp,
|
||||
is_func_update,
|
||||
innermost_ret_ty,
|
||||
innermost_breakable_ty,
|
||||
self_param,
|
||||
in_value,
|
||||
incomplete_let,
|
||||
|
|
@ -1865,24 +1871,22 @@ fn is_in_token_of_for_loop(path: &ast::Path) -> bool {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn is_in_breakable(node: &SyntaxNode) -> BreakableKind {
|
||||
fn is_in_breakable(node: &SyntaxNode) -> Option<(BreakableKind, SyntaxNode)> {
|
||||
node.ancestors()
|
||||
.take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR)
|
||||
.find_map(|it| {
|
||||
let (breakable, loop_body) = match_ast! {
|
||||
match it {
|
||||
ast::ForExpr(it) => (BreakableKind::For, it.loop_body()),
|
||||
ast::WhileExpr(it) => (BreakableKind::While, it.loop_body()),
|
||||
ast::LoopExpr(it) => (BreakableKind::Loop, it.loop_body()),
|
||||
ast::BlockExpr(it) => return it.label().map(|_| BreakableKind::Block),
|
||||
ast::ForExpr(it) => (BreakableKind::For, it.loop_body()?),
|
||||
ast::WhileExpr(it) => (BreakableKind::While, it.loop_body()?),
|
||||
ast::LoopExpr(it) => (BreakableKind::Loop, it.loop_body()?),
|
||||
ast::BlockExpr(it) => return it.label().map(|_| (BreakableKind::Block, it.syntax().clone())),
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
loop_body
|
||||
.filter(|it| it.syntax().text_range().contains_range(node.text_range()))
|
||||
.map(|_| breakable)
|
||||
loop_body.syntax().text_range().contains_range(node.text_range())
|
||||
.then_some((breakable, it))
|
||||
})
|
||||
.unwrap_or(BreakableKind::None)
|
||||
}
|
||||
|
||||
fn is_in_block(node: &SyntaxNode) -> bool {
|
||||
|
|
|
|||
|
|
@ -1090,6 +1090,45 @@ fn return_value_no_block() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_unit_block() {
|
||||
check_edit("break", r#"fn f() { loop { break; $0 } }"#, r#"fn f() { loop { break; break; } }"#);
|
||||
check_edit("break", r#"fn f() { loop { $0 } }"#, r#"fn f() { loop { break; } }"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_unit_no_block() {
|
||||
check_edit(
|
||||
"break",
|
||||
r#"fn f() { loop { break; match () { () => $0 } } }"#,
|
||||
r#"fn f() { loop { break; match () { () => break } } }"#,
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"break",
|
||||
r#"fn f() { loop { match () { () => $0 } } }"#,
|
||||
r#"fn f() { loop { match () { () => break } } }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_value_block() {
|
||||
check_edit(
|
||||
"break",
|
||||
r#"fn f() -> i32 { loop { $0 } }"#,
|
||||
r#"fn f() -> i32 { loop { break $0; } }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn break_value_no_block() {
|
||||
check_edit(
|
||||
"break",
|
||||
r#"fn f() -> i32 { loop { match () { () => $0 } } }"#,
|
||||
r#"fn f() -> i32 { loop { match () { () => break $0 } } }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn else_completion_after_if() {
|
||||
check(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue