X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=crates%2Fide_db%2Fsrc%2Fhelpers.rs;h=f6a1a5521836a8a1e41e258a217bae04301be4ed;hb=3018ffd85e6921ba57d4340f666269ec85e58902;hp=66798ea3a69aa310d50d50d4d5f9a187457da640;hpb=c2be91dcd826e1529ac6ac431b3f871ec72abebc;p=rust.git diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index 66798ea3a69..f6a1a552183 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs @@ -1,16 +1,28 @@ //! A module with ide helpers for high-level ide features. -pub mod insert_use; +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; -use std::collections::VecDeque; +use std::{collections::VecDeque, iter}; use base_db::FileId; use either::Either; -use hir::{Crate, Enum, ItemInNs, MacroDef, Module, ModuleDef, Name, ScopeDef, Semantics, Trait}; -use syntax::ast::{self, make}; +use hir::{ItemInNs, MacroDef, ModuleDef, Name, PathResolution, Semantics}; +use itertools::Itertools; +use syntax::{ + ast::{self, make, HasLoopBody, Ident}, + AstNode, AstToken, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, + T, +}; 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), @@ -19,6 +31,69 @@ pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option { } } +/// Parses and returns the derive path at the cursor position in the given attribute, if it is a derive. +/// This special case is required because the derive macro is a compiler builtin that discards the input derives. +/// +/// The returned path is synthesized from TokenTree tokens and as such cannot be used with the [`Semantics`]. +pub fn get_path_in_derive_attr( + sema: &hir::Semantics, + attr: &ast::Attr, + cursor: &Ident, +) -> Option { + let cursor = cursor.syntax(); + let path = attr.path()?; + let tt = attr.token_tree()?; + if !tt.syntax().text_range().contains_range(cursor.text_range()) { + return None; + } + let scope = sema.scope(attr.syntax()); + let resolved_attr = sema.resolve_path(&path)?; + let derive = FamousDefs(sema, scope.krate()).core_macros_builtin_derive()?; + if PathResolution::Macro(derive) != resolved_attr { + return None; + } + + let first = cursor + .siblings_with_tokens(Direction::Prev) + .filter_map(SyntaxElement::into_token) + .take_while(|tok| tok.kind() != T!['('] && tok.kind() != T![,]) + .last()?; + let path_tokens = first + .siblings_with_tokens(Direction::Next) + .filter_map(SyntaxElement::into_token) + .take_while(|tok| tok != cursor); + + ast::Path::parse(&path_tokens.chain(iter::once(cursor.clone())).join("")).ok() +} + +/// Parses and resolves the path at the cursor position in the given attribute, if it is a derive. +/// This special case is required because the derive macro is a compiler builtin that discards the input derives. +pub fn try_resolve_derive_input( + sema: &hir::Semantics, + attr: &ast::Attr, + cursor: &Ident, +) -> Option { + let path = get_path_in_derive_attr(sema, attr, cursor)?; + let scope = sema.scope(attr.syntax()); + // FIXME: This double resolve shouldn't be necessary + // It's only here so we prefer macros over other namespaces + match scope.speculative_resolve_as_mac(&path) { + Some(mac) if mac.kind() == hir::MacroKind::Derive => Some(PathResolution::Macro(mac)), + Some(_) => return None, + None => scope + .speculative_resolve(&path) + .filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_)))), + } +} + +/// Picks the token with the highest rank returned by the passed in function. +pub fn pick_best_token( + tokens: TokenAtOffset, + f: impl Fn(SyntaxKind) -> usize, +) -> Option { + tokens.max_by_key(move |t| f(t.kind())) +} + /// Converts the mod path struct into its ast representation. pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { let _p = profile::span("mod_path_to_ast"); @@ -67,104 +142,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. -pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option); - -#[allow(non_snake_case)] -impl FamousDefs<'_, '_> { - pub const FIXTURE: &'static str = include_str!("helpers/famous_defs_fixture.rs"); - - 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_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(&self) -> Option { - self.find_module("core:iter") - } - - 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: (), @@ -179,3 +156,130 @@ pub const fn new(allow_snippets: bool) -> Option { } } } + +/// Calls `cb` on each expression inside `expr` that is at "tail position". +/// Does not walk into `break` or `return` expressions. +/// Note that modifying the tree while iterating it will cause undefined iteration which might +/// potentially results in an out of bounds panic. +pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) { + match expr { + ast::Expr::BlockExpr(b) => { + match b.modifier() { + Some( + ast::BlockModifier::Async(_) + | ast::BlockModifier::Try(_) + | ast::BlockModifier::Const(_), + ) => return cb(expr), + + Some(ast::BlockModifier::Label(label)) => { + for_each_break_expr(Some(label), b.stmt_list(), &mut |b| { + cb(&ast::Expr::BreakExpr(b)) + }); + } + Some(ast::BlockModifier::Unsafe(_)) => (), + None => (), + } + if let Some(stmt_list) = b.stmt_list() { + if let Some(e) = stmt_list.tail_expr() { + for_each_tail_expr(&e, cb); + } + } + } + ast::Expr::IfExpr(if_) => { + let mut if_ = if_.clone(); + loop { + if let Some(block) = if_.then_branch() { + for_each_tail_expr(&ast::Expr::BlockExpr(block), cb); + } + match if_.else_branch() { + Some(ast::ElseBranch::IfExpr(it)) => if_ = it, + Some(ast::ElseBranch::Block(block)) => { + for_each_tail_expr(&ast::Expr::BlockExpr(block), cb); + break; + } + None => break, + } + } + } + ast::Expr::LoopExpr(l) => { + for_each_break_expr(l.label(), l.loop_body().and_then(|it| it.stmt_list()), &mut |b| { + cb(&ast::Expr::BreakExpr(b)) + }) + } + ast::Expr::MatchExpr(m) => { + if let Some(arms) = m.match_arm_list() { + arms.arms().filter_map(|arm| arm.expr()).for_each(|e| for_each_tail_expr(&e, cb)); + } + } + ast::Expr::ArrayExpr(_) + | ast::Expr::AwaitExpr(_) + | ast::Expr::BinExpr(_) + | ast::Expr::BoxExpr(_) + | ast::Expr::BreakExpr(_) + | ast::Expr::CallExpr(_) + | ast::Expr::CastExpr(_) + | ast::Expr::ClosureExpr(_) + | ast::Expr::ContinueExpr(_) + | ast::Expr::FieldExpr(_) + | ast::Expr::ForExpr(_) + | ast::Expr::IndexExpr(_) + | ast::Expr::Literal(_) + | ast::Expr::MacroCall(_) + | ast::Expr::MacroStmts(_) + | ast::Expr::MethodCallExpr(_) + | ast::Expr::ParenExpr(_) + | ast::Expr::PathExpr(_) + | ast::Expr::PrefixExpr(_) + | ast::Expr::RangeExpr(_) + | ast::Expr::RecordExpr(_) + | ast::Expr::RefExpr(_) + | ast::Expr::ReturnExpr(_) + | ast::Expr::TryExpr(_) + | ast::Expr::TupleExpr(_) + | ast::Expr::WhileExpr(_) + | ast::Expr::YieldExpr(_) => cb(expr), + } +} + +/// Calls `cb` on each break expr inside of `body` that is applicable for the given label. +pub fn for_each_break_expr( + label: Option, + body: Option, + 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| { + 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, + _ => (), + }, + } + } + } +}