Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #20521 from A4-Tacks/suggest-break-expr
Add BreakExpr completion suggest
Lukas Wirth 4 months ago
parent d1a3407 · parent ac01f88 · commit 31d2019
-rw-r--r--crates/ide-completion/src/context/analysis.rs13
-rw-r--r--crates/ide-completion/src/context/tests.rs119
-rw-r--r--crates/ide-db/src/syntax_helpers/node_ext.rs42
-rw-r--r--crates/ide/src/goto_definition.rs53
-rw-r--r--crates/ide/src/highlight_related.rs6
5 files changed, 177 insertions, 56 deletions
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index ce26d3806c..49fb36ad04 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -2,7 +2,9 @@
use std::iter;
use hir::{ExpandResult, InFile, Semantics, Type, TypeInfo, Variant};
-use ide_db::{RootDatabase, active_parameter::ActiveParameter};
+use ide_db::{
+ RootDatabase, active_parameter::ActiveParameter, syntax_helpers::node_ext::find_loops,
+};
use itertools::Either;
use stdx::always;
use syntax::{
@@ -779,6 +781,12 @@ fn expected_type_and_name<'db>(
});
(ty, None)
},
+ ast::BreakExpr(it) => {
+ let ty = it.break_token()
+ .and_then(|it| find_loops(sema, &it)?.next())
+ .and_then(|expr| sema.type_of_expr(&expr));
+ (ty.map(TypeInfo::original), None)
+ },
ast::ClosureExpr(it) => {
let ty = sema.type_of_expr(&it.into());
ty.and_then(|ty| ty.original.as_callable(sema.db))
@@ -2059,7 +2067,8 @@ fn prev_special_biased_token_at_trivia(mut token: SyntaxToken) -> SyntaxToken {
| T![|]
| T![return]
| T![break]
- | T![continue] = prev.kind()
+ | T![continue]
+ | T![lifetime_ident] = prev.kind()
{
token = prev
}
diff --git a/crates/ide-completion/src/context/tests.rs b/crates/ide-completion/src/context/tests.rs
index 09a9b6f112..e97d9720e3 100644
--- a/crates/ide-completion/src/context/tests.rs
+++ b/crates/ide-completion/src/context/tests.rs
@@ -636,6 +636,125 @@ fn foo() {
}
#[test]
+fn expected_type_break_expr_in_loop() {
+ check_expected_type_and_name(
+ r#"
+enum State { Stop }
+fn foo() {
+ let _x: State = loop {
+ {
+ break State::Stop;
+ break $0;
+ }
+ };
+}
+"#,
+ expect![[r#"ty: State, name: ?"#]],
+ );
+
+ check_expected_type_and_name(
+ r#"
+enum State { Stop }
+fn foo() {
+ let _x: State = 'a: loop {
+ {
+ break State::Stop;
+ break $0;
+ }
+ };
+}
+"#,
+ expect![[r#"ty: State, name: ?"#]],
+ );
+
+ check_expected_type_and_name(
+ r#"
+enum State { Stop }
+fn foo() {
+ let _x: State = 'a: loop {
+ while true {
+ break $0;
+ }
+ };
+}
+"#,
+ expect![[r#"ty: (), name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_break_expr_in_labeled_loop() {
+ check_expected_type_and_name(
+ r#"
+enum State { Stop }
+fn foo() {
+ let _x: State = 'a: loop {
+ let _y: i32 = loop {
+ {
+ break 'a State::Stop;
+ break 'a $0;
+ }
+ };
+ };
+}
+"#,
+ expect![[r#"ty: State, name: ?"#]],
+ );
+
+ check_expected_type_and_name(
+ r#"
+enum State { Stop }
+fn foo() {
+ let _x: State = 'a: loop {
+ let _y: i32 = loop {
+ while true {
+ break 'a State::Stop;
+ break 'a $0;
+ }
+ };
+ };
+}
+"#,
+ expect![[r#"ty: State, name: ?"#]],
+ );
+
+ check_expected_type_and_name(
+ r#"
+enum State { Stop }
+fn foo() {
+ 'a: while true {
+ let _x: State = loop {
+ break State::Stop;
+ break 'a $0;
+ };
+ }
+}
+"#,
+ expect![[r#"ty: (), name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_break_expr_in_labeled_block() {
+ check_expected_type_and_name(
+ r#"
+enum State { Stop }
+fn foo() {
+ let _x: State = 'a: {
+ let _y: i32 = 'b: {
+ {
+ break 'a State::Stop;
+ break 'a $0;
+ };
+ };
+ };
+}
+"#,
+ expect![[r#"ty: State, name: ?"#]],
+ );
+}
+
+#[test]
fn expected_type_logic_op() {
check_expected_type_and_name(
r#"
diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs
index e1d140730e..acce066b83 100644
--- a/crates/ide-db/src/syntax_helpers/node_ext.rs
+++ b/crates/ide-db/src/syntax_helpers/node_ext.rs
@@ -419,6 +419,48 @@ pub fn eq_label_lt(lt1: &Option<ast::Lifetime>, lt2: &Option<ast::Lifetime>) ->
lt1.as_ref().zip(lt2.as_ref()).is_some_and(|(lt, lbl)| lt.text() == lbl.text())
}
+/// Find the loop or block to break or continue, multiple results may be caused by macros.
+pub fn find_loops(
+ sema: &hir::Semantics<'_, crate::RootDatabase>,
+ token: &syntax::SyntaxToken,
+) -> Option<impl Iterator<Item = ast::Expr>> {
+ let parent = token.parent()?;
+ let lbl = syntax::match_ast! {
+ match parent {
+ ast::BreakExpr(break_) => break_.lifetime(),
+ ast::ContinueExpr(continue_) => continue_.lifetime(),
+ _ => None,
+ }
+ };
+ let label_matches =
+ move |it: Option<ast::Label>| match (lbl.as_ref(), it.and_then(|it| it.lifetime())) {
+ (Some(lbl), Some(it)) => lbl.text() == it.text(),
+ (None, _) => true,
+ (Some(_), None) => false,
+ };
+
+ let find_ancestors = move |token| {
+ for anc in sema.token_ancestors_with_macros(token).filter_map(ast::Expr::cast) {
+ let node = match &anc {
+ ast::Expr::LoopExpr(loop_) if label_matches(loop_.label()) => anc,
+ ast::Expr::WhileExpr(while_) if label_matches(while_.label()) => anc,
+ ast::Expr::ForExpr(for_) if label_matches(for_.label()) => anc,
+ ast::Expr::BlockExpr(blk)
+ if blk.label().is_some() && label_matches(blk.label()) =>
+ {
+ anc
+ }
+ _ => continue,
+ };
+
+ return Some(node);
+ }
+ None
+ };
+
+ sema.descend_into_macros(token.clone()).into_iter().filter_map(find_ancestors).into()
+}
+
struct TreeWithDepthIterator {
preorder: Preorder<RustLanguage>,
depth: u32,
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index b00aa4d0ca..c0a7438081 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -16,15 +16,12 @@ use ide_db::{
defs::{Definition, IdentClass},
famous_defs::FamousDefs,
helpers::pick_best_token,
+ syntax_helpers::node_ext::find_loops,
};
use itertools::Itertools;
use span::FileId;
use syntax::{
- AstNode, AstToken,
- SyntaxKind::*,
- SyntaxNode, SyntaxToken, T, TextRange,
- ast::{self, HasLoopBody},
- match_ast,
+ AstNode, AstToken, SyntaxKind::*, SyntaxNode, SyntaxToken, T, TextRange, ast, match_ast,
};
#[derive(Debug)]
@@ -510,51 +507,6 @@ fn nav_for_branch_exit_points(
Some(navs)
}
-pub(crate) fn find_loops(
- sema: &Semantics<'_, RootDatabase>,
- token: &SyntaxToken,
-) -> Option<Vec<ast::Expr>> {
- let parent = token.parent()?;
- let lbl = match_ast! {
- match parent {
- ast::BreakExpr(break_) => break_.lifetime(),
- ast::ContinueExpr(continue_) => continue_.lifetime(),
- _ => None,
- }
- };
- let label_matches =
- |it: Option<ast::Label>| match (lbl.as_ref(), it.and_then(|it| it.lifetime())) {
- (Some(lbl), Some(it)) => lbl.text() == it.text(),
- (None, _) => true,
- (Some(_), None) => false,
- };
-
- let find_ancestors = |token: SyntaxToken| {
- for anc in sema.token_ancestors_with_macros(token).filter_map(ast::Expr::cast) {
- let node = match &anc {
- ast::Expr::LoopExpr(loop_) if label_matches(loop_.label()) => anc,
- ast::Expr::WhileExpr(while_) if label_matches(while_.label()) => anc,
- ast::Expr::ForExpr(for_) if label_matches(for_.label()) => anc,
- ast::Expr::BlockExpr(blk)
- if blk.label().is_some() && label_matches(blk.label()) =>
- {
- anc
- }
- _ => continue,
- };
-
- return Some(node);
- }
- None
- };
-
- sema.descend_into_macros(token.clone())
- .into_iter()
- .filter_map(find_ancestors)
- .collect_vec()
- .into()
-}
-
fn nav_for_break_points(
sema: &Semantics<'_, RootDatabase>,
token: &SyntaxToken,
@@ -562,7 +514,6 @@ fn nav_for_break_points(
let db = sema.db;
let navs = find_loops(sema, token)?
- .into_iter()
.filter_map(|expr| {
let file_id = sema.hir_file_for(expr.syntax());
let expr_in_file = InFile::new(file_id, expr.clone());
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index acba573cc0..fce033382b 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -7,8 +7,8 @@ use ide_db::{
helpers::pick_best_token,
search::{FileReference, ReferenceCategory, SearchScope},
syntax_helpers::node_ext::{
- eq_label_lt, for_each_tail_expr, full_path_of_name_ref, is_closure_or_blk_with_modif,
- preorder_expr_with_ctx_checker,
+ eq_label_lt, find_loops, for_each_tail_expr, full_path_of_name_ref,
+ is_closure_or_blk_with_modif, preorder_expr_with_ctx_checker,
},
};
use syntax::{
@@ -562,7 +562,7 @@ pub(crate) fn highlight_break_points(
Some(highlights)
}
- let Some(loops) = goto_definition::find_loops(sema, &token) else {
+ let Some(loops) = find_loops(sema, &token) else {
return FxHashMap::default();
};