From 37085d9dcd1b4afb1bff2c641c4c92d9882f0bad Mon Sep 17 00:00:00 2001 From: roife Date: Thu, 4 Jul 2024 23:27:57 +0800 Subject: [PATCH 01/11] feat: goto-def on keywords --- crates/ide/src/goto_definition.rs | 402 +++++++++++++++++++++++++++- crates/ide/src/navigation_target.rs | 24 +- 2 files changed, 419 insertions(+), 7 deletions(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index aa485fb63d..962e3502b4 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -2,9 +2,12 @@ use std::{iter, mem::discriminant}; use crate::{ doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget, - RangeInfo, TryToNav, + RangeInfo, TryToNav, UpmappingResult, +}; +use hir::{ + AsAssocItem, AssocItem, DescendPreference, HirFileId, InFile, MacroFileIdExt, ModuleDef, + Semantics, }; -use hir::{AsAssocItem, AssocItem, DescendPreference, MacroFileIdExt, ModuleDef, Semantics}; use ide_db::{ base_db::{AnchoredPath, FileLoader}, defs::{Definition, IdentClass}, @@ -12,7 +15,12 @@ use ide_db::{ FileId, RootDatabase, }; use itertools::Itertools; -use syntax::{ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T}; +use syntax::{ + ast::{self, HasLoopBody}, + match_ast, AstNode, AstToken, + SyntaxKind::{self, *}, + SyntaxNode, SyntaxToken, TextRange, T, +}; // Feature: Go to Definition // @@ -68,6 +76,10 @@ pub(crate) fn goto_definition( )); } + if let Some(navs) = handle_control_flow_keywords(sema, &original_token) { + return Some(RangeInfo::new(original_token.text_range(), navs)); + } + let navs = sema .descend_into_macros(DescendPreference::None, original_token.clone()) .into_iter() @@ -190,6 +202,194 @@ fn try_filter_trait_item_definition( } } +fn handle_control_flow_keywords( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, +) -> Option> { + match token.kind() { + // For `fn` / `loop` / `while` / `for` / `async`, return the keyword it self, + // so that VSCode will find the references when using `ctrl + click` + T![fn] | T![async] | T![try] | T![return] => try_find_fn_or_closure(sema, token), + T![loop] | T![while] | T![break] | T![continue] => try_find_loop(sema, token), + T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => { + try_find_loop(sema, token) + } + _ => None, + } +} + +fn try_find_fn_or_closure( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, +) -> Option> { + fn find_exit_point( + sema: &Semantics<'_, RootDatabase>, + file_id: HirFileId, + ancestors: impl Iterator, + ) -> Option> { + let db = sema.db; + + for anc in ancestors { + match_ast! { + match anc { + ast::Fn(fn_) => { + let hir_fn: hir::Function = sema.to_def(&fn_)?; + let nav = hir_fn.try_to_nav(db)?; + + // For async token, we navigate to itself, which triggers + // VSCode to find the references + let focus_token = fn_.fn_token()?; + let focus_range = InFile::new(file_id, focus_token.text_range()) + .original_node_file_range_opt(db) + .map(|(frange, _)| frange.range); + + return Some(nav.map(|it| { + if focus_range.is_some_and(|range| it.full_range.contains_range(range)) { + NavigationTarget { focus_range, ..it } + } else { + it + } + })); + }, + ast::ClosureExpr(c) => { + let pipe_tok = c.param_list().and_then(|it| it.pipe_token())?.into(); + let nav = NavigationTarget::from_expr(db, InFile::new(file_id, c.into()), pipe_tok); + return Some(nav); + }, + ast::BlockExpr(blk) => match blk.modifier() { + Some(ast::BlockModifier::Async(_)) => { + let async_tok = blk.async_token()?.into(); + let nav = NavigationTarget::from_expr(db, InFile::new(file_id, blk.into()), async_tok); + return Some(nav); + }, + Some(ast::BlockModifier::Try(_)) if cursor_token_kind != T![return] => { + let try_tok = blk.try_token()?.into(); + let nav = NavigationTarget::from_expr(db, InFile::new(file_id, blk.into()), try_tok); + return Some(nav); + }, + _ => {} + }, + _ => {} + } + } + } + None + } + + sema.descend_into_macros(DescendPreference::None, token.clone()) + .into_iter() + .filter_map(|descended| { + let file_id = sema.hir_file_for(&descended.parent()?); + + // Try to find the function in the macro file + find_exit_point(sema, file_id, descended.parent_ancestors()).or_else(|| { + // If not found, try to find it in the root file + if file_id.is_macro() { + token + .parent_ancestors() + .find(|it| ast::TokenTree::can_cast(it.kind())) + .and_then(|parent| { + let file_id = sema.hir_file_for(&parent); + find_exit_point(sema, file_id, parent.ancestors()) + }) + } else { + None + } + }) + }) + .flatten() + .collect_vec() + .into() +} + +fn try_find_loop( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, +) -> Option> { + fn find_break_point( + sema: &Semantics<'_, RootDatabase>, + file_id: HirFileId, + ancestors: impl Iterator, + lbl: &Option, + ) -> Option> { + let db = sema.db; + let label_matches = |it: Option| match lbl { + Some(lbl) => { + Some(lbl.text()) == it.and_then(|it| it.lifetime()).as_ref().map(|it| it.text()) + } + None => true, + }; + + for anc in ancestors.filter_map(ast::Expr::cast) { + match anc { + ast::Expr::LoopExpr(loop_) if label_matches(loop_.label()) => { + let expr = ast::Expr::LoopExpr(loop_.clone()); + let loop_tok = loop_.loop_token()?.into(); + let nav = NavigationTarget::from_expr(db, InFile::new(file_id, expr), loop_tok); + return Some(nav); + } + ast::Expr::WhileExpr(while_) if label_matches(while_.label()) => { + let expr = ast::Expr::WhileExpr(while_.clone()); + let while_tok = while_.while_token()?.into(); + let nav = + NavigationTarget::from_expr(db, InFile::new(file_id, expr), while_tok); + return Some(nav); + } + ast::Expr::ForExpr(for_) if label_matches(for_.label()) => { + let expr = ast::Expr::ForExpr(for_.clone()); + let for_tok = for_.for_token()?.into(); + let nav = NavigationTarget::from_expr(db, InFile::new(file_id, expr), for_tok); + return Some(nav); + } + ast::Expr::BlockExpr(blk) + if blk.label().is_some() && label_matches(blk.label()) => + { + let expr = ast::Expr::BlockExpr(blk.clone()); + let lbl_tok = blk.label().unwrap().lifetime()?.lifetime_ident_token()?.into(); + let nav = NavigationTarget::from_expr(db, InFile::new(file_id, expr), lbl_tok); + return Some(nav); + } + _ => {} + } + } + None + } + + let parent = token.parent()?; + let lbl = match_ast! { + match parent { + ast::BreakExpr(break_) => break_.lifetime(), + ast::ContinueExpr(continue_) => continue_.lifetime(), + _ => None, + } + }; + + sema.descend_into_macros(DescendPreference::None, token.clone()) + .into_iter() + .filter_map(|descended| { + let file_id = sema.hir_file_for(&descended.parent()?); + + // Try to find the function in the macro file + find_break_point(sema, file_id, descended.parent_ancestors(), &lbl).or_else(|| { + // If not found, try to find it in the root file + if file_id.is_macro() { + token + .parent_ancestors() + .find(|it| ast::TokenTree::can_cast(it.kind())) + .and_then(|parent| { + let file_id = sema.hir_file_for(&parent); + find_break_point(sema, file_id, parent.ancestors(), &lbl) + }) + } else { + None + } + }) + }) + .flatten() + .collect_vec() + .into() +} + fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec { def.try_to_nav(db).map(|it| it.collect()).unwrap_or_default() } @@ -2313,4 +2513,200 @@ pub mod prelude { "#, ); } + + #[test] + fn goto_def_on_return_kw() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + fn f() { + // ^^ + N!(i, 5, { + println!("{}", i); + return$0; + }); + + for i in 1..5 { + return; + } + (|| { + return; + })(); + } +} +"#, + ) + } + + #[test] + fn goto_def_on_return_kw_in_closure() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + fn f() { + N!(i, 5, { + println!("{}", i); + return; + }); + + for i in 1..5 { + return; + } + (|| { + // ^ + return$0; + })(); + } +} +"#, + ) + } + + #[test] + fn goto_def_on_break_kw() { + check( + r#" +fn main() { + for i in 1..5 { + // ^^^ + break$0; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_continue_kw() { + check( + r#" +fn main() { + for i in 1..5 { + // ^^^ + continue$0; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_break_kw_for_block() { + check( + r#" +fn main() { + 'a:{ + // ^^ + break$0 'a; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_break_with_label() { + check( + r#" +fn foo() { + 'outer: loop { + // ^^^^ + 'inner: loop { + 'innermost: loop { + } + break$0 'outer; + } + } +} +"#, + ); + } + + #[test] + fn goto_def_on_return_in_try() { + check( + r#" +fn main() { + fn f() { + // ^^ + try { + return$0; + } + + return; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_break_in_try() { + check( + r#" +fn main() { + for i in 1..100 { + // ^^^ + let x: Result<(), ()> = try { + break$0; + }; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_return_in_async_block() { + check( + r#" +fn main() { + async { + // ^^^^^ + return$0; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_for_kw() { + check( + r#" +fn main() { + for$0 i in 1..5 {} + // ^^^ +} +"#, + ) + } + + #[test] + fn goto_def_on_fn_kw() { + check( + r#" +fn main() { + fn$0 foo() {} + // ^^ +} +"#, + ) + } } diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index c8803850d1..f1e80ab15d 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -16,7 +16,7 @@ use ide_db::{ use stdx::never; use syntax::{ ast::{self, HasName}, - format_smolstr, AstNode, SmolStr, SyntaxNode, TextRange, ToSmolStr, + format_smolstr, AstNode, SmolStr, SyntaxElement, SyntaxNode, TextRange, ToSmolStr, }; /// `NavigationTarget` represents an element in the editor's UI which you can @@ -152,6 +152,22 @@ impl NavigationTarget { ) } + pub(crate) fn from_expr( + db: &RootDatabase, + InFile { file_id, value }: InFile, + focus_syntax: SyntaxElement, + ) -> UpmappingResult { + let name: SmolStr = "".into(); + let kind = SymbolKind::Label; + let focus_range = Some(focus_syntax.text_range()); + + orig_range_with_focus_r(db, file_id, value.syntax().text_range(), focus_range).map( + |(FileRange { file_id, range: full_range }, focus_range)| { + NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind) + }, + ) + } + fn from_syntax( file_id: FileId, name: SmolStr, @@ -710,7 +726,7 @@ impl IntoIterator for UpmappingResult { } impl UpmappingResult { - fn map(self, f: impl Fn(T) -> U) -> UpmappingResult { + pub(crate) fn map(self, f: impl Fn(T) -> U) -> UpmappingResult { UpmappingResult { call_site: f(self.call_site), def_site: self.def_site.map(f) } } } @@ -736,9 +752,9 @@ fn orig_range_with_focus_r( db: &RootDatabase, hir_file: HirFileId, value: TextRange, - name: Option, + focus_range: Option, ) -> UpmappingResult<(FileRange, Option)> { - let Some(name) = name else { return orig_range_r(db, hir_file, value) }; + let Some(name) = focus_range else { return orig_range_r(db, hir_file, value) }; let call_kind = || db.lookup_intern_macro_call(hir_file.macro_file().unwrap().macro_call_id).kind; From a93a7c24037accc21776ec290ed3243f0107fef5 Mon Sep 17 00:00:00 2001 From: roife Date: Thu, 4 Jul 2024 23:54:45 +0800 Subject: [PATCH 02/11] fix: ensure that goto-def works on fn/try/async kw --- crates/ide/src/goto_definition.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 962e3502b4..1f9ee2a317 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -226,6 +226,7 @@ fn try_find_fn_or_closure( sema: &Semantics<'_, RootDatabase>, file_id: HirFileId, ancestors: impl Iterator, + cursor_token_kind: SyntaxKind, ) -> Option> { let db = sema.db; @@ -238,7 +239,11 @@ fn try_find_fn_or_closure( // For async token, we navigate to itself, which triggers // VSCode to find the references - let focus_token = fn_.fn_token()?; + let focus_token = if matches!(cursor_token_kind, T![async]) { + fn_.async_token()? + } else { + fn_.fn_token()? + }; let focus_range = InFile::new(file_id, focus_token.text_range()) .original_node_file_range_opt(db) .map(|(frange, _)| frange.range); @@ -276,13 +281,14 @@ fn try_find_fn_or_closure( None } + let token_kind = token.kind(); sema.descend_into_macros(DescendPreference::None, token.clone()) .into_iter() .filter_map(|descended| { let file_id = sema.hir_file_for(&descended.parent()?); // Try to find the function in the macro file - find_exit_point(sema, file_id, descended.parent_ancestors()).or_else(|| { + find_exit_point(sema, file_id, descended.parent_ancestors(), token_kind).or_else(|| { // If not found, try to find it in the root file if file_id.is_macro() { token @@ -290,7 +296,7 @@ fn try_find_fn_or_closure( .find(|it| ast::TokenTree::can_cast(it.kind())) .and_then(|parent| { let file_id = sema.hir_file_for(&parent); - find_exit_point(sema, file_id, parent.ancestors()) + find_exit_point(sema, file_id, parent.ancestors(), token_kind) }) } else { None From 1bca00d1bc2ca092545a7738d39e693c0592a614 Mon Sep 17 00:00:00 2001 From: roife Date: Fri, 5 Jul 2024 00:19:43 +0800 Subject: [PATCH 03/11] fix: incorrect highlighting of try blocks with control flow kws --- crates/ide-db/src/syntax_helpers/node_ext.rs | 42 ++- crates/ide/src/highlight_related.rs | 338 ++++++++++++------- 2 files changed, 242 insertions(+), 138 deletions(-) diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs index c301e10034..37238cc61d 100644 --- a/crates/ide-db/src/syntax_helpers/node_ext.rs +++ b/crates/ide-db/src/syntax_helpers/node_ext.rs @@ -36,10 +36,35 @@ pub fn walk_expr(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr)) { }) } +pub fn is_closure_or_blk_with_modif(expr: &ast::Expr) -> bool { + match expr { + ast::Expr::BlockExpr(block_expr) => { + matches!( + block_expr.modifier(), + Some( + ast::BlockModifier::Async(_) + | ast::BlockModifier::Try(_) + | ast::BlockModifier::Const(_) + ) + ) + } + ast::Expr::ClosureExpr(_) => true, + _ => false, + } +} + /// Preorder walk all the expression's child expressions preserving events. /// If the callback returns true on an [`WalkEvent::Enter`], the subtree of the expression will be skipped. /// Note that the subtree may already be skipped due to the context analysis this function does. pub fn preorder_expr(start: &ast::Expr, cb: &mut dyn FnMut(WalkEvent) -> bool) { + preorder_expr_with_ctx_checker(start, &is_closure_or_blk_with_modif, cb); +} + +pub fn preorder_expr_with_ctx_checker( + start: &ast::Expr, + check_ctx: &dyn Fn(&ast::Expr) -> bool, + cb: &mut dyn FnMut(WalkEvent) -> bool, +) { let mut preorder = start.syntax().preorder(); while let Some(event) = preorder.next() { let node = match event { @@ -71,20 +96,7 @@ pub fn preorder_expr(start: &ast::Expr, cb: &mut dyn FnMut(WalkEvent) if ast::GenericArg::can_cast(node.kind()) { preorder.skip_subtree(); } else if let Some(expr) = ast::Expr::cast(node) { - let is_different_context = match &expr { - ast::Expr::BlockExpr(block_expr) => { - matches!( - block_expr.modifier(), - Some( - ast::BlockModifier::Async(_) - | ast::BlockModifier::Try(_) - | ast::BlockModifier::Const(_) - ) - ) - } - ast::Expr::ClosureExpr(_) => true, - _ => false, - } && expr.syntax() != start.syntax(); + let is_different_context = check_ctx(&expr) && expr.syntax() != start.syntax(); let skip = cb(WalkEvent::Enter(expr)); if skip || is_different_context { preorder.skip_subtree(); @@ -394,7 +406,7 @@ fn for_each_break_expr( } } -fn eq_label_lt(lt1: &Option, lt2: &Option) -> bool { +pub fn eq_label_lt(lt1: &Option, lt2: &Option) -> bool { lt1.as_ref().zip(lt2.as_ref()).map_or(false, |(lt, lbl)| lt.text() == lbl.text()) } diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 9865190005..a5c547c7d9 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -1,12 +1,13 @@ use std::iter; -use hir::{DescendPreference, FilePosition, FileRange, Semantics}; +use hir::{db, DescendPreference, FilePosition, FileRange, HirFileId, InFile, Semantics}; use ide_db::{ defs::{Definition, IdentClass}, helpers::pick_best_token, search::{FileReference, ReferenceCategory, SearchScope}, syntax_helpers::node_ext::{ - for_each_break_and_continue_expr, for_each_tail_expr, full_path_of_name_ref, walk_expr, + eq_label_lt, for_each_tail_expr, full_path_of_name_ref, is_closure_or_blk_with_modif, + preorder_expr_with_ctx_checker, }, FxHashSet, RootDatabase, }; @@ -15,7 +16,7 @@ use syntax::{ ast::{self, HasLoopBody}, match_ast, AstNode, SyntaxKind::{self, IDENT, INT_NUMBER}, - SyntaxToken, TextRange, T, + SyntaxToken, TextRange, WalkEvent, T, }; use crate::{navigation_target::ToNav, NavigationTarget, TryToNav}; @@ -75,12 +76,12 @@ pub(crate) fn highlight_related( highlight_exit_points(sema, token) } T![fn] | T![return] | T![->] if config.exit_points => highlight_exit_points(sema, token), - T![await] | T![async] if config.yield_points => highlight_yield_points(token), + T![await] | T![async] if config.yield_points => highlight_yield_points(sema, token), T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => { - highlight_break_points(token) + highlight_break_points(sema, token) } T![break] | T![loop] | T![while] | T![continue] if config.break_points => { - highlight_break_points(token) + highlight_break_points(sema, token) } T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id), T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id), @@ -276,50 +277,53 @@ fn highlight_references( } } -fn highlight_exit_points( +pub(crate) fn highlight_exit_points( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, ) -> Option> { fn hl( sema: &Semantics<'_, RootDatabase>, - def_ranges: [Option; 2], - body: Option, + def_range: Option, + body: ast::Expr, ) -> Option> { let mut highlights = Vec::new(); - highlights.extend( - def_ranges - .into_iter() - .flatten() - .map(|range| HighlightedRange { category: ReferenceCategory::empty(), range }), - ); - let body = body?; - walk_expr(&body, &mut |expr| match expr { - ast::Expr::ReturnExpr(expr) => { - if let Some(token) = expr.return_token() { - highlights.push(HighlightedRange { - category: ReferenceCategory::empty(), - range: token.text_range(), - }); + if let Some(range) = def_range { + highlights.push(HighlightedRange { category: ReferenceCategory::empty(), range }); + } + + WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| { + let file_id = sema.hir_file_for(expr.syntax()); + + let text_range = match &expr { + ast::Expr::TryExpr(try_) => { + try_.question_mark_token().map(|token| token.text_range()) } - } - ast::Expr::TryExpr(try_) => { - if let Some(token) = try_.question_mark_token() { - highlights.push(HighlightedRange { - category: ReferenceCategory::empty(), - range: token.text_range(), - }); + ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) + if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) => + { + Some(expr.syntax().text_range()) } - } - ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) => { - if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) { - highlights.push(HighlightedRange { - category: ReferenceCategory::empty(), - range: expr.syntax().text_range(), - }); - } - } - _ => (), + _ => None, + }; + }); + + // We should handle `return` separately because when it is used in `try` block + // it will exit the outside function instead of the block it self. + WalkExpandedExprCtx::new(sema) + .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure) + .walk(&body, &mut |_, expr| { + let file_id = sema.hir_file_for(expr.syntax()); + + let text_range = match &expr { + ast::Expr::ReturnExpr(expr) => { + expr.return_token().map(|token| token.text_range()) + } + _ => None, + }; + + }); + let tail = match body { ast::Expr::BlockExpr(b) => b.tail_expr(), e => Some(e), @@ -338,26 +342,22 @@ fn highlight_exit_points( } Some(highlights) } + for anc in token.parent_ancestors() { return match_ast! { match anc { - ast::Fn(fn_) => hl(sema, [fn_.fn_token().map(|it| it.text_range()), None], fn_.body().map(ast::Expr::BlockExpr)), + ast::Fn(fn_) => hl(sema, fn_.fn_token().map(|it| it.text_range()), ast::Expr::BlockExpr(fn_.body()?)), ast::ClosureExpr(closure) => hl( sema, - closure.param_list().map_or([None; 2], |p| [p.l_paren_token().map(|it| it.text_range()), p.r_paren_token().map(|it| it.text_range())]), - closure.body() + closure.param_list().and_then(|p| p.pipe_token()).map(|tok| tok.text_range()), + closure.body()? ), - ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) { - hl( - sema, - [block_expr.modifier().and_then(|modifier| match modifier { - ast::BlockModifier::Async(t) | ast::BlockModifier::Try(t) | ast::BlockModifier::Const(t) => Some(t.text_range()), - _ => None, - }), None], - Some(block_expr.into()) - ) - } else { - continue; + ast::BlockExpr(blk) => match blk.modifier() { + Some(ast::BlockModifier::Async(t)) => hl(sema, Some(t.text_range()), blk.into()), + Some(ast::BlockModifier::Try(t)) if token.kind() != T![return] => { + hl(sema, Some(t.text_range()), blk.into()) + }, + _ => continue, }, _ => continue, } @@ -366,44 +366,56 @@ fn highlight_exit_points( None } -fn highlight_break_points(token: SyntaxToken) -> Option> { +pub(crate) fn highlight_break_points( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, +) -> Option> { fn hl( + sema: &Semantics<'_, RootDatabase>, cursor_token_kind: SyntaxKind, - token: Option, + loop_token: Option, label: Option, - body: Option, + expr: ast::Expr, ) -> Option> { let mut highlights = Vec::new(); - let range = cover_range( - token.map(|tok| tok.text_range()), - label.as_ref().map(|it| it.syntax().text_range()), - ); - highlights.extend( - range.map(|range| HighlightedRange { category: ReferenceCategory::empty(), range }), - ); - for_each_break_and_continue_expr(label, body, &mut |expr| { - let range: Option = match (cursor_token_kind, expr) { - (T![for] | T![while] | T![loop] | T![break], ast::Expr::BreakExpr(break_)) => { - cover_range( - break_.break_token().map(|it| it.text_range()), - break_.lifetime().map(|it| it.syntax().text_range()), - ) + + let (label_range, label_lt) = label + .map_or((None, None), |label| (Some(label.syntax().text_range()), label.lifetime())); + + if let Some(range) = cover_range(loop_token.map(|tok| tok.text_range()), label_range) { + highlights.push(HighlightedRange { category: ReferenceCategory::empty(), range }) + } + + WalkExpandedExprCtx::new(sema) + .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure) + .walk(&expr, &mut |depth, expr| { + let file_id = sema.hir_file_for(expr.syntax()); + + // Only highlight the `break`s for `break` and `continue`s for `continue` + let (token, token_lt) = match expr { + ast::Expr::BreakExpr(b) if cursor_token_kind != T![continue] => { + (b.break_token(), b.lifetime()) + } + ast::Expr::ContinueExpr(c) if cursor_token_kind != T![break] => { + (c.continue_token(), c.lifetime()) + } + _ => return, + }; + + if !(depth == 1 && token_lt.is_none() || eq_label_lt(&label_lt, &token_lt)) { + return; } - ( - T![for] | T![while] | T![loop] | T![continue], - ast::Expr::ContinueExpr(continue_), - ) => cover_range( - continue_.continue_token().map(|it| it.text_range()), - continue_.lifetime().map(|it| it.syntax().text_range()), - ), - _ => None, - }; - highlights.extend( - range.map(|range| HighlightedRange { category: ReferenceCategory::empty(), range }), - ); - }); + + let text_range = cover_range( + token.map(|it| it.text_range()), + token_lt.map(|it| it.syntax().text_range()), + ); + + }); + Some(highlights) } + let parent = token.parent()?; let lbl = match_ast! { match parent { @@ -416,36 +428,27 @@ fn highlight_break_points(token: SyntaxToken) -> Option> { _ => return None, } }; - let lbl = lbl.as_ref(); - let label_matches = |def_lbl: Option| match lbl { + + let label_matches = |def_lbl: Option| match lbl.as_ref() { Some(lbl) => { Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text()) } None => true, }; - let token_kind = token.kind(); + for anc in token.parent_ancestors().flat_map(ast::Expr::cast) { - return match anc { - ast::Expr::LoopExpr(l) if label_matches(l.label()) => hl( - token_kind, - l.loop_token(), - l.label(), - l.loop_body().and_then(|it| it.stmt_list()), - ), - ast::Expr::ForExpr(f) if label_matches(f.label()) => hl( - token_kind, - f.for_token(), - f.label(), - f.loop_body().and_then(|it| it.stmt_list()), - ), - ast::Expr::WhileExpr(w) if label_matches(w.label()) => hl( - token_kind, - w.while_token(), - w.label(), - w.loop_body().and_then(|it| it.stmt_list()), - ), + return match &anc { + ast::Expr::LoopExpr(l) if label_matches(l.label()) => { + hl(sema, token.kind(), l.loop_token(), l.label(), anc) + } + ast::Expr::ForExpr(f) if label_matches(f.label()) => { + hl(sema, token.kind(), f.for_token(), f.label(), anc) + } + ast::Expr::WhileExpr(w) if label_matches(w.label()) => { + hl(sema, token.kind(), w.while_token(), w.label(), anc) + } ast::Expr::BlockExpr(e) if e.label().is_some() && label_matches(e.label()) => { - hl(token_kind, None, e.label(), e.stmt_list()) + hl(sema, token.kind(), None, e.label(), anc) } _ => continue, }; @@ -453,8 +456,12 @@ fn highlight_break_points(token: SyntaxToken) -> Option> { None } -fn highlight_yield_points(token: SyntaxToken) -> Option> { +pub(crate) fn highlight_yield_points( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, +) -> Option> { fn hl( + sema: &Semantics<'_, RootDatabase>, async_token: Option, body: Option, ) -> Option> { @@ -462,31 +469,35 @@ fn highlight_yield_points(token: SyntaxToken) -> Option> { category: ReferenceCategory::empty(), range: async_token?.text_range(), }]; - if let Some(body) = body { - walk_expr(&body, &mut |expr| { - if let ast::Expr::AwaitExpr(expr) = expr { - if let Some(token) = expr.await_token() { - highlights.push(HighlightedRange { - category: ReferenceCategory::empty(), - range: token.text_range(), - }); - } - } - }); - } + let Some(body) = body else { + return Some(highlights); + }; + + WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| { + let file_id = sema.hir_file_for(expr.syntax()); + + let token_range = match expr { + ast::Expr::AwaitExpr(expr) => expr.await_token(), + ast::Expr::ReturnExpr(expr) => expr.return_token(), + _ => None, + } + .map(|it| it.text_range()); + + }); + Some(highlights) } for anc in token.parent_ancestors() { return match_ast! { match anc { - ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)), + ast::Fn(fn_) => hl(sema, fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)), ast::BlockExpr(block_expr) => { if block_expr.async_token().is_none() { continue; } - hl(block_expr.async_token(), Some(block_expr.into())) + hl(sema, block_expr.async_token(), Some(block_expr.into())) }, - ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()), + ast::ClosureExpr(closure) => hl(sema, closure.async_token(), closure.body()), _ => continue, } }; @@ -511,6 +522,87 @@ fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSe .collect() } +/// Preorder walk all the expression's child expressions. +/// For macro calls, the callback will be called on the expanded expressions after +/// visiting the macro call itself. +struct WalkExpandedExprCtx<'a> { + sema: &'a Semantics<'a, RootDatabase>, + depth: usize, + check_ctx: &'static dyn Fn(&ast::Expr) -> bool, +} + +impl<'a> WalkExpandedExprCtx<'a> { + fn new(sema: &'a Semantics<'a, RootDatabase>) -> Self { + Self { sema, depth: 0, check_ctx: &is_closure_or_blk_with_modif } + } + + fn with_check_ctx(&self, check_ctx: &'static dyn Fn(&ast::Expr) -> bool) -> Self { + Self { check_ctx, ..*self } + } + + fn walk(&mut self, expr: &ast::Expr, cb: &mut dyn FnMut(usize, ast::Expr)) { + preorder_expr_with_ctx_checker(expr, self.check_ctx, &mut |ev: WalkEvent| { + match ev { + syntax::WalkEvent::Enter(expr) => { + cb(self.depth, expr.clone()); + + if Self::should_change_depth(&expr) { + self.depth += 1; + } + + if let ast::Expr::MacroExpr(expr) = expr { + if let Some(expanded) = expr + .macro_call() + .and_then(|call| self.sema.expand(&call)) + .and_then(ast::MacroStmts::cast) + { + self.handle_expanded(expanded, cb); + } + } + } + syntax::WalkEvent::Leave(expr) if Self::should_change_depth(&expr) => { + self.depth -= 1; + } + _ => {} + } + false + }) + } + + fn handle_expanded(&mut self, expanded: ast::MacroStmts, cb: &mut dyn FnMut(usize, ast::Expr)) { + if let Some(expr) = expanded.expr() { + self.walk(&expr, cb); + } + + for stmt in expanded.statements() { + if let ast::Stmt::ExprStmt(stmt) = stmt { + if let Some(expr) = stmt.expr() { + self.walk(&expr, cb); + } + } + } + } + + fn should_change_depth(expr: &ast::Expr) -> bool { + match expr { + ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => true, + ast::Expr::BlockExpr(blk) if blk.label().is_some() => true, + _ => false, + } + } + + fn is_async_const_block_or_closure(expr: &ast::Expr) -> bool { + match expr { + ast::Expr::BlockExpr(b) => matches!( + b.modifier(), + Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Const(_)) + ), + ast::Expr::ClosureExpr(_) => true, + _ => false, + } + } +} + #[cfg(test)] mod tests { use itertools::Itertools; From 3650b4071439413ba1df5e4f24d6a97a0b6f82c9 Mon Sep 17 00:00:00 2001 From: roife Date: Fri, 5 Jul 2024 00:23:28 +0800 Subject: [PATCH 04/11] fix: keyword highlighting in macro expansion --- crates/ide/src/highlight_related.rs | 133 ++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index a5c547c7d9..bbe14f0f8b 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -306,6 +306,9 @@ pub(crate) fn highlight_exit_points( _ => None, }; + if let Some(range) = original_range(sema.db, file_id, text_range) { + highlights.push(HighlightedRange { category: ReferenceCategory::empty(), range }) + } }); // We should handle `return` separately because when it is used in `try` block @@ -322,6 +325,10 @@ pub(crate) fn highlight_exit_points( _ => None, }; + if let Some(range) = original_range(sema.db, file_id, text_range) { + highlights + .push(HighlightedRange { category: ReferenceCategory::empty(), range }) + } }); let tail = match body { @@ -411,6 +418,10 @@ pub(crate) fn highlight_break_points( token_lt.map(|it| it.syntax().text_range()), ); + if let Some(range) = original_range(sema.db, file_id, text_range) { + highlights + .push(HighlightedRange { category: ReferenceCategory::empty(), range }) + } }); Some(highlights) @@ -483,6 +494,9 @@ pub(crate) fn highlight_yield_points( } .map(|it| it.text_range()); + if let Some(range) = original_range(sema.db, file_id, token_range) { + highlights.push(HighlightedRange { category: ReferenceCategory::empty(), range }); + } }); Some(highlights) @@ -522,6 +536,20 @@ fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSe .collect() } +fn original_range( + db: &dyn db::ExpandDatabase, + file_id: HirFileId, + text_range: Option, +) -> Option { + if text_range.is_none() || !file_id.is_macro() { + return text_range; + } + + InFile::new(file_id, text_range.unwrap()) + .original_node_file_range_opt(db) + .map(|(frange, _)| frange.range) +} + /// Preorder walk all the expression's child expressions. /// For macro calls, the callback will be called on the expanded expressions after /// visiting the macro call itself. @@ -989,6 +1017,7 @@ impl Never { } macro_rules! never { () => { never() } + // ^^^^^^^ } fn never() -> ! { loop {} } fn foo() ->$0 u32 { @@ -1815,4 +1844,108 @@ fn test() { "#, ); } + + #[test] + fn return_in_macros() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + fn f() { + // ^^ + N!(i, 5, { + println!("{}", i); + return$0; + // ^^^^^^ + }); + + for i in 1..5 { + return; + // ^^^^^^ + } + (|| { + return; + })(); + } +} +"#, + ) + } + + #[test] + fn return_in_closure() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + fn f() { + N!(i, 5, { + println!("{}", i); + return; + }); + + for i in 1..5 { + return; + } + (|| { + // ^ + return$0; + // ^^^^^^ + })(); + } +} +"#, + ) + } + + #[test] + fn return_in_try() { + check( + r#" +fn main() { + fn f() { + // ^^ + try { + return$0; + // ^^^^^^ + } + + return; + // ^^^^^^ + } +} +"#, + ) + } + + #[test] + fn break_in_try() { + check( + r#" +fn main() { + for i in 1..100 { + // ^^^ + let x: Result<(), ()> = try { + break$0; + // ^^^^^ + }; + } +} +"#, + ) + } } From 07570bac66ba25e5c0429ede7fe0bdb98cd74043 Mon Sep 17 00:00:00 2001 From: roife Date: Fri, 5 Jul 2024 00:24:00 +0800 Subject: [PATCH 05/11] feat: find references on control-flow kws --- crates/ide/src/references.rs | 254 ++++++++++++++++++++++++++++++++++- 1 file changed, 253 insertions(+), 1 deletion(-) diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 6cb0225fe2..e6900888fc 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -24,7 +24,7 @@ use syntax::{ SyntaxNode, TextRange, TextSize, T, }; -use crate::{FilePosition, NavigationTarget, TryToNav}; +use crate::{highlight_related, FilePosition, HighlightedRange, NavigationTarget, TryToNav}; #[derive(Debug, Clone)] pub struct ReferenceSearchResult { @@ -103,6 +103,11 @@ pub(crate) fn find_all_refs( } }; + // Find references for control-flow keywords. + if let Some(res) = handle_control_flow_keywords(sema, position) { + return Some(vec![res]); + } + match name_for_constructor_search(&syntax, position) { Some(name) => { let def = match NameClass::classify(sema, &name)? { @@ -296,6 +301,34 @@ fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool { }).unwrap_or(false) } +fn handle_control_flow_keywords( + sema: &Semantics<'_, RootDatabase>, + FilePosition { file_id, offset }: FilePosition, +) -> Option { + let file = sema.parse(file_id); + let token = file.syntax().token_at_offset(offset).find(|t| t.kind().is_keyword())?; + + let refs = match token.kind() { + T![fn] | T![return] | T![try] => highlight_related::highlight_exit_points(sema, token)?, + T![async] => highlight_related::highlight_yield_points(sema, token)?, + T![loop] | T![while] | T![break] | T![continue] => { + highlight_related::highlight_break_points(sema, token)? + } + T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => { + highlight_related::highlight_break_points(sema, token)? + } + _ => return None, + } + .into_iter() + .map(|HighlightedRange { range, category }| (range, category)) + .collect(); + + Some(ReferenceSearchResult { + declaration: None, + references: IntMap::from_iter([(file_id, refs)]), + }) +} + #[cfg(test)] mod tests { use expect_test::{expect, Expect}; @@ -2187,4 +2220,223 @@ fn test() { "#]], ); } + + #[test] + fn goto_ref_fn_kw() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + $0fn f() { + N!(i, 5, { + println!("{}", i); + return; + }); + + for i in 1..5 { + return; + } + + (|| { + return; + })(); + } +} +"#, + expect![[r#" + FileId(0) 136..138 + FileId(0) 207..213 + FileId(0) 264..270 + "#]], + ) + } + + #[test] + fn goto_ref_exit_points() { + check( + r#" +fn$0 foo() -> u32 { + if true { + return 0; + } + + 0?; + 0xDEAD_BEEF +} +"#, + expect![[r#" + FileId(0) 0..2 + FileId(0) 62..63 + FileId(0) 40..46 + FileId(0) 69..80 + "#]], + ); + } + + #[test] + fn test_ref_yield_points() { + check( + r#" +pub async$0 fn foo() { + let x = foo() + .await + .await; + || { 0.await }; + (async { 0.await }).await +} +"#, + expect![[r#" + FileId(0) 4..9 + FileId(0) 63..68 + FileId(0) 48..53 + FileId(0) 114..119 + "#]], + ); + } + + #[test] + fn goto_ref_for_kw() { + check( + r#" +fn main() { + $0for i in 1..5 { + break; + continue; + } +} +"#, + expect![[r#" + FileId(0) 16..19 + FileId(0) 40..45 + FileId(0) 55..63 + "#]], + ) + } + + #[test] + fn goto_ref_on_break_kw() { + check( + r#" +fn main() { + for i in 1..5 { + $0break; + continue; + } +} +"#, + expect![[r#" + FileId(0) 16..19 + FileId(0) 40..45 + "#]], + ) + } + + #[test] + fn goto_ref_on_break_kw_for_block() { + check( + r#" +fn main() { + 'a:{ + $0break 'a; + } +} +"#, + expect![[r#" + FileId(0) 16..19 + FileId(0) 29..37 + "#]], + ) + } + + #[test] + fn goto_ref_on_break_with_label() { + check( + r#" +fn foo() { + 'outer: loop { + break; + 'inner: loop { + 'innermost: loop { + } + $0break 'outer; + break; + } + break; + } +} +"#, + expect![[r#" + FileId(0) 15..27 + FileId(0) 39..44 + FileId(0) 127..139 + FileId(0) 178..183 + "#]], + ); + } + + #[test] + fn goto_ref_on_return_in_try() { + check( + r#" +fn main() { + fn f() { + try { + $0return; + } + + return; + } + return; +} +"#, + expect![[r#" + FileId(0) 16..18 + FileId(0) 51..57 + FileId(0) 78..84 + "#]], + ) + } + + #[test] + fn goto_ref_on_break_in_try() { + check( + r#" +fn main() { + for i in 1..100 { + let x: Result<(), ()> = try { + $0break; + }; + } +} +"#, + expect![[r#" + FileId(0) 16..19 + FileId(0) 84..89 + "#]], + ) + } + + #[test] + fn goto_ref_on_return_in_async_block() { + check( + r#" +fn main() { + $0async { + return; + } +} +"#, + expect![[r#" + FileId(0) 16..21 + FileId(0) 32..38 + "#]], + ) + } } From 22c59240801ce33746ba9d8d831d52477ebb5d62 Mon Sep 17 00:00:00 2001 From: roife Date: Sun, 7 Jul 2024 21:05:09 +0800 Subject: [PATCH 06/11] fix: navigate to label directly when perform 'goto-def' on control-flow kw See https://github.com/rust-lang/rust-analyzer/pull/17542#discussion_r1667656190 --- crates/ide/src/goto_definition.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 1f9ee2a317..0a4fbc874a 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -351,8 +351,8 @@ fn try_find_loop( if blk.label().is_some() && label_matches(blk.label()) => { let expr = ast::Expr::BlockExpr(blk.clone()); - let lbl_tok = blk.label().unwrap().lifetime()?.lifetime_ident_token()?.into(); - let nav = NavigationTarget::from_expr(db, InFile::new(file_id, expr), lbl_tok); + let lbl = blk.label().unwrap().syntax().clone().into(); + let nav = NavigationTarget::from_expr(db, InFile::new(file_id, expr), lbl); return Some(nav); } _ => {} @@ -2618,7 +2618,7 @@ fn main() { r#" fn main() { 'a:{ - // ^^ + // ^^^ break$0 'a; } } From ae6e8d56d44c388b3dff5adbac3585de5553cce9 Mon Sep 17 00:00:00 2001 From: roife Date: Mon, 8 Jul 2024 04:18:32 +0800 Subject: [PATCH 07/11] use token_ancestors_with_macros to simplify goto-def on kw --- crates/ide/src/goto_definition.rs | 122 ++++++++++------------------ crates/ide/src/highlight_related.rs | 6 +- 2 files changed, 45 insertions(+), 83 deletions(-) diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 0a4fbc874a..284fd7861d 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -5,8 +5,7 @@ use crate::{ RangeInfo, TryToNav, UpmappingResult, }; use hir::{ - AsAssocItem, AssocItem, DescendPreference, HirFileId, InFile, MacroFileIdExt, ModuleDef, - Semantics, + AsAssocItem, AssocItem, DescendPreference, InFile, MacroFileIdExt, ModuleDef, Semantics, }; use ide_db::{ base_db::{AnchoredPath, FileLoader}, @@ -15,11 +14,12 @@ use ide_db::{ FileId, RootDatabase, }; use itertools::Itertools; +use span::FileRange; use syntax::{ - ast::{self, HasLoopBody}, + ast::{self, HasLoopBody, Label}, match_ast, AstNode, AstToken, - SyntaxKind::{self, *}, - SyntaxNode, SyntaxToken, TextRange, T, + SyntaxKind::*, + SyntaxToken, TextRange, T, }; // Feature: Go to Definition @@ -224,30 +224,28 @@ fn try_find_fn_or_closure( ) -> Option> { fn find_exit_point( sema: &Semantics<'_, RootDatabase>, - file_id: HirFileId, - ancestors: impl Iterator, - cursor_token_kind: SyntaxKind, + token: SyntaxToken, ) -> Option> { let db = sema.db; - for anc in ancestors { + for anc in sema.token_ancestors_with_macros(token.clone()) { + let file_id = sema.hir_file_for(&anc); match_ast! { match anc { ast::Fn(fn_) => { - let hir_fn: hir::Function = sema.to_def(&fn_)?; - let nav = hir_fn.try_to_nav(db)?; - + let fn_: ast::Fn = fn_; + let nav = sema.to_def(&fn_)?.try_to_nav(db)?; // For async token, we navigate to itself, which triggers // VSCode to find the references - let focus_token = if matches!(cursor_token_kind, T![async]) { + let focus_token = if matches!(token.kind(), T![async]) { fn_.async_token()? } else { fn_.fn_token()? }; - let focus_range = InFile::new(file_id, focus_token.text_range()) - .original_node_file_range_opt(db) - .map(|(frange, _)| frange.range); + let focus_range = InFile::new(file_id, focus_token.text_range()) + .original_node_file_range_opt(db) + .map(|(frange, _)| frange.range); return Some(nav.map(|it| { if focus_range.is_some_and(|range| it.full_range.contains_range(range)) { NavigationTarget { focus_range, ..it } @@ -258,21 +256,26 @@ fn try_find_fn_or_closure( }, ast::ClosureExpr(c) => { let pipe_tok = c.param_list().and_then(|it| it.pipe_token())?.into(); - let nav = NavigationTarget::from_expr(db, InFile::new(file_id, c.into()), pipe_tok); + let c_infile = InFile::new(file_id, c.into()); + let nav = NavigationTarget::from_expr(db, c_infile, pipe_tok); return Some(nav); }, - ast::BlockExpr(blk) => match blk.modifier() { - Some(ast::BlockModifier::Async(_)) => { - let async_tok = blk.async_token()?.into(); - let nav = NavigationTarget::from_expr(db, InFile::new(file_id, blk.into()), async_tok); - return Some(nav); - }, - Some(ast::BlockModifier::Try(_)) if cursor_token_kind != T![return] => { - let try_tok = blk.try_token()?.into(); - let nav = NavigationTarget::from_expr(db, InFile::new(file_id, blk.into()), try_tok); - return Some(nav); - }, - _ => {} + ast::BlockExpr(blk) => { + match blk.modifier() { + Some(ast::BlockModifier::Async(_)) => { + let async_tok = blk.async_token()?.into(); + let blk_infile = InFile::new(file_id, blk.into()); + let nav = NavigationTarget::from_expr(db, blk_infile, async_tok); + return Some(nav); + }, + Some(ast::BlockModifier::Try(_)) if token.kind() != T![return] => { + let try_tok = blk.try_token()?.into(); + let blk_infile = InFile::new(file_id, blk.into()); + let nav = NavigationTarget::from_expr(db, blk_infile, try_tok); + return Some(nav); + }, + _ => {} + } }, _ => {} } @@ -281,28 +284,9 @@ fn try_find_fn_or_closure( None } - let token_kind = token.kind(); sema.descend_into_macros(DescendPreference::None, token.clone()) .into_iter() - .filter_map(|descended| { - let file_id = sema.hir_file_for(&descended.parent()?); - - // Try to find the function in the macro file - find_exit_point(sema, file_id, descended.parent_ancestors(), token_kind).or_else(|| { - // If not found, try to find it in the root file - if file_id.is_macro() { - token - .parent_ancestors() - .find(|it| ast::TokenTree::can_cast(it.kind())) - .and_then(|parent| { - let file_id = sema.hir_file_for(&parent); - find_exit_point(sema, file_id, parent.ancestors(), token_kind) - }) - } else { - None - } - }) - }) + .filter_map(|descended| find_exit_point(sema, descended)) .flatten() .collect_vec() .into() @@ -314,19 +298,13 @@ fn try_find_loop( ) -> Option> { fn find_break_point( sema: &Semantics<'_, RootDatabase>, - file_id: HirFileId, - ancestors: impl Iterator, - lbl: &Option, + token: SyntaxToken, + label_matches: impl Fn(Option