From 07cf8cbbbcc66e62121ba75aed582de4417ecfee Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 16 Sep 2025 16:48:19 +0800 Subject: [PATCH] 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; } } ``` --- crates/ide-completion/src/completions/expr.rs | 17 +++++--- crates/ide-completion/src/context.rs | 1 + crates/ide-completion/src/context/analysis.rs | 20 ++++++---- crates/ide-completion/src/tests/expression.rs | 39 +++++++++++++++++++ 4 files changed, 65 insertions(+), 12 deletions(-) diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 2c7d9e97c44e..ee90a80058bd 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -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, in_condition, incomplete_let, @@ -67,6 +66,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, .. @@ -404,14 +404,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 { diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 007475688d20..f8767706c26c 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -155,6 +155,7 @@ pub(crate) struct PathExprCtx<'db> { pub(crate) is_func_update: Option, pub(crate) self_param: Option, pub(crate) innermost_ret_ty: Option>, + pub(crate) innermost_breakable_ty: Option>, pub(crate) impl_: Option, /// Whether this expression occurs in match arm guard position: before the /// fat arrow token diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index b33a547dee97..fbf4b159dbf5 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -902,7 +902,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()).0 } }); return Some(make_res(kind)); }, @@ -917,7 +917,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()).0 } }); return Some(make_res(kind)); }, @@ -1223,7 +1223,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); 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); @@ -1277,6 +1277,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(); @@ -1307,6 +1312,7 @@ fn classify_name_ref<'db>( after_amp, is_func_update, innermost_ret_ty, + innermost_breakable_ty, self_param, in_value, incomplete_let, @@ -1854,7 +1860,7 @@ 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) -> (BreakableKind, Option) { node.ancestors() .take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR) .find_map(|it| { @@ -1863,15 +1869,15 @@ fn is_in_breakable(node: &SyntaxNode) -> BreakableKind { 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::BlockExpr(it) => return it.label().map(|_| (BreakableKind::Block, Some(it.syntax().clone()))), _ => return None, } }; loop_body .filter(|it| it.syntax().text_range().contains_range(node.text_range())) - .map(|_| breakable) + .map(|_| (breakable, Some(it))) }) - .unwrap_or(BreakableKind::None) + .unwrap_or((BreakableKind::None, None)) } fn is_in_block(node: &SyntaxNode) -> bool { diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index 79137104b568..7509cc3e4791 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -1092,6 +1092,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(