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