7251: Group references by FileId r=matklad a=Veykril

Fixes #4901

This doesn't address https://github.com/rust-analyzer/rust-analyzer/pull/7032/files#diff-a7e1e771e911237bb893e1b0f5e0f2c2a856b54ca06f95ef0818a922f1a8b5ebR266

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2021-01-13 10:38:30 +00:00 committed by GitHub
commit e41b363027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 316 additions and 248 deletions

View File

@ -2,12 +2,16 @@ use std::iter;
use either::Either; use either::Either;
use hir::{AsName, Module, ModuleDef, Name, Variant}; use hir::{AsName, Module, ModuleDef, Name, Variant};
use ide_db::helpers::{ use ide_db::{
insert_use::{insert_use, ImportScope}, defs::Definition,
mod_path_to_ast, helpers::{
insert_use::{insert_use, ImportScope},
mod_path_to_ast,
},
search::FileReference,
RootDatabase,
}; };
use ide_db::{defs::Definition, search::Reference, RootDatabase}; use rustc_hash::FxHashSet;
use rustc_hash::{FxHashMap, FxHashSet};
use syntax::{ use syntax::{
algo::{find_node_at_offset, SyntaxRewriter}, algo::{find_node_at_offset, SyntaxRewriter},
ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner}, 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 mut visited_modules_set = FxHashSet::default();
let current_module = enum_hir.module(ctx.db()); let current_module = enum_hir.module(ctx.db());
visited_modules_set.insert(current_module); visited_modules_set.insert(current_module);
let mut rewriters = FxHashMap::default(); let mut def_rewriter = None;
for reference in usages { for (file_id, references) in usages {
let rewriter = rewriters let mut rewriter = SyntaxRewriter::default();
.entry(reference.file_range.file_id) let source_file = ctx.sema.parse(file_id);
.or_insert_with(SyntaxRewriter::default); for reference in references {
let source_file = ctx.sema.parse(reference.file_range.file_id); update_reference(
update_reference( ctx,
ctx, &mut rewriter,
rewriter, reference,
reference, &source_file,
&source_file, &enum_module_def,
&enum_module_def, &variant_hir_name,
&variant_hir_name, &mut visited_modules_set,
&mut visited_modules_set, );
); }
} if file_id == ctx.frange.file_id {
let mut rewriter = def_rewriter = Some(rewriter);
rewriters.remove(&ctx.frange.file_id).unwrap_or_else(SyntaxRewriter::default); continue;
for (file_id, rewriter) in rewriters { }
builder.edit_file(file_id); builder.edit_file(file_id);
builder.rewrite(rewriter); builder.rewrite(rewriter);
} }
builder.edit_file(ctx.frange.file_id); let mut rewriter = def_rewriter.unwrap_or_default();
update_variant(&mut rewriter, &variant); update_variant(&mut rewriter, &variant);
extract_struct_def( extract_struct_def(
&mut rewriter, &mut rewriter,
@ -90,6 +94,7 @@ pub(crate) fn extract_struct_from_enum_variant(
&variant.parent_enum().syntax().clone().into(), &variant.parent_enum().syntax().clone().into(),
enum_ast.visibility(), enum_ast.visibility(),
); );
builder.edit_file(ctx.frange.file_id);
builder.rewrite(rewriter); builder.rewrite(rewriter);
}, },
) )
@ -205,13 +210,13 @@ fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Opti
fn update_reference( fn update_reference(
ctx: &AssistContext, ctx: &AssistContext,
rewriter: &mut SyntaxRewriter, rewriter: &mut SyntaxRewriter,
reference: Reference, reference: FileReference,
source_file: &SourceFile, source_file: &SourceFile,
enum_module_def: &ModuleDef, enum_module_def: &ModuleDef,
variant_hir_name: &Name, variant_hir_name: &Name,
visited_modules_set: &mut FxHashSet<Module>, visited_modules_set: &mut FxHashSet<Module>,
) -> Option<()> { ) -> Option<()> {
let offset = reference.file_range.range.start(); let offset = reference.range.start();
let (segment, expr) = if let Some(path_expr) = let (segment, expr) = if let Some(path_expr) =
find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset) find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
{ {

View File

@ -1,4 +1,7 @@
use ide_db::{defs::Definition, search::ReferenceKind}; use ide_db::{
defs::Definition,
search::{FileReference, ReferenceKind},
};
use syntax::{ use syntax::{
ast::{self, AstNode, AstToken}, ast::{self, AstNode, AstToken},
TextRange, TextRange,
@ -44,8 +47,8 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
let def = ctx.sema.to_def(&bind_pat)?; let def = ctx.sema.to_def(&bind_pat)?;
let def = Definition::Local(def); let def = Definition::Local(def);
let refs = def.usages(&ctx.sema).all(); let usages = def.usages(&ctx.sema).all();
if refs.is_empty() { if usages.is_empty() {
mark::hit!(test_not_applicable_if_variable_unused); mark::hit!(test_not_applicable_if_variable_unused);
return None; return None;
}; };
@ -63,48 +66,45 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
let_stmt.syntax().text_range() let_stmt.syntax().text_range()
}; };
let mut wrap_in_parens = vec![true; refs.len()]; let wrap_in_parens = usages
.references
.values()
.flatten()
.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),
};
for (i, desc) in refs.iter().enumerate() { Ok(!matches!((&initializer_expr, usage_parent),
let usage_node = ctx (ast::Expr::CallExpr(_), _)
.covering_node_for_range(desc.file_range.range) | (ast::Expr::IndexExpr(_), _)
.ancestors() | (ast::Expr::MethodCallExpr(_), _)
.find_map(ast::PathExpr::cast)?; | (ast::Expr::FieldExpr(_), _)
let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); | (ast::Expr::TryExpr(_), _)
let usage_parent = match usage_parent_option { | (ast::Expr::RefExpr(_), _)
Some(u) => u, | (ast::Expr::Literal(_), _)
None => { | (ast::Expr::TupleExpr(_), _)
wrap_in_parens[i] = false; | (ast::Expr::ArrayExpr(_), _)
continue; | (ast::Expr::ParenExpr(_), _)
} | (ast::Expr::PathExpr(_), _)
}; | (ast::Expr::BlockExpr(_), _)
| (ast::Expr::EffectExpr(_), _)
wrap_in_parens[i] = match (&initializer_expr, usage_parent) { | (_, ast::Expr::CallExpr(_))
(ast::Expr::CallExpr(_), _) | (_, ast::Expr::TupleExpr(_))
| (ast::Expr::IndexExpr(_), _) | (_, ast::Expr::ArrayExpr(_))
| (ast::Expr::MethodCallExpr(_), _) | (_, ast::Expr::ParenExpr(_))
| (ast::Expr::FieldExpr(_), _) | (_, ast::Expr::ForExpr(_))
| (ast::Expr::TryExpr(_), _) | (_, ast::Expr::WhileExpr(_))
| (ast::Expr::RefExpr(_), _) | (_, ast::Expr::BreakExpr(_))
| (ast::Expr::Literal(_), _) | (_, ast::Expr::ReturnExpr(_))
| (ast::Expr::TupleExpr(_), _) | (_, ast::Expr::MatchExpr(_))
| (ast::Expr::ArrayExpr(_), _) ))
| (ast::Expr::ParenExpr(_), _) })
| (ast::Expr::PathExpr(_), _) .collect::<Result<Vec<_>, _>>()?;
| (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 init_str = initializer_expr.syntax().text().to_string(); let init_str = initializer_expr.syntax().text().to_string();
let init_in_paren = format!("({})", &init_str); let init_in_paren = format!("({})", &init_str);
@ -116,15 +116,16 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
target, target,
move |builder| { move |builder| {
builder.delete(delete_range); builder.delete(delete_range);
for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { for (reference, should_wrap) in usages.references.values().flatten().zip(wrap_in_parens)
{
let replacement = let replacement =
if should_wrap { init_in_paren.clone() } else { init_str.clone() }; if should_wrap { init_in_paren.clone() } else { init_str.clone() };
match desc.kind { match reference.kind {
ReferenceKind::FieldShorthandForLocal => { ReferenceKind::FieldShorthandForLocal => {
mark::hit!(inline_field_shorthand); 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),
} }
} }
}, },

View File

@ -1,8 +1,8 @@
use ide_db::{defs::Definition, search::Reference}; use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
use syntax::{ use syntax::{
algo::find_node_at_range, algo::find_node_at_range,
ast::{self, ArgListOwner}, ast::{self, ArgListOwner},
AstNode, SyntaxKind, SyntaxNode, TextRange, T, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
}; };
use test_utils::mark; use test_utils::mark;
use SyntaxKind::WHITESPACE; use SyntaxKind::WHITESPACE;
@ -58,32 +58,41 @@ pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Opt
param.syntax().text_range(), param.syntax().text_range(),
|builder| { |builder| {
builder.delete(range_to_remove(param.syntax())); builder.delete(range_to_remove(param.syntax()));
for usage in fn_def.usages(&ctx.sema).all() { for (file_id, references) in fn_def.usages(&ctx.sema).all() {
process_usage(ctx, builder, usage, param_position); process_usages(ctx, builder, file_id, references, param_position);
} }
}, },
) )
} }
fn process_usage( fn process_usages(
ctx: &AssistContext, ctx: &AssistContext,
builder: &mut AssistBuilder, builder: &mut AssistBuilder,
usage: Reference, file_id: FileId,
references: Vec<FileReference>,
arg_to_remove: usize, arg_to_remove: usize,
) -> Option<()> { ) {
let source_file = ctx.sema.parse(usage.file_range.file_id); let source_file = ctx.sema.parse(file_id);
let call_expr: ast::CallExpr = builder.edit_file(file_id);
find_node_at_range(source_file.syntax(), usage.file_range.range)?; for usage in 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<TextRange> {
let call_expr: ast::CallExpr = find_node_at_range(source_file.syntax(), range)?;
let call_expr_range = call_expr.expr()?.syntax().text_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; return None;
} }
let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?; let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
Some(range_to_remove(arg.syntax()))
builder.edit_file(usage.file_range.file_id);
builder.delete(range_to_remove(arg.syntax()));
Some(())
} }
fn range_to_remove(node: &SyntaxNode) -> TextRange { fn range_to_remove(node: &SyntaxNode) -> TextRange {

View File

@ -47,22 +47,23 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio
let mut calls = CallLocations::default(); let mut calls = CallLocations::default();
for reference in refs.info.references() { for (&file_id, references) in refs.info.references().iter() {
let file_id = reference.file_range.file_id;
let file = sema.parse(file_id); let file = sema.parse(file_id);
let file = file.syntax(); let file = file.syntax();
let token = file.token_at_offset(reference.file_range.range.start()).next()?; for reference in references {
let token = sema.descend_into_macros(token); let token = file.token_at_offset(reference.range.start()).next()?;
let syntax = token.parent(); let token = sema.descend_into_macros(token);
let syntax = token.parent();
// This target is the containing function // This target is the containing function
if let Some(nav) = syntax.ancestors().find_map(|node| { if let Some(nav) = syntax.ancestors().find_map(|node| {
let fn_ = ast::Fn::cast(node)?; let fn_ = ast::Fn::cast(node)?;
let def = sema.to_def(&fn_)?; let def = sema.to_def(&fn_)?;
def.try_to_nav(sema.db) def.try_to_nav(sema.db)
}) { }) {
let relative_range = reference.file_range.range; let relative_range = reference.range;
calls.add(&nav, relative_range); calls.add(&nav, relative_range);
}
} }
} }

View File

@ -92,7 +92,7 @@ pub use ide_db::base_db::{
}; };
pub use ide_db::{ pub use ide_db::{
call_info::CallInfo, call_info::CallInfo,
search::{Reference, ReferenceAccess, ReferenceKind}, search::{FileReference, ReferenceAccess, ReferenceKind},
}; };
pub use ide_db::{ pub use ide_db::{
label::Label, label::Label,

View File

@ -13,9 +13,9 @@ pub(crate) mod rename;
use hir::Semantics; use hir::Semantics;
use ide_db::{ use ide_db::{
base_db::FileId,
defs::{Definition, NameClass, NameRefClass}, defs::{Definition, NameClass, NameRefClass},
search::Reference, search::{FileReference, ReferenceAccess, ReferenceKind, SearchScope, UsageSearchResult},
search::{ReferenceAccess, ReferenceKind, SearchScope},
RootDatabase, RootDatabase,
}; };
use syntax::{ use syntax::{
@ -29,7 +29,7 @@ use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeI
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ReferenceSearchResult { pub struct ReferenceSearchResult {
declaration: Declaration, declaration: Declaration,
references: Vec<Reference>, references: UsageSearchResult,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -48,10 +48,21 @@ impl ReferenceSearchResult {
&self.declaration.nav &self.declaration.nav
} }
pub fn references(&self) -> &[Reference] { pub fn references(&self) -> &UsageSearchResult {
&self.references &self.references
} }
pub fn references_with_declaration(mut self) -> UsageSearchResult {
let decl_ref = FileReference {
range: self.declaration.nav.focus_or_full_range(),
kind: self.declaration.kind,
access: self.declaration.access,
};
let file_id = self.declaration.nav.file_id;
self.references.references.entry(file_id).or_default().push(decl_ref);
self.references
}
/// Total number of references /// Total number of references
/// At least 1 since all valid references should /// At least 1 since all valid references should
/// Have a declaration /// Have a declaration
@ -63,21 +74,11 @@ impl ReferenceSearchResult {
// allow turning ReferenceSearchResult into an iterator // allow turning ReferenceSearchResult into an iterator
// over References // over References
impl IntoIterator for ReferenceSearchResult { impl IntoIterator for ReferenceSearchResult {
type Item = Reference; type Item = (FileId, Vec<FileReference>);
type IntoIter = std::vec::IntoIter<Reference>; type IntoIter = std::collections::hash_map::IntoIter<FileId, Vec<FileReference>>;
fn into_iter(mut self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
let mut v = Vec::with_capacity(self.len()); self.references_with_declaration().into_iter()
v.push(Reference {
file_range: FileRange {
file_id: self.declaration.nav.file_id,
range: self.declaration.nav.focus_or_full_range(),
},
kind: self.declaration.kind,
access: self.declaration.access,
});
v.append(&mut self.references);
v.into_iter()
} }
} }
@ -109,13 +110,12 @@ pub(crate) fn find_all_refs(
let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?;
let references = def let mut usages = def.usages(sema).set_scope(search_scope).all();
.usages(sema) usages
.set_scope(search_scope) .references
.all() .values_mut()
.into_iter() .for_each(|it| it.retain(|r| search_kind == ReferenceKind::Other || search_kind == r.kind));
.filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) usages.references.retain(|_, it| !it.is_empty());
.collect();
let nav = def.try_to_nav(sema.db)?; let nav = def.try_to_nav(sema.db)?;
let decl_range = nav.focus_or_full_range(); let decl_range = nav.focus_or_full_range();
@ -139,7 +139,7 @@ pub(crate) fn find_all_refs(
let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) };
Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references: usages }))
} }
fn find_name( fn find_name(
@ -255,7 +255,8 @@ fn try_find_self_references(
syntax: &SyntaxNode, syntax: &SyntaxNode,
position: FilePosition, position: FilePosition,
) -> Option<RangeInfo<ReferenceSearchResult>> { ) -> Option<RangeInfo<ReferenceSearchResult>> {
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(); let parent = self_token.parent();
match_ast! { match_ast! {
match parent { match parent {
@ -276,7 +277,7 @@ fn try_find_self_references(
let declaration = Declaration { let declaration = Declaration {
nav: NavigationTarget { nav: NavigationTarget {
file_id: position.file_id, file_id,
full_range: self_param.syntax().text_range(), full_range: self_param.syntax().text_range(),
focus_range: Some(param_self_token.text_range()), focus_range: Some(param_self_token.text_range()),
name: param_self_token.text().clone(), name: param_self_token.text().clone(),
@ -292,7 +293,7 @@ fn try_find_self_references(
ReferenceAccess::Read ReferenceAccess::Read
}), }),
}; };
let references = function let refs = function
.body() .body()
.map(|body| { .map(|body| {
body.syntax() body.syntax()
@ -306,14 +307,16 @@ fn try_find_self_references(
None None
} }
}) })
.map(|token| Reference { .map(|token| FileReference {
file_range: FileRange { file_id: position.file_id, range: token.text_range() }, range: token.text_range(),
kind: ReferenceKind::SelfKw, kind: ReferenceKind::SelfKw,
access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration
}) })
.collect() .collect()
}) })
.unwrap_or_default(); .unwrap_or_default();
let mut references = UsageSearchResult::default();
references.references.insert(file_id, refs);
Some(RangeInfo::new( Some(RangeInfo::new(
param_self_token.text_range(), param_self_token.text_range(),
@ -1018,12 +1021,14 @@ impl Foo {
actual += "\n\n"; actual += "\n\n";
} }
for r in &refs.references { for (file_id, references) in refs.references {
format_to!(actual, "{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind); for r in references {
if let Some(access) = r.access { format_to!(actual, "{:?} {:?} {:?}", file_id, r.range, r.kind);
format_to!(actual, " {:?}", access); if let Some(access) = r.access {
format_to!(actual, " {:?}", access);
}
actual += "\n";
} }
actual += "\n";
} }
expect.assert_eq(&actual) expect.assert_eq(&actual)
} }

View File

@ -6,9 +6,10 @@ use std::{
}; };
use hir::{Module, ModuleDef, ModuleSource, Semantics}; use hir::{Module, ModuleDef, ModuleSource, Semantics};
use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt};
use ide_db::{ use ide_db::{
base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt},
defs::{Definition, NameClass, NameRefClass}, defs::{Definition, NameClass, NameRefClass},
search::FileReference,
RootDatabase, RootDatabase,
}; };
use syntax::{ use syntax::{
@ -20,8 +21,8 @@ use test_utils::mark;
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{ use crate::{
FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult, FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
SourceChange, SourceFileEdit, TextRange, TextSize, SourceFileEdit, TextRange, TextSize,
}; };
type RenameResult<T> = Result<T, RenameError>; type RenameResult<T> = Result<T, RenameError>;
@ -173,39 +174,46 @@ fn find_all_refs(
.ok_or_else(|| format_err!("No references found at position")) .ok_or_else(|| format_err!("No references found at position"))
} }
fn source_edit_from_reference( fn source_edit_from_references(
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
reference: Reference, file_id: FileId,
references: &[FileReference],
new_name: &str, new_name: &str,
) -> SourceFileEdit { ) -> SourceFileEdit {
let mut replacement_text = String::new(); let mut edit = TextEdit::builder();
let range = match reference.kind { for reference in references {
ReferenceKind::FieldShorthandForField => { let mut replacement_text = String::new();
mark::hit!(test_rename_struct_field_for_shorthand); let range = match reference.kind {
replacement_text.push_str(new_name); ReferenceKind::FieldShorthandForField => {
replacement_text.push_str(": "); mark::hit!(test_rename_struct_field_for_shorthand);
TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) replacement_text.push_str(new_name);
} replacement_text.push_str(": ");
ReferenceKind::FieldShorthandForLocal => { TextRange::new(reference.range.start(), reference.range.start())
mark::hit!(test_rename_local_for_field_shorthand); }
replacement_text.push_str(": "); ReferenceKind::FieldShorthandForLocal => {
replacement_text.push_str(new_name); mark::hit!(test_rename_local_for_field_shorthand);
TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) replacement_text.push_str(": ");
} replacement_text.push_str(new_name);
ReferenceKind::RecordFieldExprOrPat => { TextRange::new(reference.range.end(), reference.range.end())
mark::hit!(test_rename_field_expr_pat); }
replacement_text.push_str(new_name); ReferenceKind::RecordFieldExprOrPat => {
edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name) mark::hit!(test_rename_field_expr_pat);
} replacement_text.push_str(new_name);
_ => { edit_text_range_for_record_field_expr_or_pat(
replacement_text.push_str(new_name); sema,
reference.file_range.range FileRange { file_id, range: reference.range },
} new_name,
}; )
SourceFileEdit { }
file_id: reference.file_range.file_id, _ => {
edit: TextEdit::replace(range, replacement_text), 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( fn edit_text_range_for_record_field_expr_or_pat(
@ -276,10 +284,9 @@ fn rename_mod(
} }
let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
let ref_edits = refs let ref_edits = refs.references().iter().map(|(&file_id, references)| {
.references source_edit_from_references(sema, file_id, references, new_name)
.into_iter() });
.map(|reference| source_edit_from_reference(sema, reference, new_name));
source_file_edits.extend(ref_edits); source_file_edits.extend(ref_edits);
Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
@ -331,17 +338,12 @@ fn rename_to_self(
let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs let mut edits = refs
.into_iter() .references()
.partition(|reference| param_range.intersect(reference.file_range.range).is_some()); .iter()
.map(|(&file_id, references)| {
if param_ref.is_empty() { source_edit_from_references(sema, file_id, references, "self")
bail!("Parameter to rename not found"); })
}
let mut edits = usages
.into_iter()
.map(|reference| source_edit_from_reference(sema, reference, "self"))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
edits.push(SourceFileEdit { edits.push(SourceFileEdit {
@ -467,7 +469,9 @@ fn rename_reference(
let edit = refs let edit = refs
.into_iter() .into_iter()
.map(|reference| source_edit_from_reference(sema, reference, new_name)) .map(|(file_id, references)| {
source_edit_from_references(sema, file_id, &references, new_name)
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(RangeInfo::new(range, SourceChange::from(edit))) Ok(RangeInfo::new(range, SourceChange::from(edit)))

View File

@ -18,9 +18,43 @@ use crate::{
RootDatabase, RootDatabase,
}; };
#[derive(Debug, Default, Clone)]
pub struct UsageSearchResult {
pub references: FxHashMap<FileId, Vec<FileReference>>,
}
impl UsageSearchResult {
pub fn is_empty(&self) -> bool {
self.references.is_empty()
}
pub fn len(&self) -> usize {
self.references.len()
}
pub fn iter(&self) -> impl Iterator<Item = (&FileId, &Vec<FileReference>)> + '_ {
self.references.iter()
}
pub fn file_ranges(&self) -> impl Iterator<Item = FileRange> + '_ {
self.references.iter().flat_map(|(&file_id, refs)| {
refs.iter().map(move |&FileReference { range, .. }| FileRange { file_id, range })
})
}
}
impl IntoIterator for UsageSearchResult {
type Item = (FileId, Vec<FileReference>);
type IntoIter = <FxHashMap<FileId, Vec<FileReference>> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.references.into_iter()
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Reference { pub struct FileReference {
pub file_range: FileRange, pub range: TextRange,
pub kind: ReferenceKind, pub kind: ReferenceKind,
pub access: Option<ReferenceAccess>, pub access: Option<ReferenceAccess>,
} }
@ -252,23 +286,23 @@ impl<'a> FindUsages<'a> {
pub fn at_least_one(self) -> bool { pub fn at_least_one(self) -> bool {
let mut found = false; let mut found = false;
self.search(&mut |_reference| { self.search(&mut |_, _| {
found = true; found = true;
true true
}); });
found found
} }
pub fn all(self) -> Vec<Reference> { pub fn all(self) -> UsageSearchResult {
let mut res = Vec::new(); let mut res = UsageSearchResult::default();
self.search(&mut |reference| { self.search(&mut |file_id, reference| {
res.push(reference); res.references.entry(file_id).or_default().push(reference);
false false
}); });
res 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 _p = profile::span("FindUsages:search");
let sema = self.sema; let sema = self.sema;
@ -320,16 +354,14 @@ impl<'a> FindUsages<'a> {
fn found_lifetime( fn found_lifetime(
&self, &self,
lifetime: &ast::Lifetime, lifetime: &ast::Lifetime,
sink: &mut dyn FnMut(Reference) -> bool, sink: &mut dyn FnMut(FileId, FileReference) -> bool,
) -> bool { ) -> bool {
match NameRefClass::classify_lifetime(self.sema, lifetime) { match NameRefClass::classify_lifetime(self.sema, lifetime) {
Some(NameRefClass::Definition(def)) if &def == self.def => { Some(NameRefClass::Definition(def)) if &def == self.def => {
let reference = Reference { let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
file_range: self.sema.original_range(lifetime.syntax()), let reference =
kind: ReferenceKind::Lifetime, FileReference { range, kind: ReferenceKind::Lifetime, access: None };
access: None, sink(file_id, reference)
};
sink(reference)
} }
_ => false, // not a usage _ => false, // not a usage
} }
@ -338,7 +370,7 @@ impl<'a> FindUsages<'a> {
fn found_name_ref( fn found_name_ref(
&self, &self,
name_ref: &ast::NameRef, name_ref: &ast::NameRef,
sink: &mut dyn FnMut(Reference) -> bool, sink: &mut dyn FnMut(FileId, FileReference) -> bool,
) -> bool { ) -> bool {
match NameRefClass::classify(self.sema, &name_ref) { match NameRefClass::classify(self.sema, &name_ref) {
Some(NameRefClass::Definition(def)) if &def == self.def => { Some(NameRefClass::Definition(def)) if &def == self.def => {
@ -352,46 +384,50 @@ impl<'a> FindUsages<'a> {
ReferenceKind::Other ReferenceKind::Other
}; };
let reference = Reference { let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
file_range: self.sema.original_range(name_ref.syntax()), let reference =
kind, FileReference { range, kind, access: reference_access(&def, &name_ref) };
access: reference_access(&def, &name_ref), sink(file_id, reference)
};
sink(reference)
} }
Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { 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 { let reference = match self.def {
Definition::Field(_) if &field == self.def => Reference { Definition::Field(_) if &field == self.def => FileReference {
file_range: self.sema.original_range(name_ref.syntax()), range,
kind: ReferenceKind::FieldShorthandForField, kind: ReferenceKind::FieldShorthandForField,
access: reference_access(&field, &name_ref), access: reference_access(&field, &name_ref),
}, },
Definition::Local(l) if &local == l => Reference { Definition::Local(l) if &local == l => FileReference {
file_range: self.sema.original_range(name_ref.syntax()), range,
kind: ReferenceKind::FieldShorthandForLocal, kind: ReferenceKind::FieldShorthandForLocal,
access: reference_access(&Definition::Local(local), &name_ref), access: reference_access(&Definition::Local(local), &name_ref),
}, },
_ => return false, // not a usage _ => return false, // not a usage
}; };
sink(reference) sink(file_id, reference)
} }
_ => false, // not a usage _ => 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) { match NameClass::classify(self.sema, name) {
Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => { Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => {
let reference = match self.def { if !matches!(self.def, Definition::Field(_) if &field_ref == self.def) {
Definition::Field(_) if &field_ref == self.def => Reference { return false;
file_range: self.sema.original_range(name.syntax()), }
kind: ReferenceKind::FieldShorthandForField, let FileRange { file_id, range } = self.sema.original_range(name.syntax());
// FIXME: mutable patterns should have `Write` access let reference = FileReference {
access: Some(ReferenceAccess::Read), range,
}, kind: ReferenceKind::FieldShorthandForField,
_ => return false, // not a usage // FIXME: mutable patterns should have `Write` access
access: Some(ReferenceAccess::Read),
}; };
sink(reference) sink(file_id, reference)
} }
_ => false, // not a usage _ => false, // not a usage
} }

View File

@ -812,14 +812,15 @@ pub(crate) fn handle_references(
}; };
let locations = if params.context.include_declaration { let locations = if params.context.include_declaration {
refs.into_iter() refs.references_with_declaration()
.filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) .file_ranges()
.filter_map(|frange| to_proto::location(&snap, frange).ok())
.collect() .collect()
} else { } else {
// Only iterate over the references if include_declaration was false // Only iterate over the references if include_declaration was false
refs.references() refs.references()
.iter() .file_ranges()
.filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) .filter_map(|frange| to_proto::location(&snap, frange).ok())
.collect() .collect()
}; };
@ -1175,8 +1176,8 @@ pub(crate) fn handle_code_lens_resolve(
.unwrap_or(None) .unwrap_or(None)
.map(|r| { .map(|r| {
r.references() r.references()
.iter() .file_ranges()
.filter_map(|it| to_proto::location(&snap, it.file_range).ok()) .filter_map(|frange| to_proto::location(&snap, frange).ok())
.collect_vec() .collect_vec()
}) })
.unwrap_or_default(); .unwrap_or_default();
@ -1220,13 +1221,19 @@ pub(crate) fn handle_document_highlight(
}; };
let res = refs let res = refs
.into_iter() .references_with_declaration()
.filter(|reference| reference.file_range.file_id == position.file_id) .references
.map(|reference| DocumentHighlight { .get(&position.file_id)
range: to_proto::range(&line_index, reference.file_range.range), .map(|file_refs| {
kind: reference.access.map(to_proto::document_highlight_kind), file_refs
.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)) Ok(Some(res))
} }

View File

@ -5,10 +5,10 @@ use crate::{
resolving::{ResolvedPath, ResolvedPattern, ResolvedRule}, resolving::{ResolvedPath, ResolvedPattern, ResolvedRule},
Match, MatchFinder, Match, MatchFinder,
}; };
use ide_db::base_db::{FileId, FileRange};
use ide_db::{ use ide_db::{
base_db::{FileId, FileRange},
defs::Definition, defs::Definition,
search::{Reference, SearchScope}, search::{SearchScope, UsageSearchResult},
}; };
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; use syntax::{ast, AstNode, SyntaxKind, SyntaxNode};
@ -20,7 +20,7 @@ use test_utils::mark;
/// them more than once. /// them more than once.
#[derive(Default)] #[derive(Default)]
pub(crate) struct UsageCache { pub(crate) struct UsageCache {
usages: Vec<(Definition, Vec<Reference>)>, usages: Vec<(Definition, UsageSearchResult)>,
} }
impl<'db> MatchFinder<'db> { impl<'db> MatchFinder<'db> {
@ -58,8 +58,8 @@ impl<'db> MatchFinder<'db> {
) { ) {
if let Some(resolved_path) = pick_path_for_usages(pattern) { if let Some(resolved_path) = pick_path_for_usages(pattern) {
let definition: Definition = resolved_path.resolution.clone().into(); let definition: Definition = resolved_path.resolution.clone().into();
for reference in self.find_usages(usage_cache, definition) { for file_range in self.find_usages(usage_cache, definition).file_ranges() {
if let Some(node_to_match) = self.find_node_to_match(resolved_path, reference) { if let Some(node_to_match) = self.find_node_to_match(resolved_path, file_range) {
if !is_search_permitted_ancestors(&node_to_match) { if !is_search_permitted_ancestors(&node_to_match) {
mark::hit!(use_declaration_with_braces); mark::hit!(use_declaration_with_braces);
continue; continue;
@ -73,11 +73,11 @@ impl<'db> MatchFinder<'db> {
fn find_node_to_match( fn find_node_to_match(
&self, &self,
resolved_path: &ResolvedPath, resolved_path: &ResolvedPath,
reference: &Reference, file_range: FileRange,
) -> Option<SyntaxNode> { ) -> Option<SyntaxNode> {
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 depth = resolved_path.depth as usize;
let offset = reference.file_range.range.start(); let offset = file_range.range.start();
if let Some(path) = if let Some(path) =
self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset) self.sema.find_node_at_offset_with_descend::<ast::Path>(file.syntax(), offset)
{ {
@ -108,7 +108,7 @@ impl<'db> MatchFinder<'db> {
&self, &self,
usage_cache: &'a mut UsageCache, usage_cache: &'a mut UsageCache,
definition: Definition, definition: Definition,
) -> &'a [Reference] { ) -> &'a UsageSearchResult {
// Logically if a lookup succeeds we should just return it. Unfortunately returning it would // 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 // 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 // cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two
@ -250,7 +250,7 @@ fn is_search_permitted(node: &SyntaxNode) -> bool {
} }
impl UsageCache { impl UsageCache {
fn find(&mut self, definition: &Definition) -> Option<&[Reference]> { fn find(&mut self, definition: &Definition) -> Option<&UsageSearchResult> {
// We expect a very small number of cache entries (generally 1), so a linear scan should be // 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. // fast enough and avoids the need to implement Hash for Definition.
for (d, refs) in &self.usages { for (d, refs) in &self.usages {