diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs index bfb4ce711c..6f69421002 100755 --- a/crates/ide/src/folding_ranges.rs +++ b/crates/ide/src/folding_ranges.rs @@ -1,3 +1,4 @@ +use ide_db::helpers::node_ext::vis_eq; use rustc_hash::FxHashSet; use syntax::{ @@ -198,7 +199,7 @@ where fn eq_visibility(vis0: Option, vis1: Option) -> bool { match (vis0, vis1) { (None, None) => true, - (Some(vis0), Some(vis1)) => vis0.is_eq_to(&vis1), + (Some(vis0), Some(vis1)) => vis_eq(&vis0, &vis1), _ => false, } } diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 7cce99c3b0..d305f82fcb 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -2,7 +2,7 @@ use hir::Semantics; use ide_db::{ base_db::FilePosition, defs::{Definition, NameClass, NameRefClass}, - helpers::{for_each_break_expr, for_each_tail_expr, pick_best_token}, + helpers::{for_each_break_expr, for_each_tail_expr, node_ext::walk_expr, pick_best_token}, search::{FileReference, ReferenceAccess, SearchScope}, RootDatabase, }; @@ -122,7 +122,7 @@ fn highlight_exit_points( ) -> Option> { let mut highlights = Vec::new(); let body = body?; - body.walk(&mut |expr| match expr { + walk_expr(&body, &mut |expr| match expr { ast::Expr::ReturnExpr(expr) => { if let Some(token) = expr.return_token() { highlights.push(HighlightedRange { access: None, range: token.text_range() }); @@ -243,7 +243,7 @@ fn highlight_yield_points(token: SyntaxToken) -> Option> { let mut highlights = vec![HighlightedRange { access: None, range: async_token?.text_range() }]; if let Some(body) = body { - body.walk(&mut |expr| { + walk_expr(&body, &mut |expr| { if let ast::Expr::AwaitExpr(expr) = expr { if let Some(token) = expr.await_token() { highlights diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs index ec67c02757..8eb2e49359 100644 --- a/crates/ide/src/join_lines.rs +++ b/crates/ide/src/join_lines.rs @@ -1,6 +1,7 @@ use std::convert::TryFrom; use ide_assists::utils::extract_trivial_expression; +use ide_db::helpers::node_ext::expr_as_name_ref; use itertools::Itertools; use syntax::{ ast::{self, AstNode, AstToken, IsString}, @@ -263,7 +264,7 @@ fn join_assignments( return None; } let lhs = bin_expr.lhs()?; - let name_ref = lhs.name_ref()?; + let name_ref = expr_as_name_ref(&lhs)?; if name_ref.to_string() != let_ident_pat.syntax().to_string() { cov_mark::hit!(join_assignments_mismatch); diff --git a/crates/ide_assists/src/handlers/add_explicit_type.rs b/crates/ide_assists/src/handlers/add_explicit_type.rs index a48729b45f..169bb0cbf4 100644 --- a/crates/ide_assists/src/handlers/add_explicit_type.rs +++ b/crates/ide_assists/src/handlers/add_explicit_type.rs @@ -1,4 +1,5 @@ use hir::HirDisplay; +use ide_db::helpers::node_ext::walk_ty; use syntax::ast::{self, AstNode, LetStmt, Param}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -46,7 +47,7 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio // Don't enable the assist if there is a type ascription without any placeholders if let Some(ty) = &ascribed_ty { let mut contains_infer_ty = false; - ty.walk(&mut |ty| contains_infer_ty |= matches!(ty, ast::Type::InferType(_))); + walk_ty(ty, &mut |ty| contains_infer_ty |= matches!(ty, ast::Type::InferType(_))); if !contains_infer_ty { cov_mark::hit!(add_explicit_type_not_applicable_if_ty_already_specified); return None; diff --git a/crates/ide_assists/src/handlers/convert_bool_then.rs b/crates/ide_assists/src/handlers/convert_bool_then.rs index 497e7c2546..e7939269ad 100644 --- a/crates/ide_assists/src/handlers/convert_bool_then.rs +++ b/crates/ide_assists/src/handlers/convert_bool_then.rs @@ -1,6 +1,10 @@ use hir::{known, AsAssocItem, Semantics}; use ide_db::{ - helpers::{for_each_tail_expr, FamousDefs}, + helpers::{ + for_each_tail_expr, + node_ext::{block_as_lone_tail, preorder_expr}, + FamousDefs, + }, RootDatabase, }; use itertools::Itertools; @@ -218,7 +222,7 @@ fn is_invalid_body( expr: &ast::Expr, ) -> bool { let mut invalid = false; - expr.preorder(&mut |e| { + preorder_expr(expr, &mut |e| { invalid |= matches!(e, syntax::WalkEvent::Enter(ast::Expr::TryExpr(_) | ast::Expr::ReturnExpr(_))); invalid @@ -252,7 +256,7 @@ fn block_is_none_variant( block: &ast::BlockExpr, none_variant: hir::Variant, ) -> bool { - block.as_lone_tail().and_then(|e| match e { + block_as_lone_tail(block).and_then(|e| match e { ast::Expr::PathExpr(pat) => match sema.resolve_path(&pat.path()?)? { hir::PathResolution::Def(hir::ModuleDef::Variant(v)) => Some(v), _ => None, diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs index c0cf3fac03..328d172cb8 100644 --- a/crates/ide_assists/src/handlers/extract_function.rs +++ b/crates/ide_assists/src/handlers/extract_function.rs @@ -5,6 +5,7 @@ use either::Either; use hir::{HirDisplay, InFile, Local, Semantics, TypeInfo}; use ide_db::{ defs::{Definition, NameRefClass}, + helpers::node_ext::{preorder_expr, walk_expr, walk_pat, walk_patterns_in_expr}, search::{FileReference, ReferenceAccess, SearchScope}, RootDatabase, }; @@ -478,7 +479,7 @@ impl FunctionBody { fn walk_expr(&self, cb: &mut dyn FnMut(ast::Expr)) { match self { - FunctionBody::Expr(expr) => expr.walk(cb), + FunctionBody::Expr(expr) => walk_expr(expr, cb), FunctionBody::Span { parent, text_range } => { parent .statements() @@ -488,12 +489,12 @@ impl FunctionBody { ast::Stmt::Item(_) => None, ast::Stmt::LetStmt(stmt) => stmt.initializer(), }) - .for_each(|expr| expr.walk(cb)); + .for_each(|expr| walk_expr(&expr, cb)); if let Some(expr) = parent .tail_expr() .filter(|it| text_range.contains_range(it.syntax().text_range())) { - expr.walk(cb); + walk_expr(&expr, cb); } } } @@ -501,7 +502,7 @@ impl FunctionBody { fn preorder_expr(&self, cb: &mut dyn FnMut(WalkEvent) -> bool) { match self { - FunctionBody::Expr(expr) => expr.preorder(cb), + FunctionBody::Expr(expr) => preorder_expr(expr, cb), FunctionBody::Span { parent, text_range } => { parent .statements() @@ -511,12 +512,12 @@ impl FunctionBody { ast::Stmt::Item(_) => None, ast::Stmt::LetStmt(stmt) => stmt.initializer(), }) - .for_each(|expr| expr.preorder(cb)); + .for_each(|expr| preorder_expr(&expr, cb)); if let Some(expr) = parent .tail_expr() .filter(|it| text_range.contains_range(it.syntax().text_range())) { - expr.preorder(cb); + preorder_expr(&expr, cb); } } } @@ -524,7 +525,7 @@ impl FunctionBody { fn walk_pat(&self, cb: &mut dyn FnMut(ast::Pat)) { match self { - FunctionBody::Expr(expr) => expr.walk_patterns(cb), + FunctionBody::Expr(expr) => walk_patterns_in_expr(expr, cb), FunctionBody::Span { parent, text_range } => { parent .statements() @@ -532,16 +533,16 @@ impl FunctionBody { .for_each(|stmt| match stmt { ast::Stmt::ExprStmt(expr_stmt) => { if let Some(expr) = expr_stmt.expr() { - expr.walk_patterns(cb) + walk_patterns_in_expr(&expr, cb) } } ast::Stmt::Item(_) => (), ast::Stmt::LetStmt(stmt) => { if let Some(pat) = stmt.pat() { - pat.walk(cb); + walk_pat(&pat, cb); } if let Some(expr) = stmt.initializer() { - expr.walk_patterns(cb); + walk_patterns_in_expr(&expr, cb); } } }); @@ -549,7 +550,7 @@ impl FunctionBody { .tail_expr() .filter(|it| text_range.contains_range(it.syntax().text_range())) { - expr.walk_patterns(cb); + walk_patterns_in_expr(&expr, cb); } } } diff --git a/crates/ide_assists/src/handlers/extract_type_alias.rs b/crates/ide_assists/src/handlers/extract_type_alias.rs index 4913ac1e08..a2dd23b58b 100644 --- a/crates/ide_assists/src/handlers/extract_type_alias.rs +++ b/crates/ide_assists/src/handlers/extract_type_alias.rs @@ -1,4 +1,5 @@ use either::Either; +use ide_db::helpers::node_ext::walk_ty; use itertools::Itertools; use syntax::{ ast::{self, edit::IndentLevel, AstNode, GenericParamsOwner, NameOwner}, @@ -120,7 +121,7 @@ fn collect_used_generics<'gp>( } let mut generics = Vec::new(); - ty.walk(&mut |ty| match ty { + walk_ty(ty, &mut |ty| match ty { ast::Type::PathType(ty) => { if let Some(path) = ty.path() { if let Some(name_ref) = path.as_single_name_ref() { diff --git a/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs b/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs index 7ad3de2c57..c510a5f965 100644 --- a/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs +++ b/crates/ide_assists/src/handlers/wrap_return_type_in_result.rs @@ -1,6 +1,6 @@ use std::iter; -use ide_db::helpers::{for_each_tail_expr, FamousDefs}; +use ide_db::helpers::{for_each_tail_expr, node_ext::walk_expr, FamousDefs}; use syntax::{ ast::{self, make, Expr}, match_ast, AstNode, @@ -54,7 +54,7 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext) let mut exprs_to_wrap = Vec::new(); let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_wrap, e); - body.walk(&mut |expr| { + walk_expr(&body, &mut |expr| { if let Expr::ReturnExpr(ret_expr) = expr { if let Some(ret_expr_arg) = &ret_expr.expr() { for_each_tail_expr(ret_expr_arg, tail_cb); diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index 2433d8e918..c92893716f 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs @@ -1,15 +1,17 @@ //! A module with ide helpers for high-level ide features. +pub mod famous_defs; +pub mod generated_lints; pub mod import_assets; pub mod insert_use; pub mod merge_imports; +pub mod node_ext; pub mod rust_doc; -pub mod generated_lints; use std::collections::VecDeque; use base_db::FileId; use either::Either; -use hir::{Crate, Enum, ItemInNs, MacroDef, Module, ModuleDef, Name, ScopeDef, Semantics, Trait}; +use hir::{ItemInNs, MacroDef, ModuleDef, Name, Semantics}; use syntax::{ ast::{self, make, LoopBodyOwner}, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T, @@ -17,6 +19,8 @@ use syntax::{ use crate::RootDatabase; +pub use self::famous_defs::FamousDefs; + pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option { match item { ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).name(db), @@ -27,7 +31,7 @@ pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option { /// Resolves the path at the cursor token as a derive macro if it inside a token tree of a derive attribute. pub fn try_resolve_derive_input_at( - sema: &Semantics, + sema: &hir::Semantics, derive_attr: &ast::Attr, cursor: &SyntaxToken, ) -> Option { @@ -113,123 +117,6 @@ pub fn visit_file_defs( module.impl_defs(db).into_iter().for_each(|impl_| cb(Either::Right(impl_))); } -/// Helps with finding well-know things inside the standard library. This is -/// somewhat similar to the known paths infra inside hir, but it different; We -/// want to make sure that IDE specific paths don't become interesting inside -/// the compiler itself as well. -/// -/// Note that, by default, rust-analyzer tests **do not** include core or std -/// libraries. If you are writing tests for functionality using [`FamousDefs`], -/// you'd want to include minicore (see `test_utils::MiniCore`) declaration at -/// the start of your tests: -/// -/// ``` -/// //- minicore: iterator, ord, derive -/// ``` -pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option); - -#[allow(non_snake_case)] -impl FamousDefs<'_, '_> { - pub fn std(&self) -> Option { - self.find_crate("std") - } - - pub fn core(&self) -> Option { - self.find_crate("core") - } - - pub fn core_cmp_Ord(&self) -> Option { - self.find_trait("core:cmp:Ord") - } - - pub fn core_convert_From(&self) -> Option { - self.find_trait("core:convert:From") - } - - pub fn core_convert_Into(&self) -> Option { - self.find_trait("core:convert:Into") - } - - pub fn core_option_Option(&self) -> Option { - self.find_enum("core:option:Option") - } - - pub fn core_result_Result(&self) -> Option { - self.find_enum("core:result:Result") - } - - pub fn core_default_Default(&self) -> Option { - self.find_trait("core:default:Default") - } - - pub fn core_iter_Iterator(&self) -> Option { - self.find_trait("core:iter:traits:iterator:Iterator") - } - - pub fn core_iter_IntoIterator(&self) -> Option { - self.find_trait("core:iter:traits:collect:IntoIterator") - } - - pub fn core_iter(&self) -> Option { - self.find_module("core:iter") - } - - pub fn core_ops_Deref(&self) -> Option { - self.find_trait("core:ops:Deref") - } - - fn find_trait(&self, path: &str) -> Option { - match self.find_def(path)? { - hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), - _ => None, - } - } - - fn find_enum(&self, path: &str) -> Option { - match self.find_def(path)? { - hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it), - _ => None, - } - } - - fn find_module(&self, path: &str) -> Option { - match self.find_def(path)? { - hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(it)) => Some(it), - _ => None, - } - } - - fn find_crate(&self, name: &str) -> Option { - let krate = self.1?; - let db = self.0.db; - let res = - krate.dependencies(db).into_iter().find(|dep| dep.name.to_string() == name)?.krate; - Some(res) - } - - fn find_def(&self, path: &str) -> Option { - let db = self.0.db; - let mut path = path.split(':'); - let trait_ = path.next_back()?; - let std_crate = path.next()?; - let std_crate = self.find_crate(std_crate)?; - let mut module = std_crate.root_module(db); - for segment in path { - module = module.children(db).find_map(|child| { - let name = child.name(db)?; - if name.to_string() == segment { - Some(child) - } else { - None - } - })?; - } - let def = - module.scope(db, None).into_iter().find(|(name, _def)| name.to_string() == trait_)?.1; - Some(def) - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct SnippetCap { _private: (), diff --git a/crates/ide_db/src/helpers/famous_defs.rs b/crates/ide_db/src/helpers/famous_defs.rs new file mode 100644 index 0000000000..7c59bc28d7 --- /dev/null +++ b/crates/ide_db/src/helpers/famous_defs.rs @@ -0,0 +1,121 @@ +//! See [`FamousDefs`]. +use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; + +use crate::RootDatabase; + +/// Helps with finding well-know things inside the standard library. This is +/// somewhat similar to the known paths infra inside hir, but it different; We +/// want to make sure that IDE specific paths don't become interesting inside +/// the compiler itself as well. +/// +/// Note that, by default, rust-analyzer tests **do not** include core or std +/// libraries. If you are writing tests for functionality using [`FamousDefs`], +/// you'd want to include minicore (see `test_utils::MiniCore`) declaration at +/// the start of your tests: +/// +/// ``` +/// //- minicore: iterator, ord, derive +/// ``` +pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option); + +#[allow(non_snake_case)] +impl FamousDefs<'_, '_> { + pub fn std(&self) -> Option { + self.find_crate("std") + } + + pub fn core(&self) -> Option { + self.find_crate("core") + } + + pub fn core_cmp_Ord(&self) -> Option { + self.find_trait("core:cmp:Ord") + } + + pub fn core_convert_From(&self) -> Option { + self.find_trait("core:convert:From") + } + + pub fn core_convert_Into(&self) -> Option { + self.find_trait("core:convert:Into") + } + + pub fn core_option_Option(&self) -> Option { + self.find_enum("core:option:Option") + } + + pub fn core_result_Result(&self) -> Option { + self.find_enum("core:result:Result") + } + + pub fn core_default_Default(&self) -> Option { + self.find_trait("core:default:Default") + } + + pub fn core_iter_Iterator(&self) -> Option { + self.find_trait("core:iter:traits:iterator:Iterator") + } + + pub fn core_iter_IntoIterator(&self) -> Option { + self.find_trait("core:iter:traits:collect:IntoIterator") + } + + pub fn core_iter(&self) -> Option { + self.find_module("core:iter") + } + + pub fn core_ops_Deref(&self) -> Option { + self.find_trait("core:ops:Deref") + } + + fn find_trait(&self, path: &str) -> Option { + match self.find_def(path)? { + hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), + _ => None, + } + } + + fn find_enum(&self, path: &str) -> Option { + match self.find_def(path)? { + hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it), + _ => None, + } + } + + fn find_module(&self, path: &str) -> Option { + match self.find_def(path)? { + hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(it)) => Some(it), + _ => None, + } + } + + fn find_crate(&self, name: &str) -> Option { + let krate = self.1?; + let db = self.0.db; + let res = + krate.dependencies(db).into_iter().find(|dep| dep.name.to_string() == name)?.krate; + Some(res) + } + + fn find_def(&self, path: &str) -> Option { + let db = self.0.db; + let mut path = path.split(':'); + let trait_ = path.next_back()?; + let std_crate = path.next()?; + let std_crate = self.find_crate(std_crate)?; + let mut module = std_crate.root_module(db); + for segment in path { + module = module.children(db).find_map(|child| { + let name = child.name(db)?; + if name.to_string() == segment { + Some(child) + } else { + None + } + })?; + } + let def = + module.scope(db, None).into_iter().find(|(name, _def)| name.to_string() == trait_)?.1; + Some(def) + } +} diff --git a/crates/ide_db/src/helpers/merge_imports.rs b/crates/ide_db/src/helpers/merge_imports.rs index 69fc84874c..6f130e99b4 100644 --- a/crates/ide_db/src/helpers/merge_imports.rs +++ b/crates/ide_db/src/helpers/merge_imports.rs @@ -7,6 +7,8 @@ use syntax::{ ted, }; +use crate::helpers::node_ext::vis_eq; + /// What type of merges are allowed. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum MergeBehavior { @@ -292,7 +294,7 @@ fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering { pub fn eq_visibility(vis0: Option, vis1: Option) -> bool { match (vis0, vis1) { (None, None) => true, - (Some(vis0), Some(vis1)) => vis0.is_eq_to(&vis1), + (Some(vis0), Some(vis1)) => vis_eq(&vis0, &vis1), _ => false, } } 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 { + 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 { + 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) -> 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, + } +} diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs index a6f7c09af8..a5dca509fe 100644 --- a/crates/ide_db/src/rename.rs +++ b/crates/ide_db/src/rename.rs @@ -34,6 +34,7 @@ use text_edit::{TextEdit, TextEditBuilder}; use crate::{ defs::Definition, + helpers::node_ext::expr_as_name_ref, search::FileReference, source_change::{FileSystemEdit, SourceChange}, RootDatabase, @@ -339,7 +340,7 @@ fn source_edit_from_name_ref( if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { let rcf_name_ref = record_field.name_ref(); let rcf_expr = record_field.expr(); - match &(rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { + match &(rcf_name_ref, rcf_expr.and_then(|it| expr_as_name_ref(&it))) { // field: init-expr, check if we can use a field init shorthand (Some(field_name), Some(init)) => { if field_name == name_ref { diff --git a/crates/syntax/src/ast/expr_ext.rs b/crates/syntax/src/ast/expr_ext.rs index 1c40de1e60..9d78cb226f 100644 --- a/crates/syntax/src/ast/expr_ext.rs +++ b/crates/syntax/src/ast/expr_ext.rs @@ -1,6 +1,6 @@ //! Various extension methods to ast Expr Nodes, which are hard to code-generate. - -use rowan::WalkEvent; +//! +//! These methods should only do simple, shallow tasks related to the syntax of the node itself. use crate::{ ast::{ @@ -28,139 +28,6 @@ impl ast::Expr { | ast::Expr::EffectExpr(_) ) } - - pub fn name_ref(&self) -> Option { - if let ast::Expr::PathExpr(expr) = self { - let path = expr.path()?; - let segment = path.segment()?; - let name_ref = segment.name_ref()?; - if path.qualifier().is_none() { - return Some(name_ref); - } - } - None - } - - /// Preorder walk all the expression's child expressions. - pub fn walk(&self, cb: &mut dyn FnMut(ast::Expr)) { - self.preorder(&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(&self, cb: &mut dyn FnMut(WalkEvent) -> bool) { - let mut preorder = self.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() { - expr.preorder(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(&self, cb: &mut dyn FnMut(ast::Pat)) { - let mut preorder = self.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() { - pat.walk(cb); - } - if let Some(expr) = l.initializer() { - expr.walk_patterns(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) => { - matches!( - effect.effect(), - ast::Effect::Async(_) - | ast::Effect::Try(_) - | ast::Effect::Const(_) - ) - } - ast::Expr::ClosureExpr(_) => true, - _ => false, - }; - if is_different_context { - preorder.skip_subtree(); - } - } else if let Some(pat) = ast::Pat::cast(node) { - preorder.skip_subtree(); - pat.walk(cb); - } - } - } - } - } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -374,6 +241,7 @@ impl ast::Literal { .and_then(|e| e.into_token()) .unwrap() } + pub fn kind(&self) -> LiteralKind { let token = self.token(); diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 4070406702..ab47d4ece9 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -1,11 +1,13 @@ //! Various extension methods to ast Nodes, which are hard to code-generate. //! Extensions for various expressions live in a sibling `expr_extensions` module. +//! +//! These methods should only do simple, shallow tasks related to the syntax of the node itself. use std::{borrow::Cow, fmt, iter::successors}; use itertools::Itertools; use parser::SyntaxKind; -use rowan::{GreenNodeData, GreenTokenData, WalkEvent}; +use rowan::{GreenNodeData, GreenTokenData}; use crate::{ ast::{ @@ -56,66 +58,6 @@ impl ast::BlockExpr { pub fn is_empty(&self) -> bool { self.statements().next().is_none() && self.tail_expr().is_none() } - - pub fn as_lone_tail(&self) -> Option { - self.statements().next().is_none().then(|| self.tail_expr()).flatten() - } -} - -impl ast::Pat { - /// Preorder walk all the pattern's sub patterns. - pub fn walk(&self, cb: &mut dyn FnMut(ast::Pat)) { - let mut preorder = self.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 => (), - } - } - } -} - -impl ast::Type { - /// Preorder walk all the type's sub types. - pub fn walk(&self, cb: &mut dyn FnMut(ast::Type)) { - let mut preorder = self.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 => (), - } - } - } } #[derive(Debug, PartialEq, Eq, Clone)] @@ -443,7 +385,15 @@ impl ast::RecordExprField { if let Some(name_ref) = self.name_ref() { return Some(name_ref); } - self.expr()?.name_ref() + if let ast::Expr::PathExpr(expr) = self.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 } } @@ -721,29 +671,6 @@ impl ast::Visibility { None => VisibilityKind::Pub, } } - - pub fn is_eq_to(&self, other: &Self) -> bool { - match (self.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, - } - } } impl ast::LifetimeParam {