1use std::fmt::{self, Display};
24
25use crate::{
26 source_change::ChangeAnnotation,
27 text_edit::{TextEdit, TextEditBuilder},
28};
29use base_db::AnchoredPathBuf;
30use either::Either;
31use hir::{FieldSource, FileRange, InFile, ModuleSource, Name, Semantics, sym};
32use span::{Edition, FileId, SyntaxContext};
33use stdx::{TupleExt, never};
34use syntax::{
35 AstNode, SyntaxKind, T, TextRange,
36 ast::{self, HasName},
37};
38
39use crate::{
40 RootDatabase,
41 defs::Definition,
42 search::{FileReference, FileReferenceNode},
43 source_change::{FileSystemEdit, SourceChange},
44 syntax_helpers::node_ext::expr_as_name_ref,
45 traits::convert_to_def_in_trait,
46};
47
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct RenameConfig {
50 pub show_conflicts: bool,
51}
52
53pub type Result<T, E = RenameError> = std::result::Result<T, E>;
54
55#[derive(Debug)]
56pub struct RenameError(pub String);
57
58impl fmt::Display for RenameError {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 fmt::Display::fmt(&self.0, f)
61 }
62}
63
64#[macro_export]
65macro_rules! _format_err {
66 ($fmt:expr) => { RenameError(format!($fmt)) };
67 ($fmt:expr, $($arg:tt)+) => { RenameError(format!($fmt, $($arg)+)) }
68}
69pub use _format_err as format_err;
70
71#[macro_export]
72macro_rules! _bail {
73 ($($tokens:tt)*) => { return Err(format_err!($($tokens)*)) }
74}
75pub use _bail as bail;
76
77#[derive(Copy, Clone, Debug, PartialEq, Eq)]
78pub enum RenameDefinition {
79 Yes,
80 No,
81}
82
83impl Definition {
84 pub fn rename(
85 &self,
86 sema: &Semantics<'_, RootDatabase>,
87 new_name: &str,
88 rename_definition: RenameDefinition,
89 config: &RenameConfig,
90 ) -> Result<SourceChange> {
91 let edition = if let Some(krate) = self.krate(sema.db) {
96 if !krate.origin(sema.db).is_local() {
99 bail!("Cannot rename a non-local definition")
100 }
101 krate.edition(sema.db)
102 } else {
103 Edition::LATEST
104 };
105
106 match *self {
107 Definition::Module(module) => rename_mod(sema, module, new_name),
108 Definition::ToolModule(_) => {
109 bail!("Cannot rename a tool module")
110 }
111 Definition::BuiltinType(_) => {
112 bail!("Cannot rename builtin type")
113 }
114 Definition::BuiltinAttr(_) => {
115 bail!("Cannot rename a builtin attr.")
116 }
117 Definition::SelfType(_) => bail!("Cannot rename `Self`"),
118 Definition::Macro(mac) => rename_reference(
119 sema,
120 Definition::Macro(mac),
121 new_name,
122 rename_definition,
123 edition,
124 config,
125 ),
126 def => rename_reference(sema, def, new_name, rename_definition, edition, config),
127 }
128 }
129
130 pub fn range_for_rename(self, sema: &Semantics<'_, RootDatabase>) -> Option<FileRange> {
135 let syn_ctx_is_root = |(range, ctx): (_, SyntaxContext)| ctx.is_root().then_some(range);
136 let res = match self {
137 Definition::Macro(mac) => {
138 let src = sema.source(mac)?;
139 let name = match &src.value {
140 Either::Left(it) => it.name()?,
141 Either::Right(it) => it.name()?,
142 };
143 src.with_value(name.syntax())
144 .original_file_range_opt(sema.db)
145 .and_then(syn_ctx_is_root)
146 }
147 Definition::Field(field) => {
148 let src = sema.source(field)?;
149 match &src.value {
150 FieldSource::Named(record_field) => {
151 let name = record_field.name()?;
152 src.with_value(name.syntax())
153 .original_file_range_opt(sema.db)
154 .and_then(syn_ctx_is_root)
155 }
156 FieldSource::Pos(_) => None,
157 }
158 }
159 Definition::Crate(_) => None,
160 Definition::Module(module) => {
161 let src = module.declaration_source(sema.db)?;
162 let name = src.value.name()?;
163 src.with_value(name.syntax())
164 .original_file_range_opt(sema.db)
165 .and_then(syn_ctx_is_root)
166 }
167 Definition::Function(it) => name_range(it, sema).and_then(syn_ctx_is_root),
168 Definition::Adt(adt) => match adt {
169 hir::Adt::Struct(it) => name_range(it, sema).and_then(syn_ctx_is_root),
170 hir::Adt::Union(it) => name_range(it, sema).and_then(syn_ctx_is_root),
171 hir::Adt::Enum(it) => name_range(it, sema).and_then(syn_ctx_is_root),
172 },
173 Definition::Variant(it) => name_range(it, sema).and_then(syn_ctx_is_root),
174 Definition::Const(it) => name_range(it, sema).and_then(syn_ctx_is_root),
175 Definition::Static(it) => name_range(it, sema).and_then(syn_ctx_is_root),
176 Definition::Trait(it) => name_range(it, sema).and_then(syn_ctx_is_root),
177 Definition::TypeAlias(it) => name_range(it, sema).and_then(syn_ctx_is_root),
178 Definition::Local(it) => {
179 name_range(it.primary_source(sema.db), sema).and_then(syn_ctx_is_root)
180 }
181 Definition::GenericParam(generic_param) => match generic_param {
182 hir::GenericParam::LifetimeParam(lifetime_param) => {
183 let src = sema.source(lifetime_param)?;
184 src.with_value(src.value.lifetime()?.syntax())
185 .original_file_range_opt(sema.db)
186 .and_then(syn_ctx_is_root)
187 }
188 _ => {
189 let param = match generic_param {
190 hir::GenericParam::TypeParam(it) => it.merge(),
191 hir::GenericParam::ConstParam(it) => it.merge(),
192 hir::GenericParam::LifetimeParam(_) => return None,
193 };
194 let src = sema.source(param)?;
195 let name = match &src.value {
196 Either::Left(x) => x.name()?,
197 Either::Right(_) => return None,
198 };
199 src.with_value(name.syntax())
200 .original_file_range_opt(sema.db)
201 .and_then(syn_ctx_is_root)
202 }
203 },
204 Definition::Label(label) => {
205 let src = sema.source(label)?;
206 let lifetime = src.value.lifetime()?;
207 src.with_value(lifetime.syntax())
208 .original_file_range_opt(sema.db)
209 .and_then(syn_ctx_is_root)
210 }
211 Definition::ExternCrateDecl(it) => {
212 let src = sema.source(it)?;
213 if let Some(rename) = src.value.rename() {
214 let name = rename.name()?;
215 src.with_value(name.syntax())
216 .original_file_range_opt(sema.db)
217 .and_then(syn_ctx_is_root)
218 } else {
219 let name = src.value.name_ref()?;
220 src.with_value(name.syntax())
221 .original_file_range_opt(sema.db)
222 .and_then(syn_ctx_is_root)
223 }
224 }
225 Definition::InlineAsmOperand(it) => name_range(it, sema).and_then(syn_ctx_is_root),
226 Definition::BuiltinType(_)
227 | Definition::BuiltinLifetime(_)
228 | Definition::BuiltinAttr(_)
229 | Definition::SelfType(_)
230 | Definition::ToolModule(_)
231 | Definition::TupleField(_)
232 | Definition::InlineAsmRegOrRegClass(_) => return None,
233 Definition::DeriveHelper(_) => return None,
235 };
236 return res;
237
238 fn name_range<D>(
239 def: D,
240 sema: &Semantics<'_, RootDatabase>,
241 ) -> Option<(FileRange, SyntaxContext)>
242 where
243 D: hir::HasSource,
244 D::Ast: ast::HasName,
245 {
246 let src = sema.source(def)?;
247 let name = src.value.name()?;
248 src.with_value(name.syntax()).original_file_range_opt(sema.db)
249 }
250 }
251}
252
253fn rename_mod(
254 sema: &Semantics<'_, RootDatabase>,
255 module: hir::Module,
256 new_name: &str,
257) -> Result<SourceChange> {
258 let mut source_change = SourceChange::default();
259
260 if module.is_crate_root(sema.db) {
261 return Ok(source_change);
262 }
263
264 let InFile { file_id, value: def_source } = module.definition_source(sema.db);
265 let edition = file_id.edition(sema.db);
266 let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;
267 if kind != IdentifierKind::Ident {
268 bail!(
269 "Invalid name `{0}`: cannot rename module to {0}",
270 new_name.display(sema.db, edition)
271 );
272 }
273 if let ModuleSource::SourceFile(..) = def_source {
274 let anchor = file_id.original_file(sema.db).file_id(sema.db);
275
276 let is_mod_rs = module.is_mod_rs(sema.db);
277 let has_detached_child = module.children(sema.db).any(|child| !child.is_inline(sema.db));
278
279 if !is_mod_rs {
281 let path = format!("{}.rs", new_name.as_str());
282 let dst = AnchoredPathBuf { anchor, path };
283 source_change.push_file_system_edit(FileSystemEdit::MoveFile { src: anchor, dst })
284 }
285
286 let dir_paths = match (is_mod_rs, has_detached_child, module.name(sema.db)) {
290 (true, _, Some(mod_name)) => {
292 Some((format!("../{}", mod_name.as_str()), format!("../{}", new_name.as_str())))
293 }
294 (false, true, Some(mod_name)) => {
296 Some((mod_name.as_str().to_owned(), new_name.as_str().to_owned()))
297 }
298 _ => None,
299 };
300
301 if let Some((src, dst)) = dir_paths {
302 let src = AnchoredPathBuf { anchor, path: src };
303 let dst = AnchoredPathBuf { anchor, path: dst };
304 source_change.push_file_system_edit(FileSystemEdit::MoveDir {
305 src,
306 src_id: anchor,
307 dst,
308 })
309 }
310 }
311
312 if let Some(src) = module.declaration_source(sema.db) {
313 let file_id = src.file_id.original_file(sema.db);
314 match src.value.name() {
315 Some(name) => {
316 if let Some(file_range) = src
317 .with_value(name.syntax())
318 .original_file_range_opt(sema.db)
319 .map(TupleExt::head)
320 {
321 let new_name = new_name.display(sema.db, edition).to_string();
322 source_change.insert_source_edit(
323 file_id.file_id(sema.db),
324 TextEdit::replace(file_range.range, new_name),
325 )
326 };
327 }
328 _ => never!("Module source node is missing a name"),
329 }
330 }
331
332 let def = Definition::Module(module);
333 let usages = def.usages(sema).all();
334 let ref_edits = usages.iter().map(|(file_id, references)| {
335 let edition = file_id.edition(sema.db);
336 (
337 file_id.file_id(sema.db),
338 source_edit_from_references(sema.db, references, def, &new_name, edition),
339 )
340 });
341 source_change.extend(ref_edits);
342
343 Ok(source_change)
344}
345
346fn rename_reference(
347 sema: &Semantics<'_, RootDatabase>,
348 def: Definition,
349 new_name: &str,
350 rename_definition: RenameDefinition,
351 edition: Edition,
352 config: &RenameConfig,
353) -> Result<SourceChange> {
354 let (mut new_name, ident_kind) = IdentifierKind::classify(edition, new_name)?;
355
356 if matches!(
357 def,
358 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
359 ) {
360 match ident_kind {
361 IdentifierKind::Underscore => {
362 bail!(
363 "Invalid name `{}`: not a lifetime identifier",
364 new_name.display(sema.db, edition)
365 );
366 }
367 IdentifierKind::Ident => {
368 new_name = Name::new_lifetime(&format!("'{}", new_name.as_str()))
369 }
370 IdentifierKind::Lifetime => (),
371 IdentifierKind::LowercaseSelf => bail!(
372 "Invalid name `{}`: not a lifetime identifier",
373 new_name.display(sema.db, edition)
374 ),
375 }
376 } else {
377 match ident_kind {
378 IdentifierKind::Lifetime => {
379 cov_mark::hit!(rename_not_an_ident_ref);
380 bail!("Invalid name `{}`: not an identifier", new_name.display(sema.db, edition));
381 }
382 IdentifierKind::Ident => cov_mark::hit!(rename_non_local),
383 IdentifierKind::Underscore => (),
384 IdentifierKind::LowercaseSelf => {
385 bail!(
386 "Invalid name `{}`: cannot rename to `self`",
387 new_name.display(sema.db, edition)
388 );
389 }
390 }
391 }
392
393 let def = convert_to_def_in_trait(sema.db, def);
394 let usages = def.usages(sema).all();
395
396 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
397 cov_mark::hit!(rename_underscore_multiple);
398 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
399 }
400 let mut source_change = SourceChange::default();
401 source_change.extend(usages.iter().map(|(file_id, references)| {
402 let edition = file_id.edition(sema.db);
403 (
404 file_id.file_id(sema.db),
405 source_edit_from_references(sema.db, references, def, &new_name, edition),
406 )
407 }));
408 if rename_definition == RenameDefinition::Yes {
409 let (file_id, edit) =
412 source_edit_from_def(sema, config, def, &new_name, &mut source_change)?;
413 source_change.insert_source_edit(file_id, edit);
414 }
415 Ok(source_change)
416}
417
418pub fn source_edit_from_references(
419 db: &RootDatabase,
420 references: &[FileReference],
421 def: Definition,
422 new_name: &Name,
423 edition: Edition,
424) -> TextEdit {
425 let name_display = new_name.display(db, edition);
426 let mut edit = TextEdit::builder();
427 let mut edited_ranges = Vec::new();
429 for &FileReference { range, ref name, .. } in references {
430 let name_range = name.text_range();
431 let has_emitted_edit = match name {
432 FileReferenceNode::NameRef(name_ref) if name_range == range => {
436 source_edit_from_name_ref(&mut edit, name_ref, &name_display, def)
437 }
438 FileReferenceNode::Name(name) if name_range == range => {
439 source_edit_from_name(&mut edit, name, &name_display)
440 }
441 _ => false,
442 };
443 if !has_emitted_edit && !edited_ranges.contains(&range.start()) {
444 edit.replace(range, name_display.to_string());
445 edited_ranges.push(range.start());
446 }
447 }
448
449 edit.finish()
450}
451
452fn source_edit_from_name(
453 edit: &mut TextEditBuilder,
454 name: &ast::Name,
455 new_name: &dyn Display,
456) -> bool {
457 if ast::RecordPatField::for_field_name(name).is_some()
458 && let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast)
459 {
460 cov_mark::hit!(rename_record_pat_field_name_split);
461 edit.insert(ident_pat.syntax().text_range().start(), format!("{new_name}: "));
467 return true;
468 }
469
470 false
471}
472
473fn source_edit_from_name_ref(
474 edit: &mut TextEditBuilder,
475 name_ref: &ast::NameRef,
476 new_name: &dyn Display,
477 def: Definition,
478) -> bool {
479 if name_ref.super_token().is_some() {
480 return true;
481 }
482
483 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
484 let rcf_name_ref = record_field.name_ref();
485 let rcf_expr = record_field.expr();
486 match &(rcf_name_ref, rcf_expr.and_then(|it| expr_as_name_ref(&it))) {
487 (Some(field_name), Some(init)) => {
489 let new_name = new_name.to_string();
490 if field_name == name_ref {
491 if init.text() == new_name {
492 cov_mark::hit!(test_rename_field_put_init_shorthand);
493 let s = field_name.syntax().text_range().start();
499 let e = init.syntax().text_range().start();
500 edit.delete(TextRange::new(s, e));
501 return true;
502 }
503 } else if init == name_ref && field_name.text() == new_name {
504 cov_mark::hit!(test_rename_local_put_init_shorthand);
505 let s = field_name.syntax().text_range().end();
511 let e = init.syntax().text_range().end();
512 edit.delete(TextRange::new(s, e));
513 return true;
514 }
515 }
516 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
518 cov_mark::hit!(test_rename_field_in_field_shorthand);
519 let offset = name_ref.syntax().text_range().start();
522 edit.insert(offset, format!("{new_name}: "));
523 return true;
524 }
525 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
526 cov_mark::hit!(test_rename_local_in_field_shorthand);
527 let offset = name_ref.syntax().text_range().end();
530 edit.insert(offset, format!(": {new_name}"));
531 return true;
532 }
533 _ => (),
534 }
535 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
536 let rcf_name_ref = record_field.name_ref();
537 let rcf_pat = record_field.pat();
538 match (rcf_name_ref, rcf_pat) {
539 (Some(field_name), Some(ast::Pat::IdentPat(pat)))
541 if field_name == *name_ref && pat.at_token().is_none() =>
542 {
543 if let Some(name) = pat.name() {
545 let new_name = new_name.to_string();
546 if name.text() == new_name {
547 cov_mark::hit!(test_rename_field_put_init_shorthand_pat);
548 let s = field_name.syntax().text_range().start();
555 let e = pat.syntax().text_range().start();
556 edit.delete(TextRange::new(s, e));
557 edit.replace(name.syntax().text_range(), new_name);
558 return true;
559 }
560 }
561 }
562 _ => (),
563 }
564 }
565 false
566}
567
568fn source_edit_from_def(
569 sema: &Semantics<'_, RootDatabase>,
570 config: &RenameConfig,
571 def: Definition,
572 new_name: &Name,
573 source_change: &mut SourceChange,
574) -> Result<(FileId, TextEdit)> {
575 let mut edit = TextEdit::builder();
576 if let Definition::Local(local) = def {
577 let mut file_id = None;
578
579 let conflict_annotation =
580 if config.show_conflicts && !sema.rename_conflicts(&local, new_name).is_empty() {
581 Some(
582 source_change.insert_annotation(ChangeAnnotation {
583 label: "This rename will change the program's meaning".to_owned(),
584 needs_confirmation: true,
585 description: Some(
586 "Some variable(s) will shadow the renamed variable \
587 or be shadowed by it if the rename is performed"
588 .to_owned(),
589 ),
590 }),
591 )
592 } else {
593 None
594 };
595
596 for source in local.sources(sema.db) {
597 let source = match source.source.clone().original_ast_node_rooted(sema.db) {
598 Some(source) => source,
599 None => match source
600 .source
601 .syntax()
602 .original_file_range_opt(sema.db)
603 .map(TupleExt::head)
604 {
605 Some(FileRange { file_id: file_id2, range }) => {
606 file_id = Some(file_id2);
607 edit.replace(
608 range,
609 new_name.display(sema.db, file_id2.edition(sema.db)).to_string(),
610 );
611 continue;
612 }
613 None => {
614 bail!("Can't rename local that is defined in a macro declaration")
615 }
616 },
617 };
618 file_id = Some(source.file_id);
619 if let Either::Left(pat) = source.value {
620 let name_range = pat.name().unwrap().syntax().text_range();
621 if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
623 if let Some(name_ref) = pat_field.name_ref() {
624 if new_name.as_str() == name_ref.text().as_str().trim_start_matches("r#")
625 && pat.at_token().is_none()
626 {
627 cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
631 edit.delete(
632 name_ref
633 .syntax()
634 .text_range()
635 .cover_offset(pat.syntax().text_range().start()),
636 );
637 edit.replace(name_range, name_ref.text().to_string());
638 } else {
639 edit.replace(
643 name_range,
644 new_name
645 .display(sema.db, source.file_id.edition(sema.db))
646 .to_string(),
647 );
648 }
649 } else {
650 edit.insert(
654 pat.syntax().text_range().start(),
655 format!("{}: ", pat_field.field_name().unwrap()),
656 );
657 edit.replace(
658 name_range,
659 new_name.display(sema.db, source.file_id.edition(sema.db)).to_string(),
660 );
661 }
662 } else {
663 edit.replace(
664 name_range,
665 new_name.display(sema.db, source.file_id.edition(sema.db)).to_string(),
666 );
667 }
668 }
669 }
670 let mut edit = edit.finish();
671
672 for (edit, _) in source_change.source_file_edits.values_mut() {
673 edit.set_annotation(conflict_annotation);
674 }
675 edit.set_annotation(conflict_annotation);
676
677 let Some(file_id) = file_id else { bail!("No file available to rename") };
678 return Ok((file_id.file_id(sema.db), edit));
679 }
680 let FileRange { file_id, range } = def
681 .range_for_rename(sema)
682 .ok_or_else(|| format_err!("No identifier available to rename"))?;
683 let (range, new_name) = match def {
684 Definition::ExternCrateDecl(decl) if decl.alias(sema.db).is_none() => (
685 TextRange::empty(range.end()),
686 format!(" as {}", new_name.display(sema.db, file_id.edition(sema.db)),),
687 ),
688 _ => (range, new_name.display(sema.db, file_id.edition(sema.db)).to_string()),
689 };
690 edit.replace(range, new_name);
691 Ok((file_id.file_id(sema.db), edit.finish()))
692}
693
694#[derive(Copy, Clone, Debug, PartialEq)]
695pub enum IdentifierKind {
696 Ident,
697 Lifetime,
698 Underscore,
699 LowercaseSelf,
700}
701
702impl IdentifierKind {
703 pub fn classify(edition: Edition, new_name: &str) -> Result<(Name, IdentifierKind)> {
704 match parser::LexedStr::single_token(edition, new_name) {
705 Some(res) => match res {
706 (SyntaxKind::IDENT, _) => Ok((Name::new_root(new_name), IdentifierKind::Ident)),
707 (T![_], _) => {
708 Ok((Name::new_symbol_root(sym::underscore), IdentifierKind::Underscore))
709 }
710 (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
711 Ok((Name::new_lifetime(new_name), IdentifierKind::Lifetime))
712 }
713 _ if SyntaxKind::from_keyword(new_name, edition).is_some() => match new_name {
714 "self" => Ok((Name::new_root(new_name), IdentifierKind::LowercaseSelf)),
715 "crate" | "super" | "Self" => {
716 bail!("Invalid name `{}`: cannot rename to a keyword", new_name)
717 }
718 _ => Ok((Name::new_root(new_name), IdentifierKind::Ident)),
719 },
720 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
721 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
722 },
723 None => bail!("Invalid name `{}`: not an identifier", new_name),
724 }
725 }
726}