From fbdb32adfc49e0d69b7fd8e44135bea59382d2cb Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 12 Jan 2021 00:05:07 +0100 Subject: Group references by FileId --- .../handlers/extract_struct_from_enum_variant.rs | 59 ++++++------ .../assists/src/handlers/inline_local_variable.rs | 95 +++++++++---------- crates/assists/src/handlers/remove_unused_param.rs | 43 +++++---- crates/ide/src/call_hierarchy.rs | 31 ++++--- crates/ide/src/lib.rs | 2 +- crates/ide/src/references.rs | 98 ++++++++++---------- crates/ide/src/references/rename.rs | 94 +++++++++---------- crates/ide_db/src/search.rs | 101 +++++++++++++-------- crates/rust-analyzer/src/handlers.rs | 33 +++++-- crates/ssr/src/search.rs | 24 +++-- 10 files changed, 324 insertions(+), 256 deletions(-) diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs index 40028fc01..21b13977b 100644 --- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs @@ -2,12 +2,16 @@ use std::iter; use either::Either; use hir::{AsName, Module, ModuleDef, Name, Variant}; -use ide_db::helpers::{ - insert_use::{insert_use, ImportScope}, - mod_path_to_ast, +use ide_db::{ + defs::Definition, + helpers::{ + insert_use::{insert_use, ImportScope}, + mod_path_to_ast, + }, + search::{FileReference, FileReferences}, + RootDatabase, }; -use ide_db::{defs::Definition, search::Reference, RootDatabase}; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use syntax::{ algo::{find_node_at_offset, SyntaxRewriter}, ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner}, @@ -58,29 +62,29 @@ pub(crate) fn extract_struct_from_enum_variant( let mut visited_modules_set = FxHashSet::default(); let current_module = enum_hir.module(ctx.db()); visited_modules_set.insert(current_module); - let mut rewriters = FxHashMap::default(); - for reference in usages { - let rewriter = rewriters - .entry(reference.file_range.file_id) - .or_insert_with(SyntaxRewriter::default); - let source_file = ctx.sema.parse(reference.file_range.file_id); - update_reference( - ctx, - rewriter, - reference, - &source_file, - &enum_module_def, - &variant_hir_name, - &mut visited_modules_set, - ); - } - let mut rewriter = - rewriters.remove(&ctx.frange.file_id).unwrap_or_else(SyntaxRewriter::default); - for (file_id, rewriter) in rewriters { + let mut def_rewriter = None; + for FileReferences { file_id, references: refs } in usages { + let mut rewriter = SyntaxRewriter::default(); + let source_file = ctx.sema.parse(file_id); + for reference in refs { + update_reference( + ctx, + &mut rewriter, + reference, + &source_file, + &enum_module_def, + &variant_hir_name, + &mut visited_modules_set, + ); + } + if file_id == ctx.frange.file_id { + def_rewriter = Some(rewriter); + continue; + } builder.edit_file(file_id); builder.rewrite(rewriter); } - builder.edit_file(ctx.frange.file_id); + let mut rewriter = def_rewriter.unwrap_or_default(); update_variant(&mut rewriter, &variant); extract_struct_def( &mut rewriter, @@ -90,6 +94,7 @@ pub(crate) fn extract_struct_from_enum_variant( &variant.parent_enum().syntax().clone().into(), enum_ast.visibility(), ); + builder.edit_file(ctx.frange.file_id); builder.rewrite(rewriter); }, ) @@ -205,13 +210,13 @@ fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Opti fn update_reference( ctx: &AssistContext, rewriter: &mut SyntaxRewriter, - reference: Reference, + reference: FileReference, source_file: &SourceFile, enum_module_def: &ModuleDef, variant_hir_name: &Name, visited_modules_set: &mut FxHashSet, ) -> Option<()> { - let offset = reference.file_range.range.start(); + let offset = reference.range.start(); let (segment, expr) = if let Some(path_expr) = find_node_at_offset::(source_file.syntax(), offset) { diff --git a/crates/assists/src/handlers/inline_local_variable.rs b/crates/assists/src/handlers/inline_local_variable.rs index d559be9cb..928df6825 100644 --- a/crates/assists/src/handlers/inline_local_variable.rs +++ b/crates/assists/src/handlers/inline_local_variable.rs @@ -1,4 +1,7 @@ -use ide_db::{defs::Definition, search::ReferenceKind}; +use ide_db::{ + defs::Definition, + search::{FileReference, ReferenceKind}, +}; use syntax::{ ast::{self, AstNode, AstToken}, TextRange, @@ -63,48 +66,44 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O let_stmt.syntax().text_range() }; - let mut wrap_in_parens = vec![true; refs.len()]; - - for (i, desc) in refs.iter().enumerate() { - let usage_node = ctx - .covering_node_for_range(desc.file_range.range) - .ancestors() - .find_map(ast::PathExpr::cast)?; - let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); - let usage_parent = match usage_parent_option { - Some(u) => u, - None => { - wrap_in_parens[i] = false; - continue; - } - }; - - wrap_in_parens[i] = match (&initializer_expr, usage_parent) { - (ast::Expr::CallExpr(_), _) - | (ast::Expr::IndexExpr(_), _) - | (ast::Expr::MethodCallExpr(_), _) - | (ast::Expr::FieldExpr(_), _) - | (ast::Expr::TryExpr(_), _) - | (ast::Expr::RefExpr(_), _) - | (ast::Expr::Literal(_), _) - | (ast::Expr::TupleExpr(_), _) - | (ast::Expr::ArrayExpr(_), _) - | (ast::Expr::ParenExpr(_), _) - | (ast::Expr::PathExpr(_), _) - | (ast::Expr::BlockExpr(_), _) - | (ast::Expr::EffectExpr(_), _) - | (_, ast::Expr::CallExpr(_)) - | (_, ast::Expr::TupleExpr(_)) - | (_, ast::Expr::ArrayExpr(_)) - | (_, ast::Expr::ParenExpr(_)) - | (_, ast::Expr::ForExpr(_)) - | (_, ast::Expr::WhileExpr(_)) - | (_, ast::Expr::BreakExpr(_)) - | (_, ast::Expr::ReturnExpr(_)) - | (_, ast::Expr::MatchExpr(_)) => false, - _ => true, - }; - } + let wrap_in_parens = refs + .iter() + .flat_map(|refs| &refs.references) + .map(|&FileReference { range, .. }| { + let usage_node = + ctx.covering_node_for_range(range).ancestors().find_map(ast::PathExpr::cast)?; + let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); + let usage_parent = match usage_parent_option { + Some(u) => u, + None => return Ok(false), + }; + + Ok(!matches!((&initializer_expr, usage_parent), + (ast::Expr::CallExpr(_), _) + | (ast::Expr::IndexExpr(_), _) + | (ast::Expr::MethodCallExpr(_), _) + | (ast::Expr::FieldExpr(_), _) + | (ast::Expr::TryExpr(_), _) + | (ast::Expr::RefExpr(_), _) + | (ast::Expr::Literal(_), _) + | (ast::Expr::TupleExpr(_), _) + | (ast::Expr::ArrayExpr(_), _) + | (ast::Expr::ParenExpr(_), _) + | (ast::Expr::PathExpr(_), _) + | (ast::Expr::BlockExpr(_), _) + | (ast::Expr::EffectExpr(_), _) + | (_, ast::Expr::CallExpr(_)) + | (_, ast::Expr::TupleExpr(_)) + | (_, ast::Expr::ArrayExpr(_)) + | (_, ast::Expr::ParenExpr(_)) + | (_, ast::Expr::ForExpr(_)) + | (_, ast::Expr::WhileExpr(_)) + | (_, ast::Expr::BreakExpr(_)) + | (_, ast::Expr::ReturnExpr(_)) + | (_, ast::Expr::MatchExpr(_)) + )) + }) + .collect::, _>>()?; let init_str = initializer_expr.syntax().text().to_string(); let init_in_paren = format!("({})", &init_str); @@ -116,15 +115,17 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O target, move |builder| { builder.delete(delete_range); - for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { + for (reference, should_wrap) in + refs.iter().flat_map(|refs| &refs.references).zip(wrap_in_parens) + { let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() }; - match desc.kind { + match reference.kind { ReferenceKind::FieldShorthandForLocal => { mark::hit!(inline_field_shorthand); - builder.insert(desc.file_range.range.end(), format!(": {}", replacement)) + builder.insert(reference.range.end(), format!(": {}", replacement)) } - _ => builder.replace(desc.file_range.range, replacement), + _ => builder.replace(reference.range, replacement), } } }, diff --git a/crates/assists/src/handlers/remove_unused_param.rs b/crates/assists/src/handlers/remove_unused_param.rs index 56e8b5229..4f3b8ac46 100644 --- a/crates/assists/src/handlers/remove_unused_param.rs +++ b/crates/assists/src/handlers/remove_unused_param.rs @@ -1,8 +1,11 @@ -use ide_db::{defs::Definition, search::Reference}; +use ide_db::{ + defs::Definition, + search::{FileReference, FileReferences}, +}; use syntax::{ algo::find_node_at_range, ast::{self, ArgListOwner}, - AstNode, SyntaxKind, SyntaxNode, TextRange, T, + AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T, }; use test_utils::mark; use SyntaxKind::WHITESPACE; @@ -58,32 +61,40 @@ pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Opt param.syntax().text_range(), |builder| { builder.delete(range_to_remove(param.syntax())); - for usage in fn_def.usages(&ctx.sema).all() { - process_usage(ctx, builder, usage, param_position); + for usages in fn_def.usages(&ctx.sema).all() { + process_usages(ctx, builder, usages, param_position); } }, ) } -fn process_usage( +fn process_usages( ctx: &AssistContext, builder: &mut AssistBuilder, - usage: Reference, + usages: FileReferences, + arg_to_remove: usize, +) { + let source_file = ctx.sema.parse(usages.file_id); + builder.edit_file(usages.file_id); + for usage in usages.references { + if let Some(text_range) = process_usage(&source_file, usage, arg_to_remove) { + builder.delete(text_range); + } + } +} + +fn process_usage( + source_file: &SourceFile, + FileReference { range, .. }: FileReference, arg_to_remove: usize, -) -> Option<()> { - let source_file = ctx.sema.parse(usage.file_range.file_id); - let call_expr: ast::CallExpr = - find_node_at_range(source_file.syntax(), usage.file_range.range)?; +) -> Option { + let call_expr: ast::CallExpr = find_node_at_range(source_file.syntax(), range)?; let call_expr_range = call_expr.expr()?.syntax().text_range(); - if !call_expr_range.contains_range(usage.file_range.range) { + if !call_expr_range.contains_range(range) { return None; } let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?; - - builder.edit_file(usage.file_range.file_id); - builder.delete(range_to_remove(arg.syntax())); - - Some(()) + Some(range_to_remove(arg.syntax())) } fn range_to_remove(node: &SyntaxNode) -> TextRange { diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index b29d1fef9..90d3b9a31 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/crates/ide/src/call_hierarchy.rs @@ -3,8 +3,8 @@ use indexmap::IndexMap; use hir::Semantics; -use ide_db::call_info::FnCallNode; use ide_db::RootDatabase; +use ide_db::{call_info::FnCallNode, search::FileReferences}; use syntax::{ast, AstNode, TextRange}; use crate::{ @@ -47,22 +47,23 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio let mut calls = CallLocations::default(); - for reference in refs.info.references() { - let file_id = reference.file_range.file_id; + for &FileReferences { file_id, ref references } in refs.info.references() { let file = sema.parse(file_id); let file = file.syntax(); - let token = file.token_at_offset(reference.file_range.range.start()).next()?; - let token = sema.descend_into_macros(token); - let syntax = token.parent(); - - // This target is the containing function - if let Some(nav) = syntax.ancestors().find_map(|node| { - let fn_ = ast::Fn::cast(node)?; - let def = sema.to_def(&fn_)?; - def.try_to_nav(sema.db) - }) { - let relative_range = reference.file_range.range; - calls.add(&nav, relative_range); + for reference in references { + let token = file.token_at_offset(reference.range.start()).next()?; + let token = sema.descend_into_macros(token); + let syntax = token.parent(); + + // This target is the containing function + if let Some(nav) = syntax.ancestors().find_map(|node| { + let fn_ = ast::Fn::cast(node)?; + let def = sema.to_def(&fn_)?; + def.try_to_nav(sema.db) + }) { + let relative_range = reference.range; + calls.add(&nav, relative_range); + } } } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 1f368cbd0..1e03832ec 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -92,7 +92,7 @@ pub use ide_db::base_db::{ }; pub use ide_db::{ call_info::CallInfo, - search::{Reference, ReferenceAccess, ReferenceKind}, + search::{FileReference, ReferenceAccess, ReferenceKind}, }; pub use ide_db::{ label::Label, diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index b774a2be1..132680bfb 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -14,8 +14,7 @@ pub(crate) mod rename; use hir::Semantics; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, - search::Reference, - search::{ReferenceAccess, ReferenceKind, SearchScope}, + search::{FileReference, FileReferences, ReferenceAccess, ReferenceKind, SearchScope}, RootDatabase, }; use syntax::{ @@ -29,7 +28,7 @@ use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeI #[derive(Debug, Clone)] pub struct ReferenceSearchResult { declaration: Declaration, - references: Vec, + references: Vec, } #[derive(Debug, Clone)] @@ -48,7 +47,7 @@ impl ReferenceSearchResult { &self.declaration.nav } - pub fn references(&self) -> &[Reference] { + pub fn references(&self) -> &[FileReferences] { &self.references } @@ -63,20 +62,22 @@ impl ReferenceSearchResult { // allow turning ReferenceSearchResult into an iterator // over References impl IntoIterator for ReferenceSearchResult { - type Item = Reference; - type IntoIter = std::vec::IntoIter; + type Item = FileReferences; + type IntoIter = std::vec::IntoIter; fn into_iter(mut self) -> Self::IntoIter { let mut v = Vec::with_capacity(self.len()); - v.push(Reference { - file_range: FileRange { - file_id: self.declaration.nav.file_id, - range: self.declaration.nav.focus_or_full_range(), - }, + v.append(&mut self.references); + let decl_ref = FileReference { + range: self.declaration.nav.focus_or_full_range(), kind: self.declaration.kind, access: self.declaration.access, - }); - v.append(&mut self.references); + }; + let file_id = self.declaration.nav.file_id; + match v.iter_mut().find(|it| it.file_id == file_id) { + Some(file_refs) => file_refs.references.push(decl_ref), + None => v.push(FileReferences { file_id, references: vec![decl_ref] }), + } v.into_iter() } } @@ -109,13 +110,11 @@ pub(crate) fn find_all_refs( let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; - let references = def - .usages(sema) - .set_scope(search_scope) - .all() - .into_iter() - .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) - .collect(); + let mut references = def.usages(sema).set_scope(search_scope).all(); + references.iter_mut().for_each(|it| { + it.references.retain(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) + }); + references.retain(|r| !r.references.is_empty()); let nav = def.try_to_nav(sema.db)?; let decl_range = nav.focus_or_full_range(); @@ -255,7 +254,8 @@ fn try_find_self_references( syntax: &SyntaxNode, position: FilePosition, ) -> Option> { - let self_token = syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])?; + let FilePosition { file_id, offset } = position; + let self_token = syntax.token_at_offset(offset).find(|t| t.kind() == T![self])?; let parent = self_token.parent(); match_ast! { match parent { @@ -276,7 +276,7 @@ fn try_find_self_references( let declaration = Declaration { nav: NavigationTarget { - file_id: position.file_id, + file_id, full_range: self_param.syntax().text_range(), focus_range: Some(param_self_token.text_range()), name: param_self_token.text().clone(), @@ -295,25 +295,29 @@ fn try_find_self_references( let references = function .body() .map(|body| { - body.syntax() - .descendants() - .filter_map(ast::PathExpr::cast) - .filter_map(|expr| { - let path = expr.path()?; - if path.qualifier().is_none() { - path.segment()?.self_token() - } else { - None - } - }) - .map(|token| Reference { - file_range: FileRange { file_id: position.file_id, range: token.text_range() }, - kind: ReferenceKind::SelfKw, - access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration - }) - .collect() + FileReferences { + file_id, + references: body + .syntax() + .descendants() + .filter_map(ast::PathExpr::cast) + .filter_map(|expr| { + let path = expr.path()?; + if path.qualifier().is_none() { + path.segment()?.self_token() + } else { + None + } + }) + .map(|token| FileReference { + range: token.text_range(), + kind: ReferenceKind::SelfKw, + access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration + }) + .collect(), + } }) - .unwrap_or_default(); + .map_or_else(Vec::default, |it| vec![it]); Some(RangeInfo::new( param_self_token.text_range(), @@ -324,7 +328,7 @@ fn try_find_self_references( #[cfg(test)] mod tests { use expect_test::{expect, Expect}; - use ide_db::base_db::FileId; + use ide_db::{base_db::FileId, search::FileReferences}; use stdx::format_to; use crate::{fixture, SearchScope}; @@ -1018,12 +1022,14 @@ impl Foo { actual += "\n\n"; } - for r in &refs.references { - format_to!(actual, "{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind); - if let Some(access) = r.access { - format_to!(actual, " {:?}", access); + for FileReferences { file_id, references } in refs.references { + for r in references { + format_to!(actual, "{:?} {:?} {:?}", file_id, r.range, r.kind); + if let Some(access) = r.access { + format_to!(actual, " {:?}", access); + } + actual += "\n"; } - actual += "\n"; } expect.assert_eq(&actual) } diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 3edc43e08..dd08e1c32 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs @@ -6,9 +6,10 @@ use std::{ }; use hir::{Module, ModuleDef, ModuleSource, Semantics}; -use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt}; use ide_db::{ + base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt}, defs::{Definition, NameClass, NameRefClass}, + search::FileReferences, RootDatabase, }; use syntax::{ @@ -20,8 +21,8 @@ use test_utils::mark; use text_edit::TextEdit; use crate::{ - FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult, - SourceChange, SourceFileEdit, TextRange, TextSize, + FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, + SourceFileEdit, TextRange, TextSize, }; type RenameResult = Result; @@ -173,39 +174,45 @@ fn find_all_refs( .ok_or_else(|| format_err!("No references found at position")) } -fn source_edit_from_reference( +fn source_edit_from_references( sema: &Semantics, - reference: Reference, + &FileReferences { file_id, ref references }: &FileReferences, new_name: &str, ) -> SourceFileEdit { - let mut replacement_text = String::new(); - let range = match reference.kind { - ReferenceKind::FieldShorthandForField => { - mark::hit!(test_rename_struct_field_for_shorthand); - replacement_text.push_str(new_name); - replacement_text.push_str(": "); - TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) - } - ReferenceKind::FieldShorthandForLocal => { - mark::hit!(test_rename_local_for_field_shorthand); - replacement_text.push_str(": "); - replacement_text.push_str(new_name); - TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) - } - ReferenceKind::RecordFieldExprOrPat => { - mark::hit!(test_rename_field_expr_pat); - replacement_text.push_str(new_name); - edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name) - } - _ => { - replacement_text.push_str(new_name); - reference.file_range.range - } - }; - SourceFileEdit { - file_id: reference.file_range.file_id, - edit: TextEdit::replace(range, replacement_text), + let mut edit = TextEdit::builder(); + for reference in references { + let mut replacement_text = String::new(); + let range = match reference.kind { + ReferenceKind::FieldShorthandForField => { + mark::hit!(test_rename_struct_field_for_shorthand); + replacement_text.push_str(new_name); + replacement_text.push_str(": "); + TextRange::new(reference.range.start(), reference.range.start()) + } + ReferenceKind::FieldShorthandForLocal => { + mark::hit!(test_rename_local_for_field_shorthand); + replacement_text.push_str(": "); + replacement_text.push_str(new_name); + TextRange::new(reference.range.end(), reference.range.end()) + } + ReferenceKind::RecordFieldExprOrPat => { + mark::hit!(test_rename_field_expr_pat); + replacement_text.push_str(new_name); + edit_text_range_for_record_field_expr_or_pat( + sema, + FileRange { file_id, range: reference.range }, + new_name, + ) + } + _ => { + replacement_text.push_str(new_name); + reference.range + } + }; + edit.replace(range, replacement_text); } + + SourceFileEdit { file_id, edit: edit.finish() } } fn edit_text_range_for_record_field_expr_or_pat( @@ -277,9 +284,9 @@ fn rename_mod( let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; let ref_edits = refs - .references - .into_iter() - .map(|reference| source_edit_from_reference(sema, reference, new_name)); + .references() + .iter() + .map(|reference| source_edit_from_references(sema, reference, new_name)); source_file_edits.extend(ref_edits); Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) @@ -331,17 +338,10 @@ fn rename_to_self( let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; - let (param_ref, usages): (Vec, Vec) = refs - .into_iter() - .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); - - if param_ref.is_empty() { - bail!("Parameter to rename not found"); - } - - let mut edits = usages - .into_iter() - .map(|reference| source_edit_from_reference(sema, reference, "self")) + let mut edits = refs + .references() + .iter() + .map(|reference| source_edit_from_references(sema, reference, "self")) .collect::>(); edits.push(SourceFileEdit { @@ -467,7 +467,7 @@ fn rename_reference( let edit = refs .into_iter() - .map(|reference| source_edit_from_reference(sema, reference, new_name)) + .map(|reference| source_edit_from_references(sema, &reference, new_name)) .collect::>(); Ok(RangeInfo::new(range, SourceChange::from(edit))) diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 773bfbc2c..b8359a9b4 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs @@ -8,6 +8,7 @@ use std::{convert::TryInto, mem}; use base_db::{FileId, FileRange, SourceDatabaseExt}; use hir::{DefWithBody, HasSource, Module, ModuleSource, Semantics, Visibility}; +use itertools::Itertools; use once_cell::unsync::Lazy; use rustc_hash::FxHashMap; use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; @@ -19,8 +20,22 @@ use crate::{ }; #[derive(Debug, Clone)] -pub struct Reference { - pub file_range: FileRange, +pub struct FileReferences { + pub file_id: FileId, + pub references: Vec, +} + +impl FileReferences { + pub fn file_ranges(&self) -> impl Iterator + '_ { + self.references + .iter() + .map(move |&FileReference { range, .. }| FileRange { file_id: self.file_id, range }) + } +} + +#[derive(Debug, Clone)] +pub struct FileReference { + pub range: TextRange, pub kind: ReferenceKind, pub access: Option, } @@ -252,23 +267,33 @@ impl<'a> FindUsages<'a> { pub fn at_least_one(self) -> bool { let mut found = false; - self.search(&mut |_reference| { + self.search(&mut |_, _| { found = true; true }); found } - pub fn all(self) -> Vec { - let mut res = Vec::new(); - self.search(&mut |reference| { - res.push(reference); + /// The [`FileReferences`] returned always have unique [`FileId`]s. + pub fn all(self) -> Vec { + let mut res = >::new(); + self.search(&mut |file_id, reference| { + match res.iter_mut().find(|it| it.file_id == file_id) { + Some(file_refs) => file_refs.references.push(reference), + _ => res.push(FileReferences { file_id, references: vec![reference] }), + } false }); + assert!(res + .iter() + .map(|refs| refs.file_id) + .sorted_unstable() + .tuple_windows::<(_, _)>() + .all(|(a, b)| a < b)); res } - fn search(self, sink: &mut dyn FnMut(Reference) -> bool) { + fn search(self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) { let _p = profile::span("FindUsages:search"); let sema = self.sema; @@ -320,16 +345,14 @@ impl<'a> FindUsages<'a> { fn found_lifetime( &self, lifetime: &ast::Lifetime, - sink: &mut dyn FnMut(Reference) -> bool, + sink: &mut dyn FnMut(FileId, FileReference) -> bool, ) -> bool { match NameRefClass::classify_lifetime(self.sema, lifetime) { Some(NameRefClass::Definition(def)) if &def == self.def => { - let reference = Reference { - file_range: self.sema.original_range(lifetime.syntax()), - kind: ReferenceKind::Lifetime, - access: None, - }; - sink(reference) + let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax()); + let reference = + FileReference { range, kind: ReferenceKind::Lifetime, access: None }; + sink(file_id, reference) } _ => false, // not a usage } @@ -338,7 +361,7 @@ impl<'a> FindUsages<'a> { fn found_name_ref( &self, name_ref: &ast::NameRef, - sink: &mut dyn FnMut(Reference) -> bool, + sink: &mut dyn FnMut(FileId, FileReference) -> bool, ) -> bool { match NameRefClass::classify(self.sema, &name_ref) { Some(NameRefClass::Definition(def)) if &def == self.def => { @@ -352,46 +375,50 @@ impl<'a> FindUsages<'a> { ReferenceKind::Other }; - let reference = Reference { - file_range: self.sema.original_range(name_ref.syntax()), - kind, - access: reference_access(&def, &name_ref), - }; - sink(reference) + let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); + let reference = + FileReference { range, kind, access: reference_access(&def, &name_ref) }; + sink(file_id, reference) } Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { + let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); let reference = match self.def { - Definition::Field(_) if &field == self.def => Reference { - file_range: self.sema.original_range(name_ref.syntax()), + Definition::Field(_) if &field == self.def => FileReference { + range, kind: ReferenceKind::FieldShorthandForField, access: reference_access(&field, &name_ref), }, - Definition::Local(l) if &local == l => Reference { - file_range: self.sema.original_range(name_ref.syntax()), + Definition::Local(l) if &local == l => FileReference { + range, kind: ReferenceKind::FieldShorthandForLocal, access: reference_access(&Definition::Local(local), &name_ref), }, _ => return false, // not a usage }; - sink(reference) + sink(file_id, reference) } _ => false, // not a usage } } - fn found_name(&self, name: &ast::Name, sink: &mut dyn FnMut(Reference) -> bool) -> bool { + fn found_name( + &self, + name: &ast::Name, + sink: &mut dyn FnMut(FileId, FileReference) -> bool, + ) -> bool { match NameClass::classify(self.sema, name) { Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => { - let reference = match self.def { - Definition::Field(_) if &field_ref == self.def => Reference { - file_range: self.sema.original_range(name.syntax()), - kind: ReferenceKind::FieldShorthandForField, - // FIXME: mutable patterns should have `Write` access - access: Some(ReferenceAccess::Read), - }, - _ => return false, // not a usage + if !matches!(self.def, Definition::Field(_) if &field_ref == self.def) { + return false; + } + let FileRange { file_id, range } = self.sema.original_range(name.syntax()); + let reference = FileReference { + range, + kind: ReferenceKind::FieldShorthandForField, + // FIXME: mutable patterns should have `Write` access + access: Some(ReferenceAccess::Read), }; - sink(reference) + sink(file_id, reference) } _ => false, // not a usage } diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 29cc9051e..d862f370a 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -12,6 +12,7 @@ use ide::{ FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex, NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, SymbolKind, TextEdit, }; +use ide_db::search::FileReferences; use itertools::Itertools; use lsp_server::ErrorCode; use lsp_types::{ @@ -812,14 +813,19 @@ pub(crate) fn handle_references( }; let locations = if params.context.include_declaration { - refs.into_iter() - .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) - .collect() + let mut locations = Vec::default(); + refs.into_iter().for_each(|it| { + locations.extend( + it.file_ranges().filter_map(|frange| to_proto::location(&snap, frange).ok()), + ) + }); + locations } else { // Only iterate over the references if include_declaration was false refs.references() .iter() - .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) + .flat_map(FileReferences::file_ranges) + .filter_map(|frange| to_proto::location(&snap, frange).ok()) .collect() }; @@ -1176,7 +1182,8 @@ pub(crate) fn handle_code_lens_resolve( .map(|r| { r.references() .iter() - .filter_map(|it| to_proto::location(&snap, it.file_range).ok()) + .flat_map(FileReferences::file_ranges) + .filter_map(|frange| to_proto::location(&snap, frange).ok()) .collect_vec() }) .unwrap_or_default(); @@ -1221,12 +1228,18 @@ pub(crate) fn handle_document_highlight( let res = refs .into_iter() - .filter(|reference| reference.file_range.file_id == position.file_id) - .map(|reference| DocumentHighlight { - range: to_proto::range(&line_index, reference.file_range.range), - kind: reference.access.map(to_proto::document_highlight_kind), + .find(|refs| refs.file_id == position.file_id) + .map(|file_refs| { + file_refs + .references + .into_iter() + .map(|r| DocumentHighlight { + range: to_proto::range(&line_index, r.range), + kind: r.access.map(to_proto::document_highlight_kind), + }) + .collect() }) - .collect(); + .unwrap_or_default(); Ok(Some(res)) } diff --git a/crates/ssr/src/search.rs b/crates/ssr/src/search.rs index 44b5db029..a1d653aff 100644 --- a/crates/ssr/src/search.rs +++ b/crates/ssr/src/search.rs @@ -5,10 +5,10 @@ use crate::{ resolving::{ResolvedPath, ResolvedPattern, ResolvedRule}, Match, MatchFinder, }; -use ide_db::base_db::{FileId, FileRange}; use ide_db::{ + base_db::{FileId, FileRange}, defs::Definition, - search::{Reference, SearchScope}, + search::{FileReferences, SearchScope}, }; use rustc_hash::FxHashSet; use syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; @@ -20,7 +20,7 @@ use test_utils::mark; /// them more than once. #[derive(Default)] pub(crate) struct UsageCache { - usages: Vec<(Definition, Vec)>, + usages: Vec<(Definition, Vec)>, } impl<'db> MatchFinder<'db> { @@ -58,8 +58,12 @@ impl<'db> MatchFinder<'db> { ) { if let Some(resolved_path) = pick_path_for_usages(pattern) { let definition: Definition = resolved_path.resolution.clone().into(); - for reference in self.find_usages(usage_cache, definition) { - if let Some(node_to_match) = self.find_node_to_match(resolved_path, reference) { + for file_range in self + .find_usages(usage_cache, definition) + .iter() + .flat_map(FileReferences::file_ranges) + { + if let Some(node_to_match) = self.find_node_to_match(resolved_path, file_range) { if !is_search_permitted_ancestors(&node_to_match) { mark::hit!(use_declaration_with_braces); continue; @@ -73,11 +77,11 @@ impl<'db> MatchFinder<'db> { fn find_node_to_match( &self, resolved_path: &ResolvedPath, - reference: &Reference, + file_range: FileRange, ) -> Option { - let file = self.sema.parse(reference.file_range.file_id); + let file = self.sema.parse(file_range.file_id); let depth = resolved_path.depth as usize; - let offset = reference.file_range.range.start(); + let offset = file_range.range.start(); if let Some(path) = self.sema.find_node_at_offset_with_descend::(file.syntax(), offset) { @@ -108,7 +112,7 @@ impl<'db> MatchFinder<'db> { &self, usage_cache: &'a mut UsageCache, definition: Definition, - ) -> &'a [Reference] { + ) -> &'a [FileReferences] { // Logically if a lookup succeeds we should just return it. Unfortunately returning it would // extend the lifetime of the borrow, then we wouldn't be able to do the insertion on a // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two @@ -250,7 +254,7 @@ fn is_search_permitted(node: &SyntaxNode) -> bool { } impl UsageCache { - fn find(&mut self, definition: &Definition) -> Option<&[Reference]> { + fn find(&mut self, definition: &Definition) -> Option<&[FileReferences]> { // We expect a very small number of cache entries (generally 1), so a linear scan should be // fast enough and avoids the need to implement Hash for Definition. for (d, refs) in &self.usages { -- cgit v1.2.3