Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide_db/src/helpers/node_ext.rs')
-rw-r--r--crates/ide_db/src/helpers/node_ext.rs212
1 files changed, 212 insertions, 0 deletions
diff --git a/crates/ide_db/src/helpers/node_ext.rs b/crates/ide_db/src/helpers/node_ext.rs
new file mode 100644
index 0000000000..24f853737b
--- /dev/null
+++ b/crates/ide_db/src/helpers/node_ext.rs
@@ -0,0 +1,212 @@
+//! Various helper functions to work with SyntaxNodes.
+use syntax::{
+ ast::{self, PathSegmentKind, VisibilityKind},
+ AstNode, WalkEvent,
+};
+
+pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
+ if let ast::Expr::PathExpr(expr) = expr {
+ let path = expr.path()?;
+ let segment = path.segment()?;
+ let name_ref = segment.name_ref()?;
+ if path.qualifier().is_none() {
+ return Some(name_ref);
+ }
+ }
+ None
+}
+
+pub fn block_as_lone_tail(block: &ast::BlockExpr) -> Option<ast::Expr> {
+ block.statements().next().is_none().then(|| block.tail_expr()).flatten()
+}
+/// Preorder walk all the expression's child expressions.
+pub fn walk_expr(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr)) {
+ preorder_expr(expr, &mut |ev| {
+ if let WalkEvent::Enter(expr) = ev {
+ cb(expr);
+ }
+ 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(expr: &ast::Expr, cb: &mut dyn FnMut(WalkEvent<ast::Expr>) -> bool) {
+ let mut preorder = expr.syntax().preorder();
+ while let Some(event) = preorder.next() {
+ let node = match event {
+ WalkEvent::Enter(node) => node,
+ WalkEvent::Leave(node) => {
+ if let Some(expr) = ast::Expr::cast(node) {
+ cb(WalkEvent::Leave(expr));
+ }
+ continue;
+ }
+ };
+ match ast::Stmt::cast(node.clone()) {
+ // recursively walk the initializer, skipping potential const pat expressions
+ // let statements aren't usually nested too deeply so this is fine to recurse on
+ Some(ast::Stmt::LetStmt(l)) => {
+ if let Some(expr) = l.initializer() {
+ preorder_expr(&expr, cb);
+ }
+ preorder.skip_subtree();
+ }
+ // Don't skip subtree since we want to process the expression child next
+ Some(ast::Stmt::ExprStmt(_)) => (),
+ // This might be an expression
+ Some(ast::Stmt::Item(ast::Item::MacroCall(mcall))) => {
+ cb(WalkEvent::Enter(ast::Expr::MacroCall(mcall)));
+ preorder.skip_subtree();
+ }
+ // skip inner items which might have their own expressions
+ Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
+ None => {
+ // skip const args, those expressions are a different context
+ 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::EffectExpr(effect) => {
+ matches!(
+ effect.effect(),
+ ast::Effect::Async(_) | ast::Effect::Try(_) | ast::Effect::Const(_)
+ )
+ }
+ ast::Expr::ClosureExpr(_) => true,
+ _ => false,
+ };
+ let skip = cb(WalkEvent::Enter(expr));
+ if skip || is_different_context {
+ preorder.skip_subtree();
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Preorder walk all the expression's child patterns.
+pub fn walk_patterns_in_expr(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Pat)) {
+ let mut preorder = expr.syntax().preorder();
+ while let Some(event) = preorder.next() {
+ let node = match event {
+ WalkEvent::Enter(node) => node,
+ WalkEvent::Leave(_) => continue,
+ };
+ match ast::Stmt::cast(node.clone()) {
+ Some(ast::Stmt::LetStmt(l)) => {
+ if let Some(pat) = l.pat() {
+ walk_pat(&pat, cb);
+ }
+ if let Some(expr) = l.initializer() {
+ walk_patterns_in_expr(&expr, cb);
+ }
+ preorder.skip_subtree();
+ }
+ // Don't skip subtree since we want to process the expression child next
+ Some(ast::Stmt::ExprStmt(_)) => (),
+ // skip inner items which might have their own patterns
+ Some(ast::Stmt::Item(_)) => preorder.skip_subtree(),
+ None => {
+ // skip const args, those are a different context
+ if ast::GenericArg::can_cast(node.kind()) {
+ preorder.skip_subtree();
+ } else if let Some(expr) = ast::Expr::cast(node.clone()) {
+ let is_different_context = match &expr {
+ ast::Expr::EffectExpr(effect) => match effect.effect() {
+ ast::Effect::Async(_) | ast::Effect::Try(_) | ast::Effect::Const(_) => {
+ true
+ }
+ ast::Effect::Unsafe(_) | ast::Effect::Label(_) => false,
+ },
+ ast::Expr::ClosureExpr(_) => true,
+ _ => false,
+ };
+ if is_different_context {
+ preorder.skip_subtree();
+ }
+ } else if let Some(pat) = ast::Pat::cast(node) {
+ preorder.skip_subtree();
+ walk_pat(&pat, cb);
+ }
+ }
+ }
+ }
+}
+
+/// Preorder walk all the pattern's sub patterns.
+pub fn walk_pat(pat: &ast::Pat, cb: &mut dyn FnMut(ast::Pat)) {
+ let mut preorder = pat.syntax().preorder();
+ while let Some(event) = preorder.next() {
+ let node = match event {
+ WalkEvent::Enter(node) => node,
+ WalkEvent::Leave(_) => continue,
+ };
+ let kind = node.kind();
+ match ast::Pat::cast(node) {
+ Some(pat @ ast::Pat::ConstBlockPat(_)) => {
+ preorder.skip_subtree();
+ cb(pat);
+ }
+ Some(pat) => {
+ cb(pat);
+ }
+ // skip const args
+ None if ast::GenericArg::can_cast(kind) => {
+ preorder.skip_subtree();
+ }
+ None => (),
+ }
+ }
+}
+
+/// Preorder walk all the type's sub types.
+pub fn walk_ty(ty: &ast::Type, cb: &mut dyn FnMut(ast::Type)) {
+ let mut preorder = ty.syntax().preorder();
+ while let Some(event) = preorder.next() {
+ let node = match event {
+ WalkEvent::Enter(node) => node,
+ WalkEvent::Leave(_) => continue,
+ };
+ let kind = node.kind();
+ match ast::Type::cast(node) {
+ Some(ty @ ast::Type::MacroType(_)) => {
+ preorder.skip_subtree();
+ cb(ty)
+ }
+ Some(ty) => {
+ cb(ty);
+ }
+ // skip const args
+ None if ast::ConstArg::can_cast(kind) => {
+ preorder.skip_subtree();
+ }
+ None => (),
+ }
+ }
+}
+
+pub fn vis_eq(this: &ast::Visibility, other: &ast::Visibility) -> bool {
+ match (this.kind(), other.kind()) {
+ (VisibilityKind::In(this), VisibilityKind::In(other)) => {
+ stdx::iter_eq_by(this.segments(), other.segments(), |lhs, rhs| {
+ lhs.kind().zip(rhs.kind()).map_or(false, |it| match it {
+ (PathSegmentKind::CrateKw, PathSegmentKind::CrateKw)
+ | (PathSegmentKind::SelfKw, PathSegmentKind::SelfKw)
+ | (PathSegmentKind::SuperKw, PathSegmentKind::SuperKw) => true,
+ (PathSegmentKind::Name(lhs), PathSegmentKind::Name(rhs)) => {
+ lhs.text() == rhs.text()
+ }
+ _ => false,
+ })
+ })
+ }
+ (VisibilityKind::PubSelf, VisibilityKind::PubSelf)
+ | (VisibilityKind::PubSuper, VisibilityKind::PubSuper)
+ | (VisibilityKind::PubCrate, VisibilityKind::PubCrate)
+ | (VisibilityKind::Pub, VisibilityKind::Pub) => true,
+ _ => false,
+ }
+}