Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-db/src/syntax_helpers/node_ext.rs')
-rw-r--r--crates/ide-db/src/syntax_helpers/node_ext.rs81
1 files changed, 80 insertions, 1 deletions
diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs
index e1d140730e..94ecf6a02d 100644
--- a/crates/ide-db/src/syntax_helpers/node_ext.rs
+++ b/crates/ide-db/src/syntax_helpers/node_ext.rs
@@ -1,12 +1,15 @@
//! Various helper functions to work with SyntaxNodes.
use std::ops::ControlFlow;
+use either::Either;
use itertools::Itertools;
use parser::T;
use span::Edition;
use syntax::{
- AstNode, AstToken, Preorder, RustLanguage, WalkEvent,
+ AstNode, AstToken, Direction, Preorder, RustLanguage, SyntaxToken, WalkEvent,
+ algo::non_trivia_sibling,
ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind},
+ syntax_editor::Element,
};
pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
@@ -419,6 +422,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,
@@ -500,3 +545,37 @@ pub fn macro_call_for_string_token(string: &ast::String) -> Option<MacroCall> {
let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?;
Some(macro_call)
}
+
+pub fn is_in_macro_matcher(token: &SyntaxToken) -> bool {
+ let Some(macro_def) = token
+ .parent_ancestors()
+ .map_while(Either::<ast::TokenTree, ast::Macro>::cast)
+ .find_map(Either::right)
+ else {
+ return false;
+ };
+ let range = token.text_range();
+ let Some(body) = (match macro_def {
+ ast::Macro::MacroDef(macro_def) => {
+ if let Some(args) = macro_def.args() {
+ return args.syntax().text_range().contains_range(range);
+ }
+ macro_def.body()
+ }
+ ast::Macro::MacroRules(macro_rules) => macro_rules.token_tree(),
+ }) else {
+ return false;
+ };
+ if !body.syntax().text_range().contains_range(range) {
+ return false;
+ }
+ body.token_trees_and_tokens().filter_map(|tt| tt.into_node()).any(|tt| {
+ let Some(next) = non_trivia_sibling(tt.syntax().syntax_element(), Direction::Next) else {
+ return false;
+ };
+ let Some(next_next) = next.next_sibling_or_token() else { return false };
+ next.kind() == T![=]
+ && next_next.kind() == T![>]
+ && tt.syntax().text_range().contains_range(range)
+ })
+}