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