aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-01-18 19:00:04 +0000
committerGitHub <[email protected]>2021-01-18 19:00:04 +0000
commit39282ec419046d84ad5972d5b02b1af9885e1282 (patch)
tree124f7d3ee6a18bf22b9ba5d1fc78b34c5f1b8b2b /crates
parent08efb8a94360735f505699021eaa901464c76f4a (diff)
parent6015a66cae81456d0de14c9bcff73c990c27dcb1 (diff)
Merge #7332
7332: Rename reference search uses ide_db::search directly r=matklad a=Veykril https://github.com/rust-analyzer/rust-analyzer/pull/7251#discussion_r556644241 Co-authored-by: Lukas Wirth <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ide/src/diagnostics/fixes.rs2
-rw-r--r--crates/ide/src/references/rename.rs372
2 files changed, 210 insertions, 164 deletions
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index e4335119b..4b4afd40c 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -140,7 +140,7 @@ impl DiagnosticWithFix for IncorrectCase {
140 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; 140 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
141 141
142 let label = format!("Rename to {}", self.suggested_text); 142 let label = format!("Rename to {}", self.suggested_text);
143 Some(Fix::new(&label, rename_changes.info, rename_changes.range)) 143 Some(Fix::new(&label, rename_changes.info, frange.range))
144 } 144 }
145} 145}
146 146
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 4df189c98..c5010df0a 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1,23 +1,24 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2use std::fmt::{self, Display}; 2use std::fmt::{self, Display};
3 3
4use hir::{Module, ModuleDef, ModuleSource, Semantics}; 4use either::Either;
5use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics};
5use ide_db::{ 6use ide_db::{
6 base_db::{AnchoredPathBuf, FileId, FileRange}, 7 base_db::{AnchoredPathBuf, FileId, FileRange},
7 defs::{Definition, NameClass, NameRefClass}, 8 defs::{Definition, NameClass, NameRefClass},
8 search::FileReference, 9 search::FileReference,
9 RootDatabase, 10 RootDatabase,
10}; 11};
12use stdx::assert_never;
11use syntax::{ 13use syntax::{
12 algo::find_node_at_offset,
13 ast::{self, NameOwner}, 14 ast::{self, NameOwner},
14 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, T, 15 lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T,
15}; 16};
16use test_utils::mark; 17use test_utils::mark;
17use text_edit::TextEdit; 18use text_edit::TextEdit;
18 19
19use crate::{ 20use crate::{
20 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, 21 display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, SourceChange,
21 TextRange, 22 TextRange,
22}; 23};
23 24
@@ -47,13 +48,15 @@ pub(crate) fn prepare_rename(
47 let sema = Semantics::new(db); 48 let sema = Semantics::new(db);
48 let source_file = sema.parse(position.file_id); 49 let source_file = sema.parse(position.file_id);
49 let syntax = source_file.syntax(); 50 let syntax = source_file.syntax();
50 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 51 let range = match &find_name_like(&sema, &syntax, position)
51 rename_mod(&sema, position, module, "dummy") 52 .ok_or_else(|| format_err!("No references found at position"))?
52 } else { 53 {
53 let RangeInfo { range, .. } = find_all_refs(&sema, position)?; 54 NameLike::Name(it) => it.syntax(),
54 Ok(RangeInfo::new(range, SourceChange::default())) 55 NameLike::NameRef(it) => it.syntax(),
56 NameLike::Lifetime(it) => it.syntax(),
55 } 57 }
56 .map(|info| RangeInfo::new(info.range, ())) 58 .text_range();
59 Ok(RangeInfo::new(range, ()))
57} 60}
58 61
59pub(crate) fn rename( 62pub(crate) fn rename(
@@ -73,10 +76,11 @@ pub(crate) fn rename_with_semantics(
73 let source_file = sema.parse(position.file_id); 76 let source_file = sema.parse(position.file_id);
74 let syntax = source_file.syntax(); 77 let syntax = source_file.syntax();
75 78
76 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 79 let def = find_definition(sema, syntax, position)
77 rename_mod(&sema, position, module, new_name) 80 .ok_or_else(|| format_err!("No references found at position"))?;
78 } else { 81 match def {
79 rename_reference(&sema, position, new_name) 82 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
83 def => rename_reference(sema, def, new_name),
80 } 84 }
81} 85}
82 86
@@ -87,12 +91,7 @@ pub(crate) fn will_rename_file(
87) -> Option<SourceChange> { 91) -> Option<SourceChange> {
88 let sema = Semantics::new(db); 92 let sema = Semantics::new(db);
89 let module = sema.to_module_def(file_id)?; 93 let module = sema.to_module_def(file_id)?;
90 94 let mut change = rename_mod(&sema, module, new_name_stem).ok()?.info;
91 let decl = module.declaration_source(db)?;
92 let range = decl.value.name()?.syntax().text_range();
93
94 let position = FilePosition { file_id: decl.file_id.original_file(db), offset: range.start() };
95 let mut change = rename_mod(&sema, position, module, new_name_stem).ok()?.info;
96 change.file_system_edits.clear(); 95 change.file_system_edits.clear();
97 Some(change) 96 Some(change)
98} 97}
@@ -124,40 +123,51 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
124 } 123 }
125} 124}
126 125
127fn find_module_at_offset( 126enum NameLike {
127 Name(ast::Name),
128 NameRef(ast::NameRef),
129 Lifetime(ast::Lifetime),
130}
131
132fn find_name_like(
128 sema: &Semantics<RootDatabase>, 133 sema: &Semantics<RootDatabase>,
129 position: FilePosition,
130 syntax: &SyntaxNode, 134 syntax: &SyntaxNode,
131) -> Option<Module> { 135 position: FilePosition,
132 let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?; 136) -> Option<NameLike> {
133 137 let namelike = if let Some(name_ref) =
134 let module = match_ast! { 138 sema.find_node_at_offset_with_descend::<ast::NameRef>(syntax, position.offset)
135 match (ident.parent()) { 139 {
136 ast::NameRef(name_ref) => { 140 NameLike::NameRef(name_ref)
137 match NameRefClass::classify(sema, &name_ref)? { 141 } else if let Some(name) =
138 NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, 142 sema.find_node_at_offset_with_descend::<ast::Name>(syntax, position.offset)
139 _ => return None, 143 {
140 } 144 NameLike::Name(name)
141 }, 145 } else if let Some(lifetime) =
142 ast::Name(name) => { 146 sema.find_node_at_offset_with_descend::<ast::Lifetime>(syntax, position.offset)
143 match NameClass::classify(&sema, &name)? { 147 {
144 NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, 148 NameLike::Lifetime(lifetime)
145 _ => return None, 149 } else {
146 } 150 return None;
147 },
148 _ => return None,
149 }
150 }; 151 };
151 152 Some(namelike)
152 Some(module)
153} 153}
154 154
155fn find_all_refs( 155fn find_definition(
156 sema: &Semantics<RootDatabase>, 156 sema: &Semantics<RootDatabase>,
157 syntax: &SyntaxNode,
157 position: FilePosition, 158 position: FilePosition,
158) -> RenameResult<RangeInfo<ReferenceSearchResult>> { 159) -> Option<Definition> {
159 crate::references::find_all_refs(sema, position, None) 160 let def = match find_name_like(sema, syntax, position)? {
160 .ok_or_else(|| format_err!("No references found at position")) 161 NameLike::Name(name) => NameClass::classify(sema, &name)?.referenced_or_defined(sema.db),
162 NameLike::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref)?.referenced(sema.db),
163 NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
164 .map(|class| NameRefClass::referenced(class, sema.db))
165 .or_else(|| {
166 NameClass::classify_lifetime(sema, &lifetime)
167 .map(|it| it.referenced_or_defined(sema.db))
168 })?,
169 };
170 Some(def)
161} 171}
162 172
163fn source_edit_from_references( 173fn source_edit_from_references(
@@ -231,7 +241,6 @@ fn edit_text_range_for_record_field_expr_or_pat(
231 241
232fn rename_mod( 242fn rename_mod(
233 sema: &Semantics<RootDatabase>, 243 sema: &Semantics<RootDatabase>,
234 position: FilePosition,
235 module: Module, 244 module: Module,
236 new_name: &str, 245 new_name: &str,
237) -> RenameResult<RangeInfo<SourceChange>> { 246) -> RenameResult<RangeInfo<SourceChange>> {
@@ -241,62 +250,78 @@ fn rename_mod(
241 250
242 let mut source_change = SourceChange::default(); 251 let mut source_change = SourceChange::default();
243 252
244 let src = module.definition_source(sema.db); 253 let InFile { file_id, value: def_source } = module.definition_source(sema.db);
245 let file_id = src.file_id.original_file(sema.db); 254 let file_id = file_id.original_file(sema.db);
246 match src.value { 255 if let ModuleSource::SourceFile(..) = def_source {
247 ModuleSource::SourceFile(..) => { 256 // mod is defined in path/to/dir/mod.rs
248 // mod is defined in path/to/dir/mod.rs 257 let path = if module.is_mod_rs(sema.db) {
249 let path = if module.is_mod_rs(sema.db) { 258 format!("../{}/mod.rs", new_name)
250 format!("../{}/mod.rs", new_name) 259 } else {
251 } else { 260 format!("{}.rs", new_name)
252 format!("{}.rs", new_name) 261 };
253 }; 262 let dst = AnchoredPathBuf { anchor: file_id, path };
254 let dst = AnchoredPathBuf { anchor: file_id, path }; 263 let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
255 let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; 264 source_change.push_file_system_edit(move_file);
256 source_change.push_file_system_edit(move_file); 265 }
257 } 266
258 ModuleSource::Module(..) => {} 267 if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) {
259 } 268 let file_id = file_id.original_file(sema.db);
260 269 match decl_source.name() {
261 if let Some(src) = module.declaration_source(sema.db) { 270 Some(name) => source_change.insert_source_edit(
262 let file_id = src.file_id.original_file(sema.db); 271 file_id,
263 let name = src.value.name().unwrap(); 272 TextEdit::replace(name.syntax().text_range(), new_name.to_string()),
264 source_change.insert_source_edit( 273 ),
265 file_id, 274 _ => unreachable!(),
266 TextEdit::replace(name.syntax().text_range(), new_name.into()), 275 };
267 );
268 } 276 }
269 277 let def = Definition::ModuleDef(ModuleDef::Module(module));
270 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 278 let usages = def.usages(sema).all();
271 let ref_edits = refs.references().iter().map(|(&file_id, references)| { 279 let ref_edits = usages.iter().map(|(&file_id, references)| {
272 source_edit_from_references(sema, file_id, references, new_name) 280 source_edit_from_references(sema, file_id, references, new_name)
273 }); 281 });
274 source_change.extend(ref_edits); 282 source_change.extend(ref_edits);
275 283
276 Ok(RangeInfo::new(range, source_change)) 284 Ok(RangeInfo::new(TextRange::default(), source_change))
277} 285}
278 286
279fn rename_to_self( 287fn rename_to_self(
280 sema: &Semantics<RootDatabase>, 288 sema: &Semantics<RootDatabase>,
281 position: FilePosition, 289 local: hir::Local,
282) -> Result<RangeInfo<SourceChange>, RenameError> { 290) -> RenameResult<RangeInfo<SourceChange>> {
283 let source_file = sema.parse(position.file_id); 291 if assert_never!(local.is_self(sema.db)) {
284 let syn = source_file.syntax(); 292 bail!("rename_to_self invoked on self");
293 }
294
295 let fn_def = match local.parent(sema.db) {
296 hir::DefWithBody::Function(func) => func,
297 _ => bail!("Cannot rename non-param local to self"),
298 };
299
300 // FIXME: reimplement this on the hir instead
301 // as of the time of this writing params in hir don't keep their names
302 let fn_ast =
303 fn_def.source(sema.db).ok_or(format_err!("Cannot rename non-param local to self"))?.value;
285 304
286 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) 305 let first_param_range = fn_ast
287 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
288 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
289 let param_range = fn_ast
290 .param_list() 306 .param_list()
291 .and_then(|p| p.params().next()) 307 .and_then(|p| p.params().next())
292 .ok_or_else(|| format_err!("Method has no parameters"))? 308 .ok_or_else(|| format_err!("Method has no parameters"))?
293 .syntax() 309 .syntax()
294 .text_range(); 310 .text_range();
295 if !param_range.contains(position.offset) { 311 let InFile { file_id, value: local_source } = local.source(sema.db);
296 bail!("Only the first parameter can be self"); 312 match local_source {
313 either::Either::Left(pat)
314 if !first_param_range.contains_range(pat.syntax().text_range()) =>
315 {
316 bail!("Only the first parameter can be self");
317 }
318 _ => (),
297 } 319 }
298 320
299 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) 321 let impl_block = fn_ast
322 .syntax()
323 .ancestors()
324 .find_map(|node| ast::Impl::cast(node))
300 .and_then(|def| sema.to_def(&def)) 325 .and_then(|def| sema.to_def(&def))
301 .ok_or_else(|| format_err!("No impl block found for function"))?; 326 .ok_or_else(|| format_err!("No impl block found for function"))?;
302 if fn_def.self_param(sema.db).is_some() { 327 if fn_def.self_param(sema.db).is_some() {
@@ -320,25 +345,21 @@ fn rename_to_self(
320 bail!("Parameter type differs from impl block type"); 345 bail!("Parameter type differs from impl block type");
321 } 346 }
322 347
323 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 348 let def = Definition::Local(local);
324 349 let usages = def.usages(sema).all();
325 let mut source_change = SourceChange::default(); 350 let mut source_change = SourceChange::default();
326 source_change.extend(refs.references().iter().map(|(&file_id, references)| { 351 source_change.extend(usages.iter().map(|(&file_id, references)| {
327 source_edit_from_references(sema, file_id, references, "self") 352 source_edit_from_references(sema, file_id, references, "self")
328 })); 353 }));
329 source_change.insert_source_edit( 354 source_change.insert_source_edit(
330 position.file_id, 355 file_id.original_file(sema.db),
331 TextEdit::replace(param_range, String::from(self_param)), 356 TextEdit::replace(first_param_range, String::from(self_param)),
332 ); 357 );
333 358
334 Ok(RangeInfo::new(range, source_change)) 359 Ok(RangeInfo::new(TextRange::default(), source_change))
335} 360}
336 361
337fn text_edit_from_self_param( 362fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
338 syn: &SyntaxNode,
339 self_param: &ast::SelfParam,
340 new_name: &str,
341) -> Option<TextEdit> {
342 fn target_type_name(impl_def: &ast::Impl) -> Option<String> { 363 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
343 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { 364 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
344 return Some(p.path()?.segment()?.name_ref()?.text().to_string()); 365 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
@@ -346,7 +367,7 @@ fn text_edit_from_self_param(
346 None 367 None
347 } 368 }
348 369
349 let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; 370 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
350 let type_name = target_type_name(&impl_def)?; 371 let type_name = target_type_name(&impl_def)?;
351 372
352 let mut replacement_text = String::from(new_name); 373 let mut replacement_text = String::from(new_name);
@@ -363,94 +384,119 @@ fn text_edit_from_self_param(
363 384
364fn rename_self_to_param( 385fn rename_self_to_param(
365 sema: &Semantics<RootDatabase>, 386 sema: &Semantics<RootDatabase>,
366 position: FilePosition, 387 local: hir::Local,
367 new_name: &str, 388 new_name: &str,
368 ident_kind: IdentifierKind, 389 identifier_kind: IdentifierKind,
369 range: TextRange, 390) -> RenameResult<RangeInfo<SourceChange>> {
370 refs: ReferenceSearchResult, 391 let (file_id, self_param) = match local.source(sema.db) {
371) -> Result<RangeInfo<SourceChange>, RenameError> { 392 InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param),
372 match ident_kind { 393 _ => {
373 IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), 394 assert_never!(true, "rename_self_to_param invoked on a non-self local");
374 IdentifierKind::ToSelf => { 395 bail!("rename_self_to_param invoked on a non-self local");
375 // no-op
376 mark::hit!(rename_self_to_self);
377 return Ok(RangeInfo::new(range, SourceChange::default()));
378 } 396 }
379 _ => (), 397 };
380 }
381 let source_file = sema.parse(position.file_id);
382 let syn = source_file.syntax();
383
384 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
385 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
386
387 let mut source_change = SourceChange::default();
388 if let Some(self_param) = fn_def.param_list().and_then(|it| it.self_param()) {
389 if self_param
390 .syntax()
391 .text_range()
392 .contains_range(refs.declaration().nav.focus_or_full_range())
393 {
394 let edit = text_edit_from_self_param(syn, &self_param, new_name)
395 .ok_or_else(|| format_err!("No target type found"))?;
396 source_change.insert_source_edit(position.file_id, edit);
397
398 source_change.extend(refs.references().iter().map(|(&file_id, references)| {
399 source_edit_from_references(sema, file_id, &references, new_name)
400 }));
401
402 if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore
403 {
404 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
405 }
406 398
407 return Ok(RangeInfo::new(range, source_change)); 399 let def = Definition::Local(local);
408 } 400 let usages = def.usages(sema).all();
401 let edit = text_edit_from_self_param(&self_param, new_name)
402 .ok_or_else(|| format_err!("No target type found"))?;
403 if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {
404 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
409 } 405 }
410 Err(format_err!("Method has no self param")) 406 let mut source_change = SourceChange::default();
407 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
408 source_change.extend(usages.iter().map(|(&file_id, references)| {
409 source_edit_from_references(sema, file_id, &references, new_name)
410 }));
411 Ok(RangeInfo::new(TextRange::default(), source_change))
411} 412}
412 413
413fn rename_reference( 414fn rename_reference(
414 sema: &Semantics<RootDatabase>, 415 sema: &Semantics<RootDatabase>,
415 position: FilePosition, 416 def: Definition,
416 new_name: &str, 417 new_name: &str,
417) -> Result<RangeInfo<SourceChange>, RenameError> { 418) -> RenameResult<RangeInfo<SourceChange>> {
418 let ident_kind = check_identifier(new_name)?; 419 let ident_kind = check_identifier(new_name)?;
419 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
420 420
421 match (ident_kind, &refs.declaration.kind) { 421 let def_is_lbl_or_lt = matches!(def,
422 (IdentifierKind::ToSelf, ReferenceKind::Lifetime) 422 Definition::GenericParam(hir::GenericParam::LifetimeParam(_))
423 | (IdentifierKind::Underscore, ReferenceKind::Lifetime) 423 | Definition::Label(_)
424 | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { 424 );
425 match (ident_kind, def) {
426 (IdentifierKind::ToSelf, _)
427 | (IdentifierKind::Underscore, _)
428 | (IdentifierKind::Ident, _)
429 if def_is_lbl_or_lt =>
430 {
425 mark::hit!(rename_not_a_lifetime_ident_ref); 431 mark::hit!(rename_not_a_lifetime_ident_ref);
426 bail!("Invalid name `{}`: not a lifetime identifier", new_name) 432 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
427 } 433 }
428 (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), 434 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
429 (IdentifierKind::Lifetime, _) => { 435 (IdentifierKind::Lifetime, _) => {
430 mark::hit!(rename_not_an_ident_ref); 436 mark::hit!(rename_not_an_ident_ref);
431 bail!("Invalid name `{}`: not an identifier", new_name) 437 bail!("Invalid name `{}`: not an identifier", new_name)
432 } 438 }
433 (_, ReferenceKind::SelfParam) => { 439 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
440 // no-op
441 mark::hit!(rename_self_to_self);
442 return Ok(RangeInfo::new(TextRange::default(), SourceChange::default()));
443 }
444 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
434 mark::hit!(rename_self_to_param); 445 mark::hit!(rename_self_to_param);
435 return rename_self_to_param(sema, position, new_name, ident_kind, range, refs); 446 return rename_self_to_param(sema, local, new_name, ident_kind);
436 } 447 }
437 (IdentifierKind::ToSelf, _) => { 448 (IdentifierKind::ToSelf, Definition::Local(local)) => {
438 mark::hit!(rename_to_self); 449 mark::hit!(rename_to_self);
439 return rename_to_self(sema, position); 450 return rename_to_self(sema, local);
440 }
441 (IdentifierKind::Underscore, _) if !refs.references.is_empty() => {
442 mark::hit!(rename_underscore_multiple);
443 bail!("Cannot rename reference to `_` as it is being referenced multiple times")
444 } 451 }
452 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name),
445 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 453 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
446 } 454 }
447 455
456 let usages = def.usages(sema).all();
457 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
458 mark::hit!(rename_underscore_multiple);
459 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
460 }
448 let mut source_change = SourceChange::default(); 461 let mut source_change = SourceChange::default();
449 source_change.extend(refs.into_iter().map(|(file_id, references)| { 462 source_change.extend(usages.iter().map(|(&file_id, references)| {
450 source_edit_from_references(sema, file_id, &references, new_name) 463 source_edit_from_references(sema, file_id, &references, new_name)
451 })); 464 }));
452 465
453 Ok(RangeInfo::new(range, source_change)) 466 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
467 source_change.insert_source_edit(file_id, edit);
468 Ok(RangeInfo::new(TextRange::default(), source_change))
469}
470
471fn source_edit_from_def(
472 sema: &Semantics<RootDatabase>,
473 def: Definition,
474 new_name: &str,
475) -> RenameResult<(FileId, TextEdit)> {
476 let nav = def.try_to_nav(sema.db).unwrap();
477
478 let mut replacement_text = String::new();
479 let mut repl_range = nav.focus_or_full_range();
480 if let Definition::Local(local) = def {
481 if let Either::Left(pat) = local.source(sema.db).value {
482 if matches!(
483 pat.syntax().parent().and_then(ast::RecordPatField::cast),
484 Some(pat_field) if pat_field.name_ref().is_none()
485 ) {
486 replacement_text.push_str(": ");
487 replacement_text.push_str(new_name);
488 repl_range = TextRange::new(
489 pat.syntax().text_range().end(),
490 pat.syntax().text_range().end(),
491 );
492 }
493 }
494 }
495 if replacement_text.is_empty() {
496 replacement_text.push_str(new_name);
497 }
498 let edit = TextEdit::replace(repl_range, replacement_text);
499 Ok((nav.file_id, edit))
454} 500}
455 501
456#[cfg(test)] 502#[cfg(test)]
@@ -872,7 +918,7 @@ mod foo$0;
872"#, 918"#,
873 expect![[r#" 919 expect![[r#"
874 RangeInfo { 920 RangeInfo {
875 range: 4..7, 921 range: 0..0,
876 info: SourceChange { 922 info: SourceChange {
877 source_file_edits: { 923 source_file_edits: {
878 FileId( 924 FileId(
@@ -924,7 +970,7 @@ use crate::foo$0::FooContent;
924"#, 970"#,
925 expect![[r#" 971 expect![[r#"
926 RangeInfo { 972 RangeInfo {
927 range: 11..14, 973 range: 0..0,
928 info: SourceChange { 974 info: SourceChange {
929 source_file_edits: { 975 source_file_edits: {
930 FileId( 976 FileId(
@@ -980,7 +1026,7 @@ mod fo$0o;
980"#, 1026"#,
981 expect![[r#" 1027 expect![[r#"
982 RangeInfo { 1028 RangeInfo {
983 range: 4..7, 1029 range: 0..0,
984 info: SourceChange { 1030 info: SourceChange {
985 source_file_edits: { 1031 source_file_edits: {
986 FileId( 1032 FileId(
@@ -1027,7 +1073,7 @@ mod outer { mod fo$0o; }
1027"#, 1073"#,
1028 expect![[r#" 1074 expect![[r#"
1029 RangeInfo { 1075 RangeInfo {
1030 range: 16..19, 1076 range: 0..0,
1031 info: SourceChange { 1077 info: SourceChange {
1032 source_file_edits: { 1078 source_file_edits: {
1033 FileId( 1079 FileId(
@@ -1097,7 +1143,7 @@ pub mod foo$0;
1097"#, 1143"#,
1098 expect![[r#" 1144 expect![[r#"
1099 RangeInfo { 1145 RangeInfo {
1100 range: 8..11, 1146 range: 0..0,
1101 info: SourceChange { 1147 info: SourceChange {
1102 source_file_edits: { 1148 source_file_edits: {
1103 FileId( 1149 FileId(