From f79719b8ae4d1929acaa940802a1e293f7dd7a6b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 4 Mar 2020 12:14:48 +0100 Subject: Move find_refs_to_def --- crates/ra_ide_db/Cargo.toml | 1 + crates/ra_ide_db/src/search.rs | 149 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 3 deletions(-) (limited to 'crates/ra_ide_db') diff --git a/crates/ra_ide_db/Cargo.toml b/crates/ra_ide_db/Cargo.toml index 7ff1a536e..52f0f23df 100644 --- a/crates/ra_ide_db/Cargo.toml +++ b/crates/ra_ide_db/Cargo.toml @@ -16,6 +16,7 @@ rayon = "1.3.0" fst = { version = "0.3.5", default-features = false } rustc-hash = "1.1.0" superslice = "1.0.0" +once_cell = "1.3.1" ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs index ca458388c..efd43f4c1 100644 --- a/crates/ra_ide_db/src/search.rs +++ b/crates/ra_ide_db/src/search.rs @@ -4,13 +4,19 @@ //! e.g. for things like local variables. use std::mem; -use hir::{DefWithBody, HasSource, ModuleSource}; +use hir::{DefWithBody, HasSource, ModuleSource, Semantics}; +use once_cell::unsync::Lazy; use ra_db::{FileId, FileRange, SourceDatabaseExt}; use ra_prof::profile; -use ra_syntax::{AstNode, TextRange}; +use ra_syntax::{ + algo::find_node_at_offset, ast, match_ast, AstNode, TextRange, TextUnit, TokenAtOffset, +}; use rustc_hash::FxHashMap; -use crate::{defs::Definition, RootDatabase}; +use crate::{ + defs::{classify_name_ref, Definition}, + RootDatabase, +}; #[derive(Debug, Clone)] pub struct Reference { @@ -164,3 +170,140 @@ impl IntoIterator for SearchScope { self.entries.into_iter() } } + +pub fn find_refs_to_def( + db: &RootDatabase, + def: &Definition, + search_scope: Option, +) -> Vec { + let _p = profile("find_refs_to_def"); + + let search_scope = { + let base = SearchScope::for_def(&def, db); + match search_scope { + None => base, + Some(scope) => base.intersection(&scope), + } + }; + + let name = match def.name(db) { + None => return Vec::new(), + Some(it) => it.to_string(), + }; + + let pat = name.as_str(); + let mut refs = vec![]; + + for (file_id, search_range) in search_scope { + let text = db.file_text(file_id); + let search_range = + search_range.unwrap_or(TextRange::offset_len(0.into(), TextUnit::of_str(&text))); + + let sema = Semantics::new(db); + let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); + + for (idx, _) in text.match_indices(pat) { + let offset = TextUnit::from_usize(idx); + if !search_range.contains_inclusive(offset) { + // tested_by!(search_filters_by_range); + continue; + } + + let name_ref = + if let Some(name_ref) = find_node_at_offset::(&tree, offset) { + name_ref + } else { + // Handle macro token cases + let token = match tree.token_at_offset(offset) { + TokenAtOffset::None => continue, + TokenAtOffset::Single(t) => t, + TokenAtOffset::Between(_, t) => t, + }; + let expanded = sema.descend_into_macros(token); + match ast::NameRef::cast(expanded.parent()) { + Some(name_ref) => name_ref, + _ => continue, + } + }; + + // FIXME: reuse sb + // See https://github.com/rust-lang/rust/pull/68198#issuecomment-574269098 + + if let Some(d) = classify_name_ref(&sema, &name_ref) { + let d = d.definition(); + if &d == def { + let kind = + if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) { + ReferenceKind::StructLiteral + } else { + ReferenceKind::Other + }; + + let file_range = sema.original_range(name_ref.syntax()); + refs.push(Reference { + file_range, + kind, + access: reference_access(&d, &name_ref), + }); + } + } + } + } + refs +} + +fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option { + // Only Locals and Fields have accesses for now. + match def { + Definition::Local(_) | Definition::StructField(_) => {} + _ => return None, + }; + + let mode = name_ref.syntax().ancestors().find_map(|node| { + match_ast! { + match (node) { + ast::BinExpr(expr) => { + if expr.op_kind()?.is_assignment() { + // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals). + // FIXME: This is not terribly accurate. + if let Some(lhs) = expr.lhs() { + if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() { + return Some(ReferenceAccess::Write); + } + } + } + Some(ReferenceAccess::Read) + }, + _ => {None} + } + } + }); + + // Default Locals and Fields to read + mode.or(Some(ReferenceAccess::Read)) +} + +fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { + name_ref + .syntax() + .ancestors() + .find_map(ast::CallExpr::cast) + .and_then(|c| match c.expr()? { + ast::Expr::PathExpr(p) => { + Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref)) + } + _ => None, + }) + .unwrap_or(false) +} + +fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { + name_ref + .syntax() + .ancestors() + .find_map(ast::RecordLit::cast) + .and_then(|l| l.path()) + .and_then(|p| p.segment()) + .map(|p| p.name_ref().as_ref() == Some(name_ref)) + .unwrap_or(false) +} -- cgit v1.2.3