X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=crates%2Fide_db%2Fsrc%2Fhelpers.rs;h=e589940dae29f58ef7bb0d2d48eb80a4de89cf21;hb=bfc263f1f98aece963a4b103d787005346f0c1c7;hp=2433d8e918e40e4dc7ddb3fb629b4a236f22fe63;hpb=f2246fecef40fb86806fbe440df3b1eeb19a0f34;p=rust.git diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index 2433d8e918e..e589940dae2 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs @@ -1,21 +1,27 @@ //! 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 insert_whitespace_into_node; +pub mod node_ext; pub mod rust_doc; -pub mod generated_lints; -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 hir::{ItemInNs, MacroDef, ModuleDef, Name, PathResolution, Semantics}; +use itertools::Itertools; use syntax::{ - ast::{self, make, LoopBodyOwner}, - AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T, + ast::{self, make, HasLoopBody}, + AstNode, AstToken, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, + T, }; -use crate::RootDatabase; +use crate::{defs::Definition, RootDatabase}; + +pub use self::famous_defs::FamousDefs; pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option { match item { @@ -25,35 +31,66 @@ 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, - derive_attr: &ast::Attr, - cursor: &SyntaxToken, -) -> Option { - use itertools::Itertools; - if cursor.kind() != T![ident] { +/// 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: &ast::Ident, +) -> Option { + let path = attr.path()?; + let tt = attr.token_tree()?; + if !tt.syntax().text_range().contains_range(cursor.syntax().text_range()) { return None; } - let tt = match derive_attr.as_simple_call() { - Some((name, tt)) - if name == "derive" && tt.syntax().text_range().contains_range(cursor.text_range()) => - { - tt - } - _ => return None, - }; - let tokens: Vec<_> = cursor + 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; + } + get_path_at_cursor_in_tt(cursor) +} + +/// Parses the path the identifier is part of inside a token tree. +pub fn get_path_at_cursor_in_tt(cursor: &ast::Ident) -> Option { + let cursor = cursor.syntax(); + let first = cursor .siblings_with_tokens(Direction::Prev) - .flat_map(SyntaxElement::into_token) + .filter_map(SyntaxElement::into_token) .take_while(|tok| tok.kind() != T!['('] && tok.kind() != T![,]) - .collect(); - let path = ast::Path::parse(&tokens.into_iter().rev().join("")).ok()?; - match sema.scope(tt.syntax()).speculative_resolve(&path) { - Some(hir::PathResolution::Macro(makro)) if makro.kind() == hir::MacroKind::Derive => { - Some(makro) - } - _ => None, + .last()?; + let path_tokens = first + .siblings_with_tokens(Direction::Next) + .filter_map(SyntaxElement::into_token) + .take_while(|tok| tok != cursor); + + syntax::hacks::parse_expr_from_str(&path_tokens.chain(iter::once(cursor.clone())).join("")) + .and_then(|expr| match expr { + ast::Expr::PathExpr(it) => it.path(), + _ => None, + }) +} + +/// 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: &ast::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(_)))), } } @@ -84,7 +121,7 @@ pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { segments.extend( path.segments() .iter() - .map(|segment| make::path_segment(make::name_ref(&segment.to_string()))), + .map(|segment| make::path_segment(make::name_ref(&segment.to_smol_str()))), ); make::path_from_segments(segments, is_abs) } @@ -93,7 +130,7 @@ pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { pub fn visit_file_defs( sema: &Semantics, file_id: FileId, - cb: &mut dyn FnMut(Either), + cb: &mut dyn FnMut(Definition), ) { let db = sema.db; let module = match sema.to_module_def(file_id) { @@ -105,129 +142,12 @@ pub fn visit_file_defs( if let ModuleDef::Module(submodule) = def { if let hir::ModuleSource::Module(_) = submodule.definition_source(db).value { defs.extend(submodule.declarations(db)); - submodule.impl_defs(db).into_iter().for_each(|impl_| cb(Either::Right(impl_))); + submodule.impl_defs(db).into_iter().for_each(|impl_| cb(impl_.into())); } } - cb(Either::Left(def)); - } - 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) + cb(def.into()); } + module.impl_defs(db).into_iter().for_each(|impl_| cb(impl_.into())); } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -252,26 +172,27 @@ pub const fn new(allow_snippets: bool) -> Option { pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) { match expr { ast::Expr::BlockExpr(b) => { - if let Some(e) = b.tail_expr() { - for_each_tail_expr(&e, cb); - } - } - ast::Expr::EffectExpr(e) => match e.effect() { - ast::Effect::Label(label) => { - for_each_break_expr(Some(label), e.block_expr(), &mut |b| { - cb(&ast::Expr::BreakExpr(b)) - }); - if let Some(b) = e.block_expr() { - for_each_tail_expr(&ast::Expr::BlockExpr(b), cb); + 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 => (), } - ast::Effect::Unsafe(_) => { - if let Some(e) = e.block_expr().and_then(|b| b.tail_expr()) { + if let Some(stmt_list) = b.stmt_list() { + if let Some(e) = stmt_list.tail_expr() { for_each_tail_expr(&e, cb); } } - ast::Effect::Async(_) | ast::Effect::Try(_) | ast::Effect::Const(_) => cb(expr), - }, + } ast::Expr::IfExpr(if_) => { let mut if_ = if_.clone(); loop { @@ -289,7 +210,9 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) { } } ast::Expr::LoopExpr(l) => { - for_each_break_expr(l.label(), l.loop_body(), &mut |b| cb(&ast::Expr::BreakExpr(b))) + 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() { @@ -329,7 +252,7 @@ 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( label: Option, - body: Option, + body: Option, cb: &mut dyn FnMut(ast::BreakExpr), ) { let label = label.and_then(|lbl| lbl.lifetime()); @@ -349,7 +272,7 @@ pub fn for_each_break_expr( ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => { depth += 1 } - ast::Expr::EffectExpr(e) if e.label().is_some() => 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()) => { @@ -361,10 +284,55 @@ pub fn for_each_break_expr( ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => { depth -= 1 } - ast::Expr::EffectExpr(e) if e.label().is_some() => depth -= 1, + ast::Expr::BlockExpr(e) if e.label().is_some() => depth -= 1, _ => (), }, } } } } + +/// Checks if the given lint is equal or is contained by the other lint which may or may not be a group. +pub fn lint_eq_or_in_group(lint: &str, lint_is: &str) -> bool { + if lint == lint_is { + return true; + } + + if let Some(group) = generated_lints::DEFAULT_LINT_GROUPS + .iter() + .chain(generated_lints::CLIPPY_LINT_GROUPS.iter()) + .chain(generated_lints::RUSTDOC_LINT_GROUPS.iter()) + .find(|&check| check.lint.label == lint_is) + { + group.children.contains(&lint) + } else { + false + } +} + +/// Parses the input token tree as comma separated plain paths. +pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option> { + let r_paren = input.r_paren_token(); + let tokens = + input.syntax().children_with_tokens().skip(1).map_while(|it| match it.into_token() { + // seeing a keyword means the attribute is unclosed so stop parsing here + Some(tok) if tok.kind().is_keyword() => None, + // don't include the right token tree parenthesis if it exists + tok @ Some(_) if tok == r_paren => None, + // only nodes that we can find are other TokenTrees, those are unexpected in this parse though + None => None, + Some(tok) => Some(tok), + }); + let input_expressions = tokens.into_iter().group_by(|tok| tok.kind() == T![,]); + let paths = input_expressions + .into_iter() + .filter_map(|(is_sep, group)| (!is_sep).then(|| group)) + .filter_map(|mut tokens| { + syntax::hacks::parse_expr_from_str(&tokens.join("")).and_then(|expr| match expr { + ast::Expr::PathExpr(it) => it.path(), + _ => None, + }) + }) + .collect(); + Some(paths) +}