aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/references/rename.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/references/rename.rs')
-rw-r--r--crates/ide/src/references/rename.rs378
1 files changed, 189 insertions, 189 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 22ddeeae3..1919639a3 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -88,6 +88,8 @@ pub(crate) fn rename_with_semantics(
88 let def = find_definition(sema, syntax, position)?; 88 let def = find_definition(sema, syntax, position)?;
89 match def { 89 match def {
90 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), 90 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
91 Definition::SelfType(_) => bail!("Cannot rename `Self`"),
92 Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"),
91 def => rename_reference(sema, def, new_name), 93 def => rename_reference(sema, def, new_name),
92 } 94 }
93} 95}
@@ -122,7 +124,7 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
122 Ok(IdentifierKind::Lifetime) 124 Ok(IdentifierKind::Lifetime)
123 } 125 }
124 (SyntaxKind::LIFETIME_IDENT, _) => { 126 (SyntaxKind::LIFETIME_IDENT, _) => {
125 bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name) 127 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
126 } 128 }
127 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), 129 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
128 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), 130 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
@@ -162,119 +164,6 @@ fn find_definition(
162 .ok_or_else(|| format_err!("No references found at position")) 164 .ok_or_else(|| format_err!("No references found at position"))
163} 165}
164 166
165fn source_edit_from_references(
166 _sema: &Semantics<RootDatabase>,
167 file_id: FileId,
168 references: &[FileReference],
169 def: Definition,
170 new_name: &str,
171) -> (FileId, TextEdit) {
172 let mut edit = TextEdit::builder();
173 for reference in references {
174 let (range, replacement) = match &reference.name {
175 // if the ranges differ then the node is inside a macro call, we can't really attempt
176 // to make special rewrites like shorthand syntax and such, so just rename the node in
177 // the macro input
178 ast::NameLike::NameRef(name_ref)
179 if name_ref.syntax().text_range() == reference.range =>
180 {
181 source_edit_from_name_ref(name_ref, new_name, def)
182 }
183 ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
184 source_edit_from_name(name, new_name)
185 }
186 _ => None,
187 }
188 .unwrap_or_else(|| (reference.range, new_name.to_string()));
189 edit.replace(range, replacement);
190 }
191 (file_id, edit.finish())
192}
193
194fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
195 if let Some(_) = ast::RecordPatField::for_field_name(name) {
196 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
197 return Some((
198 TextRange::empty(ident_pat.syntax().text_range().start()),
199 format!("{}: ", new_name),
200 ));
201 }
202 }
203 None
204}
205
206fn source_edit_from_name_ref(
207 name_ref: &ast::NameRef,
208 new_name: &str,
209 def: Definition,
210) -> Option<(TextRange, String)> {
211 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
212 let rcf_name_ref = record_field.name_ref();
213 let rcf_expr = record_field.expr();
214 match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
215 // field: init-expr, check if we can use a field init shorthand
216 (Some(field_name), Some(init)) => {
217 if field_name == *name_ref {
218 if init.text() == new_name {
219 mark::hit!(test_rename_field_put_init_shorthand);
220 // same names, we can use a shorthand here instead.
221 // we do not want to erase attributes hence this range start
222 let s = field_name.syntax().text_range().start();
223 let e = record_field.syntax().text_range().end();
224 return Some((TextRange::new(s, e), new_name.to_owned()));
225 }
226 } else if init == *name_ref {
227 if field_name.text() == new_name {
228 mark::hit!(test_rename_local_put_init_shorthand);
229 // same names, we can use a shorthand here instead.
230 // we do not want to erase attributes hence this range start
231 let s = field_name.syntax().text_range().start();
232 let e = record_field.syntax().text_range().end();
233 return Some((TextRange::new(s, e), new_name.to_owned()));
234 }
235 }
236 None
237 }
238 // init shorthand
239 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
240 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
241 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
242 mark::hit!(test_rename_field_in_field_shorthand);
243 let s = name_ref.syntax().text_range().start();
244 Some((TextRange::empty(s), format!("{}: ", new_name)))
245 }
246 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
247 mark::hit!(test_rename_local_in_field_shorthand);
248 let s = name_ref.syntax().text_range().end();
249 Some((TextRange::empty(s), format!(": {}", new_name)))
250 }
251 _ => None,
252 }
253 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
254 let rcf_name_ref = record_field.name_ref();
255 let rcf_pat = record_field.pat();
256 match (rcf_name_ref, rcf_pat) {
257 // field: rename
258 (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
259 // field name is being renamed
260 if pat.name().map_or(false, |it| it.text() == new_name) {
261 mark::hit!(test_rename_field_put_init_shorthand_pat);
262 // same names, we can use a shorthand here instead/
263 // we do not want to erase attributes hence this range start
264 let s = field_name.syntax().text_range().start();
265 let e = record_field.syntax().text_range().end();
266 Some((TextRange::new(s, e), pat.to_string()))
267 } else {
268 None
269 }
270 }
271 _ => None,
272 }
273 } else {
274 None
275 }
276}
277
278fn rename_mod( 167fn rename_mod(
279 sema: &Semantics<RootDatabase>, 168 sema: &Semantics<RootDatabase>,
280 module: Module, 169 module: Module,
@@ -308,18 +197,75 @@ fn rename_mod(
308 TextEdit::replace(name.syntax().text_range(), new_name.to_string()), 197 TextEdit::replace(name.syntax().text_range(), new_name.to_string()),
309 ), 198 ),
310 _ => unreachable!(), 199 _ => unreachable!(),
311 }; 200 }
312 } 201 }
313 let def = Definition::ModuleDef(ModuleDef::Module(module)); 202 let def = Definition::ModuleDef(ModuleDef::Module(module));
314 let usages = def.usages(sema).all(); 203 let usages = def.usages(sema).all();
315 let ref_edits = usages.iter().map(|(&file_id, references)| { 204 let ref_edits = usages.iter().map(|(&file_id, references)| {
316 source_edit_from_references(sema, file_id, references, def, new_name) 205 (file_id, source_edit_from_references(references, def, new_name))
317 }); 206 });
318 source_change.extend(ref_edits); 207 source_change.extend(ref_edits);
319 208
320 Ok(source_change) 209 Ok(source_change)
321} 210}
322 211
212fn rename_reference(
213 sema: &Semantics<RootDatabase>,
214 def: Definition,
215 new_name: &str,
216) -> RenameResult<SourceChange> {
217 let ident_kind = check_identifier(new_name)?;
218
219 let def_is_lbl_or_lt = matches!(
220 def,
221 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
222 );
223 match (ident_kind, def) {
224 (IdentifierKind::ToSelf, _)
225 | (IdentifierKind::Underscore, _)
226 | (IdentifierKind::Ident, _)
227 if def_is_lbl_or_lt =>
228 {
229 mark::hit!(rename_not_a_lifetime_ident_ref);
230 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
231 }
232 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
233 (IdentifierKind::Lifetime, _) => {
234 mark::hit!(rename_not_an_ident_ref);
235 bail!("Invalid name `{}`: not an identifier", new_name)
236 }
237 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
238 // no-op
239 mark::hit!(rename_self_to_self);
240 return Ok(SourceChange::default());
241 }
242 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
243 mark::hit!(rename_self_to_param);
244 return rename_self_to_param(sema, local, new_name, ident_kind);
245 }
246 (IdentifierKind::ToSelf, Definition::Local(local)) => {
247 mark::hit!(rename_to_self);
248 return rename_to_self(sema, local);
249 }
250 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name),
251 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
252 }
253
254 let usages = def.usages(sema).all();
255 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
256 mark::hit!(rename_underscore_multiple);
257 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
258 }
259 let mut source_change = SourceChange::default();
260 source_change.extend(usages.iter().map(|(&file_id, references)| {
261 (file_id, source_edit_from_references(&references, def, new_name))
262 }));
263
264 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
265 source_change.insert_source_edit(file_id, edit);
266 Ok(source_change)
267}
268
323fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { 269fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {
324 if never!(local.is_self(sema.db)) { 270 if never!(local.is_self(sema.db)) {
325 bail!("rename_to_self invoked on self"); 271 bail!("rename_to_self invoked on self");
@@ -384,7 +330,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
384 let usages = def.usages(sema).all(); 330 let usages = def.usages(sema).all();
385 let mut source_change = SourceChange::default(); 331 let mut source_change = SourceChange::default();
386 source_change.extend(usages.iter().map(|(&file_id, references)| { 332 source_change.extend(usages.iter().map(|(&file_id, references)| {
387 source_edit_from_references(sema, file_id, references, def, "self") 333 (file_id, source_edit_from_references(references, def, "self"))
388 })); 334 }));
389 source_change.insert_source_edit( 335 source_change.insert_source_edit(
390 file_id.original_file(sema.db), 336 file_id.original_file(sema.db),
@@ -394,29 +340,6 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
394 Ok(source_change) 340 Ok(source_change)
395} 341}
396 342
397fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
398 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
399 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
400 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
401 }
402 None
403 }
404
405 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
406 let type_name = target_type_name(&impl_def)?;
407
408 let mut replacement_text = String::from(new_name);
409 replacement_text.push_str(": ");
410 match (self_param.amp_token(), self_param.mut_token()) {
411 (None, None) => (),
412 (Some(_), None) => replacement_text.push('&'),
413 (_, Some(_)) => replacement_text.push_str("&mut "),
414 };
415 replacement_text.push_str(type_name.as_str());
416
417 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
418}
419
420fn rename_self_to_param( 343fn rename_self_to_param(
421 sema: &Semantics<RootDatabase>, 344 sema: &Semantics<RootDatabase>,
422 local: hir::Local, 345 local: hir::Local,
@@ -441,66 +364,143 @@ fn rename_self_to_param(
441 let mut source_change = SourceChange::default(); 364 let mut source_change = SourceChange::default();
442 source_change.insert_source_edit(file_id.original_file(sema.db), edit); 365 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
443 source_change.extend(usages.iter().map(|(&file_id, references)| { 366 source_change.extend(usages.iter().map(|(&file_id, references)| {
444 source_edit_from_references(sema, file_id, &references, def, new_name) 367 (file_id, source_edit_from_references(&references, def, new_name))
445 })); 368 }));
446 Ok(source_change) 369 Ok(source_change)
447} 370}
448 371
449fn rename_reference( 372fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
450 sema: &Semantics<RootDatabase>, 373 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
374 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
375 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
376 }
377 None
378 }
379
380 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
381 let type_name = target_type_name(&impl_def)?;
382
383 let mut replacement_text = String::from(new_name);
384 replacement_text.push_str(": ");
385 match (self_param.amp_token(), self_param.mut_token()) {
386 (Some(_), None) => replacement_text.push('&'),
387 (Some(_), Some(_)) => replacement_text.push_str("&mut "),
388 (_, _) => (),
389 };
390 replacement_text.push_str(type_name.as_str());
391
392 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
393}
394
395fn source_edit_from_references(
396 references: &[FileReference],
451 def: Definition, 397 def: Definition,
452 new_name: &str, 398 new_name: &str,
453) -> RenameResult<SourceChange> { 399) -> TextEdit {
454 let ident_kind = check_identifier(new_name)?; 400 let mut edit = TextEdit::builder();
455 401 for reference in references {
456 let def_is_lbl_or_lt = matches!( 402 let (range, replacement) = match &reference.name {
457 def, 403 // if the ranges differ then the node is inside a macro call, we can't really attempt
458 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) 404 // to make special rewrites like shorthand syntax and such, so just rename the node in
459 ); 405 // the macro input
460 match (ident_kind, def) { 406 ast::NameLike::NameRef(name_ref)
461 (IdentifierKind::ToSelf, _) 407 if name_ref.syntax().text_range() == reference.range =>
462 | (IdentifierKind::Underscore, _) 408 {
463 | (IdentifierKind::Ident, _) 409 source_edit_from_name_ref(name_ref, new_name, def)
464 if def_is_lbl_or_lt => 410 }
465 { 411 ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
466 mark::hit!(rename_not_a_lifetime_ident_ref); 412 source_edit_from_name(name, new_name)
467 bail!("Invalid name `{}`: not a lifetime identifier", new_name) 413 }
468 } 414 _ => None,
469 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
470 (IdentifierKind::Lifetime, _) => {
471 mark::hit!(rename_not_an_ident_ref);
472 bail!("Invalid name `{}`: not an identifier", new_name)
473 }
474 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
475 // no-op
476 mark::hit!(rename_self_to_self);
477 return Ok(SourceChange::default());
478 }
479 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
480 mark::hit!(rename_self_to_param);
481 return rename_self_to_param(sema, local, new_name, ident_kind);
482 }
483 (IdentifierKind::ToSelf, Definition::Local(local)) => {
484 mark::hit!(rename_to_self);
485 return rename_to_self(sema, local);
486 } 415 }
487 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), 416 .unwrap_or_else(|| (reference.range, new_name.to_string()));
488 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 417 edit.replace(range, replacement);
489 } 418 }
419 edit.finish()
420}
490 421
491 let usages = def.usages(sema).all(); 422fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
492 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { 423 if let Some(_) = ast::RecordPatField::for_field_name(name) {
493 mark::hit!(rename_underscore_multiple); 424 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
494 bail!("Cannot rename reference to `_` as it is being referenced multiple times"); 425 return Some((
426 TextRange::empty(ident_pat.syntax().text_range().start()),
427 [new_name, ": "].concat(),
428 ));
429 }
495 } 430 }
496 let mut source_change = SourceChange::default(); 431 None
497 source_change.extend(usages.iter().map(|(&file_id, references)| { 432}
498 source_edit_from_references(sema, file_id, &references, def, new_name)
499 }));
500 433
501 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; 434fn source_edit_from_name_ref(
502 source_change.insert_source_edit(file_id, edit); 435 name_ref: &ast::NameRef,
503 Ok(source_change) 436 new_name: &str,
437 def: Definition,
438) -> Option<(TextRange, String)> {
439 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
440 let rcf_name_ref = record_field.name_ref();
441 let rcf_expr = record_field.expr();
442 match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
443 // field: init-expr, check if we can use a field init shorthand
444 (Some(field_name), Some(init)) => {
445 if field_name == *name_ref {
446 if init.text() == new_name {
447 mark::hit!(test_rename_field_put_init_shorthand);
448 // same names, we can use a shorthand here instead.
449 // we do not want to erase attributes hence this range start
450 let s = field_name.syntax().text_range().start();
451 let e = record_field.syntax().text_range().end();
452 return Some((TextRange::new(s, e), new_name.to_owned()));
453 }
454 } else if init == *name_ref {
455 if field_name.text() == new_name {
456 mark::hit!(test_rename_local_put_init_shorthand);
457 // same names, we can use a shorthand here instead.
458 // we do not want to erase attributes hence this range start
459 let s = field_name.syntax().text_range().start();
460 let e = record_field.syntax().text_range().end();
461 return Some((TextRange::new(s, e), new_name.to_owned()));
462 }
463 }
464 None
465 }
466 // init shorthand
467 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
468 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
469 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
470 mark::hit!(test_rename_field_in_field_shorthand);
471 let s = name_ref.syntax().text_range().start();
472 Some((TextRange::empty(s), format!("{}: ", new_name)))
473 }
474 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
475 mark::hit!(test_rename_local_in_field_shorthand);
476 let s = name_ref.syntax().text_range().end();
477 Some((TextRange::empty(s), format!(": {}", new_name)))
478 }
479 _ => None,
480 }
481 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
482 let rcf_name_ref = record_field.name_ref();
483 let rcf_pat = record_field.pat();
484 match (rcf_name_ref, rcf_pat) {
485 // field: rename
486 (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
487 // field name is being renamed
488 if pat.name().map_or(false, |it| it.text() == new_name) {
489 mark::hit!(test_rename_field_put_init_shorthand_pat);
490 // same names, we can use a shorthand here instead/
491 // we do not want to erase attributes hence this range start
492 let s = field_name.syntax().text_range().start();
493 let e = record_field.syntax().text_range().end();
494 Some((TextRange::new(s, e), pat.to_string()))
495 } else {
496 None
497 }
498 }
499 _ => None,
500 }
501 } else {
502 None
503 }
504} 504}
505 505
506fn source_edit_from_def( 506fn source_edit_from_def(