From c3a4c4429de83450654795534e64e878a774a088 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 18 Feb 2020 18:35:10 +0100 Subject: Refactor primary IDE API This introduces the new type -- Semantics. Semantics maps SyntaxNodes to various semantic info, such as type, name resolution or macro expansions. To do so, Semantics maintains a HashMap which maps every node it saw to the file from which the node originated. This is enough to get all the necessary hir bits just from syntax. --- crates/ra_ide/src/references.rs | 130 +++++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 61 deletions(-) (limited to 'crates/ra_ide/src/references.rs') diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index aadc2dbcb..baa8a4d29 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -13,25 +13,22 @@ mod classify; mod rename; mod search_scope; -use crate::expand::descend_into_macros_with_analyzer; -use hir::{InFile, SourceBinder}; +use hir::Semantics; use once_cell::unsync::Lazy; -use ra_db::{SourceDatabase, SourceDatabaseExt}; +use ra_db::SourceDatabaseExt; use ra_ide_db::RootDatabase; use ra_prof::profile; use ra_syntax::{ algo::find_node_at_offset, ast::{self, NameOwner}, - match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, TextUnit, TokenAtOffset, + match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TextUnit, TokenAtOffset, }; +use test_utils::tested_by; use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; -pub(crate) use self::{ - classify::{classify_name, classify_name_ref}, - rename::rename, -}; -pub(crate) use ra_ide_db::defs::NameDefinition; +pub(crate) use self::{classify::classify_name_ref, rename::rename}; +pub(crate) use ra_ide_db::defs::{classify_name, NameDefinition}; pub use self::search_scope::SearchScope; @@ -114,8 +111,8 @@ pub(crate) fn find_all_refs( position: FilePosition, search_scope: Option, ) -> Option> { - let parse = db.parse(position.file_id); - let syntax = parse.tree().syntax().clone(); + let sema = Semantics::new(db); + let syntax = sema.parse(position.file_id).syntax().clone(); let (opt_name, search_kind) = if let Some(name) = get_struct_def_name_for_struc_litetal_search(&syntax, position) { @@ -124,7 +121,7 @@ pub(crate) fn find_all_refs( (find_node_at_offset::(&syntax, position.offset), ReferenceKind::Other) }; - let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position, opt_name)?; + let RangeInfo { range, info: (name, def) } = find_name(&sema, &syntax, position, opt_name)?; let declaration = def.try_to_nav(db)?; let search_scope = { @@ -152,19 +149,18 @@ pub(crate) fn find_all_refs( } fn find_name( - db: &RootDatabase, + sema: &Semantics, syntax: &SyntaxNode, position: FilePosition, opt_name: Option, ) -> Option> { - let mut sb = SourceBinder::new(db); if let Some(name) = opt_name { - let def = classify_name(&mut sb, InFile::new(position.file_id.into(), &name))?; + let def = classify_name(sema, &name)?; let range = name.syntax().text_range(); return Some(RangeInfo::new(range, (name.text().to_string(), def))); } let name_ref = find_node_at_offset::(&syntax, position.offset)?; - let def = classify_name_ref(&mut sb, InFile::new(position.file_id.into(), &name_ref))?; + let def = classify_name_ref(sema, &name_ref)?; let range = name_ref.syntax().text_range(); Some(RangeInfo::new(range, (name_ref.text().to_string(), def))) } @@ -182,64 +178,53 @@ fn process_definition( for (file_id, search_range) in 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 parse = Lazy::new(|| SourceFile::parse(&text)); - let mut sb = Lazy::new(|| SourceBinder::new(db)); - let mut analyzer = None; + 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, range) = if let Some(name_ref) = - find_node_at_offset::(parse.tree().syntax(), offset) - { - let range = name_ref.syntax().text_range(); - (InFile::new(file_id.into(), name_ref), range) - } else { - // Handle macro token cases - let t = match parse.tree().syntax().token_at_offset(offset) { - TokenAtOffset::None => continue, - TokenAtOffset::Single(t) => t, - TokenAtOffset::Between(_, t) => t, - }; - let range = t.text_range(); - let analyzer = analyzer.get_or_insert_with(|| { - sb.analyze(InFile::new(file_id.into(), parse.tree().syntax()), None) - }); - let expanded = descend_into_macros_with_analyzer( - db, - &analyzer, - InFile::new(file_id.into(), t), - ); - if let Some(token) = ast::NameRef::cast(expanded.value.parent()) { - (expanded.with_value(token), range) + let name_ref = + if let Some(name_ref) = find_node_at_offset::(&tree, offset) { + name_ref } else { - continue; - } - }; + // 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, + } + }; - if let Some(search_range) = search_range { - if !range.is_subrange(&search_range) { - continue; - } - } // FIXME: reuse sb // See https://github.com/rust-lang/rust/pull/68198#issuecomment-574269098 - if let Some(d) = classify_name_ref(&mut sb, name_ref.as_ref()) { + if let Some(d) = classify_name_ref(&sema, &name_ref) { if d == def { - let kind = if is_record_lit_name_ref(&name_ref.value) - || is_call_expr_name_ref(&name_ref.value) - { - ReferenceKind::StructLiteral - } else { - ReferenceKind::Other - }; - + 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: FileRange { file_id, range }, + file_range, kind, - access: reference_access(&d, &name_ref.value), + access: reference_access(&d, &name_ref), }); } } @@ -348,6 +333,8 @@ fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { #[cfg(test)] mod tests { + use test_utils::covers; + use crate::{ mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, Declaration, Reference, ReferenceSearchResult, SearchScope, @@ -455,6 +442,27 @@ mod tests { ); } + #[test] + fn search_filters_by_range() { + covers!(search_filters_by_range); + let code = r#" + fn foo() { + let spam<|> = 92; + spam + spam + } + fn bar() { + let spam = 92; + spam + spam + } + "#; + let refs = get_all_refs(code); + check_result( + refs, + "spam BIND_PAT FileId(1) [44; 48) Other Write", + &["FileId(1) [71; 75) Other Read", "FileId(1) [78; 82) Other Read"], + ); + } + #[test] fn test_find_all_refs_for_param_inside() { let code = r#" -- cgit v1.2.3