]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_db/src/helpers.rs
fix(11422): have two different funuctions - one for iterating breaks, one for iteraat...
[rust.git] / crates / ide_db / src / helpers.rs
index cbe4adf1b9c706e42ae9e24ad1b7267fb4a6846b..944b69c1acf4ad6e01d4dafb357fb0a12933c051 100644 (file)
@@ -16,7 +16,8 @@
 use itertools::Itertools;
 use syntax::{
     ast::{self, make, HasLoopBody},
-    AstNode, AstToken, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T,
+    AstNode, AstToken, Preorder, RustLanguage, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent,
+    T,
 };
 
 use crate::{defs::Definition, RootDatabase};
@@ -190,46 +191,104 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
     }
 }
 
-/// Calls `cb` on each break expr inside of `body` that is applicable for the given label.
-pub fn for_each_break_expr(
+pub fn for_each_break_and_continue_expr(
+    label: Option<ast::Label>,
+    body: Option<ast::StmtList>,
+    cb: &mut dyn FnMut(ast::Expr),
+) {
+    let label = label.and_then(|lbl| lbl.lifetime());
+    if let Some(b) = body {
+        let tree_depth_iterator = TreeWithDepthIterator::new(b);
+        for (expr, depth) in tree_depth_iterator {
+            match expr {
+                ast::Expr::BreakExpr(b)
+                    if (depth == 0 && b.lifetime().is_none())
+                        || eq_label_lt(&label, &b.lifetime()) =>
+                {
+                    cb(ast::Expr::BreakExpr(b));
+                }
+                ast::Expr::ContinueExpr(c)
+                    if (depth == 0 && c.lifetime().is_none())
+                        || eq_label_lt(&label, &c.lifetime()) =>
+                {
+                    cb(ast::Expr::ContinueExpr(c));
+                }
+                _ => (),
+            }
+        }
+    }
+}
+
+fn for_each_break_expr(
     label: Option<ast::Label>,
     body: Option<ast::StmtList>,
     cb: &mut dyn FnMut(ast::BreakExpr),
 ) {
     let label = label.and_then(|lbl| lbl.lifetime());
-    let mut depth = 0;
     if let Some(b) = body {
-        let preorder = &mut b.syntax().preorder();
-        let ev_as_expr = |ev| match ev {
-            WalkEvent::Enter(it) => Some(WalkEvent::Enter(ast::Expr::cast(it)?)),
-            WalkEvent::Leave(it) => Some(WalkEvent::Leave(ast::Expr::cast(it)?)),
-        };
-        let eq_label = |lt: Option<ast::Lifetime>| {
-            lt.zip(label.as_ref()).map_or(false, |(lt, lbl)| lt.text() == lbl.text())
-        };
-        while let Some(node) = preorder.find_map(ev_as_expr) {
-            match node {
-                WalkEvent::Enter(expr) => match expr {
-                    ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => {
-                        depth += 1
-                    }
-                    ast::Expr::BlockExpr(e) if e.label().is_some() => depth += 1,
-                    ast::Expr::BreakExpr(b)
-                        if (depth == 0 && b.lifetime().is_none()) || eq_label(b.lifetime()) =>
-                    {
-                        cb(b);
-                    }
-                    _ => (),
-                },
-                WalkEvent::Leave(expr) => match expr {
-                    ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => {
-                        depth -= 1
-                    }
-                    ast::Expr::BlockExpr(e) if e.label().is_some() => depth -= 1,
-                    _ => (),
-                },
+        let tree_depth_iterator = TreeWithDepthIterator::new(b);
+        for (expr, depth) in tree_depth_iterator {
+            match expr {
+                ast::Expr::BreakExpr(b)
+                    if (depth == 0 && b.lifetime().is_none())
+                        || eq_label_lt(&label, &b.lifetime()) =>
+                {
+                    cb(b);
+                }
+                _ => (),
+            }
+        }
+    }
+}
+
+fn eq_label_lt(lt1: &Option<ast::Lifetime>, lt2: &Option<ast::Lifetime>) -> bool {
+    lt1.as_ref().zip(lt2.as_ref()).map_or(false, |(lt, lbl)| lt.text() == lbl.text())
+}
+
+struct TreeWithDepthIterator {
+    preorder: Preorder<RustLanguage>,
+    depth: i32,
+}
+
+impl TreeWithDepthIterator {
+    fn new(body: ast::StmtList) -> Self {
+        let preorder = body.syntax().preorder();
+        Self { preorder, depth: 0 }
+    }
+}
+
+impl<'a> Iterator for TreeWithDepthIterator {
+    type Item = (ast::Expr, i32);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        while let Some((event, expr)) = self.preorder.find_map(|ev| match ev {
+            WalkEvent::Enter(it) => Some(WalkEvent::Enter(())).zip(ast::Expr::cast(it)),
+            WalkEvent::Leave(it) => Some(WalkEvent::Leave(())).zip(ast::Expr::cast(it)),
+        }) {
+            match (event, expr) {
+                (
+                    WalkEvent::Enter(_),
+                    ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_),
+                ) => {
+                    self.depth += 1;
+                }
+                (
+                    WalkEvent::Leave(_),
+                    ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_),
+                ) => {
+                    self.depth -= 1;
+                }
+                (WalkEvent::Enter(_), ast::Expr::BlockExpr(e)) if e.label().is_some() => {
+                    self.depth += 1;
+                }
+                (WalkEvent::Leave(_), ast::Expr::BlockExpr(e)) if e.label().is_some() => {
+                    self.depth -= 1;
+                }
+                (WalkEvent::Enter(_), expr) => return Some((expr, self.depth)),
+                _ => (),
             }
         }
+        None
     }
 }