1use std::ops::ControlFlow;
3
4use either::Either;
5use itertools::Itertools;
6use parser::T;
7use span::Edition;
8use syntax::{
9 AstNode, AstToken, Direction, Preorder, RustLanguage, SyntaxToken, WalkEvent,
10 algo::non_trivia_sibling,
11 ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind},
12 syntax_editor::Element,
13};
14
15pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
16 if let ast::Expr::PathExpr(expr) = expr {
17 let path = expr.path()?;
18 path.as_single_name_ref()
19 } else {
20 None
21 }
22}
23
24pub fn full_path_of_name_ref(name_ref: &ast::NameRef) -> Option<ast::Path> {
25 let mut ancestors = name_ref.syntax().ancestors();
26 let _ = ancestors.next()?; let _ = ancestors.next().filter(|it| ast::PathSegment::can_cast(it.kind()))?; ancestors.take_while(|it| ast::Path::can_cast(it.kind())).last().and_then(ast::Path::cast)
29}
30
31pub fn block_as_lone_tail(block: &ast::BlockExpr) -> Option<ast::Expr> {
32 block.statements().next().is_none().then(|| block.tail_expr()).flatten()
33}
34
35pub fn walk_expr(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr)) {
37 preorder_expr(expr, &mut |ev| {
38 if let WalkEvent::Enter(expr) = ev {
39 cb(expr);
40 }
41 false
42 })
43}
44
45pub fn is_closure_or_blk_with_modif(expr: &ast::Expr) -> bool {
46 match expr {
47 ast::Expr::BlockExpr(block_expr) => {
48 matches!(
49 block_expr.modifier(),
50 Some(
51 ast::BlockModifier::Async(_)
52 | ast::BlockModifier::Try { .. }
53 | ast::BlockModifier::Const(_)
54 )
55 )
56 }
57 ast::Expr::ClosureExpr(_) => true,
58 _ => false,
59 }
60}
61
62pub fn preorder_expr(start: &ast::Expr, cb: &mut dyn FnMut(WalkEvent<ast::Expr>) -> bool) {
66 preorder_expr_with_ctx_checker(start, &is_closure_or_blk_with_modif, cb);
67}
68
69pub fn preorder_expr_with_ctx_checker(
70 start: &ast::Expr,
71 check_ctx: &dyn Fn(&ast::Expr) -> bool,
72 cb: &mut dyn FnMut(WalkEvent<ast::Expr>) -> bool,
73) {
74 let mut preorder = start.syntax().preorder();
75 while let Some(event) = preorder.next() {
76 let node = match event {
77 WalkEvent::Enter(node) => node,
78 WalkEvent::Leave(node) => {
79 if let Some(expr) = ast::Expr::cast(node) {
80 cb(WalkEvent::Leave(expr));
81 }
82 continue;
83 }
84 };
85 if let Some(let_stmt) = node.parent().and_then(ast::LetStmt::cast)
86 && let_stmt.initializer().map(|it| it.syntax() != &node).unwrap_or(true)
87 && let_stmt.let_else().map(|it| it.syntax() != &node).unwrap_or(true)
88 {
89 preorder.skip_subtree();
91 continue;
92 }
93
94 match ast::Stmt::cast(node.clone()) {
95 Some(ast::Stmt::ExprStmt(_)) | Some(ast::Stmt::LetStmt(_)) => (),
97 Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
99 None => {
100 if ast::GenericArg::can_cast(node.kind()) {
102 preorder.skip_subtree();
103 } else if let Some(expr) = ast::Expr::cast(node) {
104 let is_different_context = check_ctx(&expr) && expr.syntax() != start.syntax();
105 let skip = cb(WalkEvent::Enter(expr));
106 if skip || is_different_context {
107 preorder.skip_subtree();
108 }
109 }
110 }
111 }
112 }
113}
114
115pub fn walk_patterns_in_expr(start: &ast::Expr, cb: &mut dyn FnMut(ast::Pat)) {
117 let mut preorder = start.syntax().preorder();
118 while let Some(event) = preorder.next() {
119 let node = match event {
120 WalkEvent::Enter(node) => node,
121 WalkEvent::Leave(_) => continue,
122 };
123 match ast::Stmt::cast(node.clone()) {
124 Some(ast::Stmt::LetStmt(l)) => {
125 if let Some(pat) = l.pat() {
126 _ = walk_pat(&pat, &mut |pat| {
127 cb(pat);
128 ControlFlow::<(), ()>::Continue(())
129 });
130 }
131 if let Some(expr) = l.initializer() {
132 walk_patterns_in_expr(&expr, cb);
133 }
134 preorder.skip_subtree();
135 }
136 Some(ast::Stmt::ExprStmt(_)) => (),
138 Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
140 None => {
141 if ast::GenericArg::can_cast(node.kind()) {
143 preorder.skip_subtree();
144 } else if let Some(expr) = ast::Expr::cast(node.clone()) {
145 let is_different_context = match &expr {
146 ast::Expr::BlockExpr(block_expr) => {
147 matches!(
148 block_expr.modifier(),
149 Some(
150 ast::BlockModifier::Async(_)
151 | ast::BlockModifier::Try { .. }
152 | ast::BlockModifier::Const(_)
153 )
154 )
155 }
156 ast::Expr::ClosureExpr(_) => true,
157 _ => false,
158 } && expr.syntax() != start.syntax();
159 if is_different_context {
160 preorder.skip_subtree();
161 }
162 } else if let Some(pat) = ast::Pat::cast(node) {
163 preorder.skip_subtree();
164 _ = walk_pat(&pat, &mut |pat| {
165 cb(pat);
166 ControlFlow::<(), ()>::Continue(())
167 });
168 }
169 }
170 }
171 }
172}
173
174pub fn walk_pat<T>(
176 pat: &ast::Pat,
177 cb: &mut dyn FnMut(ast::Pat) -> ControlFlow<T>,
178) -> ControlFlow<T> {
179 let mut preorder = pat.syntax().preorder();
180 while let Some(event) = preorder.next() {
181 let node = match event {
182 WalkEvent::Enter(node) => node,
183 WalkEvent::Leave(_) => continue,
184 };
185 let kind = node.kind();
186 match ast::Pat::cast(node) {
187 Some(pat @ ast::Pat::ConstBlockPat(_)) => {
188 preorder.skip_subtree();
189 cb(pat)?;
190 }
191 Some(pat) => {
192 cb(pat)?;
193 }
194 None if ast::GenericArg::can_cast(kind) => {
196 preorder.skip_subtree();
197 }
198 None => (),
199 }
200 }
201 ControlFlow::Continue(())
202}
203
204pub fn walk_ty(ty: &ast::Type, cb: &mut dyn FnMut(ast::Type) -> bool) {
207 let mut preorder = ty.syntax().preorder();
208 while let Some(event) = preorder.next() {
209 let node = match event {
210 WalkEvent::Enter(node) => node,
211 WalkEvent::Leave(_) => continue,
212 };
213 let kind = node.kind();
214 match ast::Type::cast(node) {
215 Some(ty @ ast::Type::MacroType(_)) => {
216 preorder.skip_subtree();
217 cb(ty);
218 }
219 Some(ty) =>
220 {
221 #[expect(
222 clippy::collapsible_match,
223 reason = "it won't compile due to exhaustiveness"
224 )]
225 if cb(ty) {
226 preorder.skip_subtree();
227 }
228 }
229 None if ast::ConstArg::can_cast(kind) => {
231 preorder.skip_subtree();
232 }
233 None => (),
234 }
235 }
236}
237
238pub fn vis_eq(this: &ast::Visibility, other: &ast::Visibility) -> bool {
239 match (this.kind(), other.kind()) {
240 (VisibilityKind::In(this), VisibilityKind::In(other)) => {
241 stdx::iter_eq_by(this.segments(), other.segments(), |lhs, rhs| {
242 lhs.kind().zip(rhs.kind()).is_some_and(|it| match it {
243 (PathSegmentKind::CrateKw, PathSegmentKind::CrateKw)
244 | (PathSegmentKind::SelfKw, PathSegmentKind::SelfKw)
245 | (PathSegmentKind::SuperKw, PathSegmentKind::SuperKw) => true,
246 (PathSegmentKind::Name(lhs), PathSegmentKind::Name(rhs)) => {
247 lhs.text() == rhs.text()
248 }
249 _ => false,
250 })
251 })
252 }
253 (VisibilityKind::PubSelf, VisibilityKind::PubSelf)
254 | (VisibilityKind::PubSuper, VisibilityKind::PubSuper)
255 | (VisibilityKind::PubCrate, VisibilityKind::PubCrate)
256 | (VisibilityKind::Pub, VisibilityKind::Pub) => true,
257 _ => false,
258 }
259}
260
261pub fn single_let(expr: ast::Expr) -> Option<ast::LetExpr> {
264 match expr {
265 ast::Expr::ParenExpr(expr) => expr.expr().and_then(single_let),
266 ast::Expr::LetExpr(expr) => Some(expr),
267 _ => None,
268 }
269}
270
271pub fn is_pattern_cond(expr: ast::Expr) -> bool {
272 match expr {
273 ast::Expr::BinExpr(expr)
274 if expr.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And)) =>
275 {
276 expr.lhs().map_or(false, is_pattern_cond) || expr.rhs().map_or(false, is_pattern_cond)
277 }
278 ast::Expr::ParenExpr(expr) => expr.expr().is_some_and(is_pattern_cond),
279 ast::Expr::LetExpr(_) => true,
280 _ => false,
281 }
282}
283
284pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
289 let walk_loop = |cb: &mut dyn FnMut(&ast::Expr), label, body: Option<ast::BlockExpr>| {
290 for_each_break_expr(label, body.and_then(|it| it.stmt_list()), &mut |b| {
291 cb(&ast::Expr::BreakExpr(b))
292 })
293 };
294 match expr {
295 ast::Expr::BlockExpr(b) => {
296 match b.modifier() {
297 Some(
298 ast::BlockModifier::Async(_)
299 | ast::BlockModifier::Try { .. }
300 | ast::BlockModifier::Const(_),
301 ) => return cb(expr),
302
303 Some(ast::BlockModifier::Label(label)) => {
304 for_each_break_expr(Some(label), b.stmt_list(), &mut |b| {
305 cb(&ast::Expr::BreakExpr(b))
306 });
307 }
308 Some(ast::BlockModifier::Unsafe(_)) => (),
309 Some(ast::BlockModifier::Gen(_)) => (),
310 Some(ast::BlockModifier::AsyncGen(_)) => (),
311 None => (),
312 }
313 if let Some(stmt_list) = b.stmt_list()
314 && let Some(e) = stmt_list.tail_expr()
315 {
316 for_each_tail_expr(&e, cb);
317 }
318 }
319 ast::Expr::IfExpr(if_) => {
320 let mut if_ = if_.clone();
321 loop {
322 if let Some(block) = if_.then_branch() {
323 for_each_tail_expr(&ast::Expr::BlockExpr(block), cb);
324 }
325 match if_.else_branch() {
326 Some(ast::ElseBranch::IfExpr(it)) => if_ = it,
327 Some(ast::ElseBranch::Block(block)) => {
328 for_each_tail_expr(&ast::Expr::BlockExpr(block), cb);
329 break;
330 }
331 None => break,
332 }
333 }
334 }
335 ast::Expr::LoopExpr(l) => walk_loop(cb, l.label(), l.loop_body()),
336 ast::Expr::WhileExpr(w) => walk_loop(cb, w.label(), w.loop_body()),
337 ast::Expr::ForExpr(f) => walk_loop(cb, f.label(), f.loop_body()),
338 ast::Expr::MatchExpr(m) => {
339 if let Some(arms) = m.match_arm_list() {
340 arms.arms().filter_map(|arm| arm.expr()).for_each(|e| for_each_tail_expr(&e, cb));
341 }
342 }
343 ast::Expr::ArrayExpr(_)
344 | ast::Expr::AwaitExpr(_)
345 | ast::Expr::BinExpr(_)
346 | ast::Expr::BreakExpr(_)
347 | ast::Expr::CallExpr(_)
348 | ast::Expr::CastExpr(_)
349 | ast::Expr::ClosureExpr(_)
350 | ast::Expr::ContinueExpr(_)
351 | ast::Expr::FieldExpr(_)
352 | ast::Expr::IndexExpr(_)
353 | ast::Expr::Literal(_)
354 | ast::Expr::MacroExpr(_)
355 | ast::Expr::MethodCallExpr(_)
356 | ast::Expr::ParenExpr(_)
357 | ast::Expr::PathExpr(_)
358 | ast::Expr::PrefixExpr(_)
359 | ast::Expr::RangeExpr(_)
360 | ast::Expr::RecordExpr(_)
361 | ast::Expr::RefExpr(_)
362 | ast::Expr::ReturnExpr(_)
363 | ast::Expr::BecomeExpr(_)
364 | ast::Expr::TryExpr(_)
365 | ast::Expr::TupleExpr(_)
366 | ast::Expr::LetExpr(_)
367 | ast::Expr::UnderscoreExpr(_)
368 | ast::Expr::YieldExpr(_)
369 | ast::Expr::YeetExpr(_)
370 | ast::Expr::OffsetOfExpr(_)
371 | ast::Expr::FormatArgsExpr(_)
372 | ast::Expr::AsmExpr(_) => cb(expr),
373 }
374}
375
376pub fn for_each_break_and_continue_expr(
377 label: Option<ast::Label>,
378 body: Option<ast::StmtList>,
379 cb: &mut dyn FnMut(ast::Expr),
380) {
381 let label = label.and_then(|lbl| lbl.lifetime());
382 if let Some(b) = body {
383 let tree_depth_iterator = TreeWithDepthIterator::new(b);
384 for (expr, depth) in tree_depth_iterator {
385 match expr {
386 ast::Expr::BreakExpr(b)
387 if (depth == 0 && b.lifetime().is_none())
388 || eq_label_lt(&label, &b.lifetime()) =>
389 {
390 cb(ast::Expr::BreakExpr(b));
391 }
392 ast::Expr::ContinueExpr(c)
393 if (depth == 0 && c.lifetime().is_none())
394 || eq_label_lt(&label, &c.lifetime()) =>
395 {
396 cb(ast::Expr::ContinueExpr(c));
397 }
398 _ => (),
399 }
400 }
401 }
402}
403
404fn for_each_break_expr(
405 label: Option<ast::Label>,
406 body: Option<ast::StmtList>,
407 cb: &mut dyn FnMut(ast::BreakExpr),
408) {
409 let label = label.and_then(|lbl| lbl.lifetime());
410 if let Some(b) = body {
411 let tree_depth_iterator = TreeWithDepthIterator::new(b);
412 for (expr, depth) in tree_depth_iterator {
413 match expr {
414 ast::Expr::BreakExpr(b)
415 if (depth == 0 && b.lifetime().is_none())
416 || eq_label_lt(&label, &b.lifetime()) =>
417 {
418 cb(b);
419 }
420 _ => (),
421 }
422 }
423 }
424}
425
426pub fn eq_label_lt(lt1: &Option<ast::Lifetime>, lt2: &Option<ast::Lifetime>) -> bool {
427 lt1.as_ref().zip(lt2.as_ref()).is_some_and(|(lt, lbl)| lt.text() == lbl.text())
428}
429
430pub fn find_loops(
432 sema: &hir::Semantics<'_, crate::RootDatabase>,
433 token: &syntax::SyntaxToken,
434) -> Option<impl Iterator<Item = ast::Expr>> {
435 let parent = token.parent()?;
436 let lbl = syntax::match_ast! {
437 match parent {
438 ast::BreakExpr(break_) => break_.lifetime(),
439 ast::ContinueExpr(continue_) => continue_.lifetime(),
440 _ => None,
441 }
442 };
443 let label_matches =
444 move |it: Option<ast::Label>| match (lbl.as_ref(), it.and_then(|it| it.lifetime())) {
445 (Some(lbl), Some(it)) => lbl.text() == it.text(),
446 (None, _) => true,
447 (Some(_), None) => false,
448 };
449
450 let find_ancestors = move |token| {
451 for anc in sema.token_ancestors_with_macros(token).filter_map(ast::Expr::cast) {
452 let node = match &anc {
453 ast::Expr::LoopExpr(loop_) if label_matches(loop_.label()) => anc,
454 ast::Expr::WhileExpr(while_) if label_matches(while_.label()) => anc,
455 ast::Expr::ForExpr(for_) if label_matches(for_.label()) => anc,
456 ast::Expr::BlockExpr(blk)
457 if blk.label().is_some() && label_matches(blk.label()) =>
458 {
459 anc
460 }
461 _ => continue,
462 };
463
464 return Some(node);
465 }
466 None
467 };
468
469 sema.descend_into_macros(token.clone()).into_iter().filter_map(find_ancestors).into()
470}
471
472struct TreeWithDepthIterator {
473 preorder: Preorder<RustLanguage>,
474 depth: u32,
475}
476
477impl TreeWithDepthIterator {
478 fn new(body: ast::StmtList) -> Self {
479 let preorder = body.syntax().preorder();
480 Self { preorder, depth: 0 }
481 }
482}
483
484impl Iterator for TreeWithDepthIterator {
485 type Item = (ast::Expr, u32);
486
487 fn next(&mut self) -> Option<Self::Item> {
488 while let Some(event) = self.preorder.find_map(|ev| match ev {
489 WalkEvent::Enter(it) => ast::Expr::cast(it).map(WalkEvent::Enter),
490 WalkEvent::Leave(it) => ast::Expr::cast(it).map(WalkEvent::Leave),
491 }) {
492 match event {
493 WalkEvent::Enter(
494 ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_),
495 ) => {
496 self.depth += 1;
497 }
498 WalkEvent::Leave(
499 ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_),
500 ) => {
501 self.depth -= 1;
502 }
503 WalkEvent::Enter(ast::Expr::BlockExpr(e)) if e.label().is_some() => {
504 self.depth += 1;
505 }
506 WalkEvent::Leave(ast::Expr::BlockExpr(e)) if e.label().is_some() => {
507 self.depth -= 1;
508 }
509 WalkEvent::Enter(expr) => return Some((expr, self.depth)),
510 _ => (),
511 }
512 }
513 None
514 }
515}
516
517pub fn parse_tt_as_comma_sep_paths(
519 input: ast::TokenTree,
520 edition: Edition,
521) -> Option<Vec<ast::Path>> {
522 let r_paren = input.r_paren_token();
523 let tokens =
524 input.syntax().children_with_tokens().skip(1).map_while(|it| match it.into_token() {
525 Some(tok) if tok.kind().is_keyword(edition) => None,
527 tok @ Some(_) if tok == r_paren => None,
529 None => None,
531 Some(tok) => Some(tok),
532 });
533 let input_expressions = tokens.chunk_by(|tok| tok.kind() == T![,]);
534 let paths = input_expressions
535 .into_iter()
536 .filter_map(|(is_sep, group)| (!is_sep).then_some(group))
537 .filter_map(|mut tokens| {
538 syntax::hacks::parse_expr_from_str(&tokens.join(""), Edition::CURRENT).and_then(
539 |expr| match expr {
540 ast::Expr::PathExpr(it) => it.path(),
541 _ => None,
542 },
543 )
544 })
545 .collect();
546 Some(paths)
547}
548
549pub fn macro_call_for_string_token(string: &ast::String) -> Option<MacroCall> {
550 let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?;
551 Some(macro_call)
552}
553
554pub fn is_in_macro_matcher(token: &SyntaxToken) -> bool {
555 let Some(macro_def) = token
556 .parent_ancestors()
557 .map_while(Either::<ast::TokenTree, ast::Macro>::cast)
558 .find_map(Either::right)
559 else {
560 return false;
561 };
562 let range = token.text_range();
563 let Some(body) = (match macro_def {
564 ast::Macro::MacroDef(macro_def) => {
565 if let Some(args) = macro_def.args() {
566 return args.syntax().text_range().contains_range(range);
567 }
568 macro_def.body()
569 }
570 ast::Macro::MacroRules(macro_rules) => macro_rules.token_tree(),
571 }) else {
572 return false;
573 };
574 if !body.syntax().text_range().contains_range(range) {
575 return false;
576 }
577 body.token_trees_and_tokens().filter_map(|tt| tt.into_node()).any(|tt| {
578 let Some(next) = non_trivia_sibling(tt.syntax().syntax_element(), Direction::Next) else {
579 return false;
580 };
581 let Some(next_next) = next.next_sibling_or_token() else { return false };
582 next.kind() == T![=]
583 && next_next.kind() == T![>]
584 && tt.syntax().text_range().contains_range(range)
585 })
586}