Skip to main content

ide_db/
rename.rs

1//! Rename infrastructure for rust-analyzer. It is used primarily for the
2//! literal "rename" in the ide (look for tests there), but it is also available
3//! as a general-purpose service. For example, it is used by the fix for the
4//! "incorrect case" diagnostic.
5//!
6//! It leverages the [`crate::search`] functionality to find what needs to be
7//! renamed. The actual renames are tricky -- field shorthands need special
8//! attention, and, when renaming modules, you also want to rename files on the
9//! file system.
10//!
11//! Another can of worms are macros:
12//!
13//! ```ignore
14//! macro_rules! m { () => { fn f() {} } }
15//! m!();
16//! fn main() {
17//!     f() // <- rename me
18//! }
19//! ```
20//!
21//! The correct behavior in such cases is probably to show a dialog to the user.
22//! Our current behavior is ¯\_(ツ)_/¯.
23use 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, HasCrate, InFile, ModuleSource, Name, Semantics, sym};
32use itertools::Itertools;
33use rustc_hash::FxHashSet;
34use span::{Edition, FileId, SyntaxContext};
35use stdx::{TupleExt, never};
36use syntax::{
37    AstNode, SyntaxKind, T, TextRange,
38    ast::{self, HasName},
39};
40
41use crate::{
42    RootDatabase,
43    defs::Definition,
44    search::{FileReference, FileReferenceNode},
45    source_change::{FileSystemEdit, SourceChange},
46    syntax_helpers::node_ext::expr_as_name_ref,
47    traits::convert_to_def_in_trait,
48};
49
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct RenameConfig {
52    pub show_conflicts: bool,
53}
54
55pub type Result<T, E = RenameError> = std::result::Result<T, E>;
56
57#[derive(Debug)]
58pub struct RenameError(pub String);
59
60impl fmt::Display for RenameError {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        fmt::Display::fmt(&self.0, f)
63    }
64}
65
66#[macro_export]
67macro_rules! _format_err {
68    ($fmt:expr) => { RenameError(format!($fmt)) };
69    ($fmt:expr, $($arg:tt)+) => { RenameError(format!($fmt, $($arg)+)) }
70}
71pub use _format_err as format_err;
72
73#[macro_export]
74macro_rules! _bail {
75    ($($tokens:tt)*) => { return Err(format_err!($($tokens)*)) }
76}
77pub use _bail as bail;
78
79#[derive(Copy, Clone, Debug, PartialEq, Eq)]
80pub enum RenameDefinition {
81    Yes,
82    No,
83}
84
85impl Definition {
86    pub fn rename(
87        &self,
88        sema: &Semantics<'_, RootDatabase>,
89        new_name: &str,
90        rename_definition: RenameDefinition,
91        config: &RenameConfig,
92    ) -> Result<SourceChange> {
93        // self.krate() returns None if
94        // self is a built-in attr, built-in type or tool module.
95        // it is not allowed for these defs to be renamed.
96        // cases where self.krate() is None is handled below.
97        let edition = if let Some(krate) = self.krate(sema.db) {
98            // Can we not rename non-local items?
99            // Then bail if non-local
100            if !krate.origin(sema.db).is_local() {
101                bail!("Cannot rename a non-local definition")
102            }
103            krate.edition(sema.db)
104        } else {
105            Edition::LATEST
106        };
107
108        match *self {
109            Definition::Module(module) => rename_mod(sema, module, new_name),
110            Definition::ToolModule(_) => {
111                bail!("Cannot rename a tool module")
112            }
113            Definition::BuiltinType(_) => {
114                bail!("Cannot rename builtin type")
115            }
116            Definition::BuiltinAttr(_) => {
117                bail!("Cannot rename a builtin attr.")
118            }
119            Definition::SelfType(_) => bail!("Cannot rename `Self`"),
120            Definition::Macro(mac) => rename_reference(
121                sema,
122                Definition::Macro(mac),
123                new_name,
124                rename_definition,
125                edition,
126                config,
127            ),
128            def => rename_reference(sema, def, new_name, rename_definition, edition, config),
129        }
130    }
131
132    /// Textual range of the identifier which will change when renaming this
133    /// `Definition`. Note that builtin types can't be
134    /// renamed and extern crate names will report its range, though a rename will introduce
135    /// an alias instead.
136    pub fn range_for_rename(self, sema: &Semantics<'_, RootDatabase>) -> Option<FileRange> {
137        let syn_ctx_is_root = |(range, ctx): (_, SyntaxContext)| ctx.is_root().then_some(range);
138        let res = match self {
139            Definition::Macro(mac) => {
140                let src = sema.source(mac)?;
141                let name = match &src.value {
142                    Either::Left(it) => it.name()?,
143                    Either::Right(it) => it.name()?,
144                };
145                src.with_value(name.syntax())
146                    .original_file_range_opt(sema.db)
147                    .and_then(syn_ctx_is_root)
148            }
149            Definition::Field(field) => {
150                let src = sema.source(field)?;
151                match &src.value {
152                    FieldSource::Named(record_field) => {
153                        let name = record_field.name()?;
154                        src.with_value(name.syntax())
155                            .original_file_range_opt(sema.db)
156                            .and_then(syn_ctx_is_root)
157                    }
158                    FieldSource::Pos(_) => None,
159                }
160            }
161            Definition::Crate(_) => None,
162            Definition::Module(module) => {
163                let src = module.declaration_source(sema.db)?;
164                let name = src.value.name()?;
165                src.with_value(name.syntax())
166                    .original_file_range_opt(sema.db)
167                    .and_then(syn_ctx_is_root)
168            }
169            Definition::Function(it) => name_range(it, sema).and_then(syn_ctx_is_root),
170            Definition::Adt(adt) => match adt {
171                hir::Adt::Struct(it) => name_range(it, sema).and_then(syn_ctx_is_root),
172                hir::Adt::Union(it) => name_range(it, sema).and_then(syn_ctx_is_root),
173                hir::Adt::Enum(it) => name_range(it, sema).and_then(syn_ctx_is_root),
174            },
175            Definition::EnumVariant(it) => name_range(it, sema).and_then(syn_ctx_is_root),
176            Definition::Const(it) => name_range(it, sema).and_then(syn_ctx_is_root),
177            Definition::Static(it) => name_range(it, sema).and_then(syn_ctx_is_root),
178            Definition::Trait(it) => name_range(it, sema).and_then(syn_ctx_is_root),
179            Definition::TypeAlias(it) => name_range(it, sema).and_then(syn_ctx_is_root),
180            Definition::Local(it) => {
181                name_range(it.primary_source(sema.db), sema).and_then(syn_ctx_is_root)
182            }
183            Definition::GenericParam(generic_param) => match generic_param {
184                hir::GenericParam::LifetimeParam(lifetime_param) => {
185                    let src = sema.source(lifetime_param)?;
186                    src.with_value(src.value.lifetime()?.syntax())
187                        .original_file_range_opt(sema.db)
188                        .and_then(syn_ctx_is_root)
189                }
190                _ => {
191                    let param = match generic_param {
192                        hir::GenericParam::TypeParam(it) => it.merge(),
193                        hir::GenericParam::ConstParam(it) => it.merge(),
194                        hir::GenericParam::LifetimeParam(_) => return None,
195                    };
196                    let src = sema.source(param)?;
197                    let name = match &src.value {
198                        Either::Left(x) => x.name()?,
199                        Either::Right(_) => return None,
200                    };
201                    src.with_value(name.syntax())
202                        .original_file_range_opt(sema.db)
203                        .and_then(syn_ctx_is_root)
204                }
205            },
206            Definition::Label(label) => {
207                let src = sema.source(label)?;
208                let lifetime = src.value.lifetime()?;
209                src.with_value(lifetime.syntax())
210                    .original_file_range_opt(sema.db)
211                    .and_then(syn_ctx_is_root)
212            }
213            Definition::ExternCrateDecl(it) => {
214                let src = sema.source(it)?;
215                if let Some(rename) = src.value.rename() {
216                    let name = rename.name()?;
217                    src.with_value(name.syntax())
218                        .original_file_range_opt(sema.db)
219                        .and_then(syn_ctx_is_root)
220                } else {
221                    let name = src.value.name_ref()?;
222                    src.with_value(name.syntax())
223                        .original_file_range_opt(sema.db)
224                        .and_then(syn_ctx_is_root)
225                }
226            }
227            Definition::InlineAsmOperand(it) => name_range(it, sema).and_then(syn_ctx_is_root),
228            Definition::BuiltinType(_)
229            | Definition::BuiltinLifetime(_)
230            | Definition::BuiltinAttr(_)
231            | Definition::SelfType(_)
232            | Definition::ToolModule(_)
233            | Definition::TupleField(_)
234            | Definition::InlineAsmRegOrRegClass(_) => return None,
235            // FIXME: This should be doable in theory
236            Definition::DeriveHelper(_) => return None,
237        };
238        return res;
239
240        fn name_range<D>(
241            def: D,
242            sema: &Semantics<'_, RootDatabase>,
243        ) -> Option<(FileRange, SyntaxContext)>
244        where
245            D: hir::HasSource,
246            D::Ast: ast::HasName,
247        {
248            let src = sema.source(def)?;
249            let name = src.value.name()?;
250            src.with_value(name.syntax()).original_file_range_opt(sema.db)
251        }
252    }
253}
254
255fn rename_mod(
256    sema: &Semantics<'_, RootDatabase>,
257    module: hir::Module,
258    new_name: &str,
259) -> Result<SourceChange> {
260    let mut source_change = SourceChange::default();
261
262    if module.is_crate_root(sema.db) {
263        return Ok(source_change);
264    }
265
266    let InFile { file_id, value: def_source } = module.definition_source(sema.db);
267    let edition = file_id.edition(sema.db);
268    let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;
269    if kind != IdentifierKind::Ident {
270        bail!(
271            "Invalid name `{0}`: cannot rename module to {0}",
272            new_name.display(sema.db, edition)
273        );
274    }
275    if let ModuleSource::SourceFile(..) = def_source {
276        let anchor = file_id.original_file(sema.db).file_id(sema.db);
277
278        let is_mod_rs = module.is_mod_rs(sema.db);
279        let has_detached_child = module.children(sema.db).any(|child| !child.is_inline(sema.db));
280
281        // Module exists in a named file
282        if !is_mod_rs {
283            let path = format!("{}.rs", new_name.as_str());
284            let dst = AnchoredPathBuf { anchor, path };
285            source_change.push_file_system_edit(FileSystemEdit::MoveFile { src: anchor, dst })
286        }
287
288        // Rename the dir if:
289        //  - Module source is in mod.rs
290        //  - Module has submodules defined in separate files
291        let dir_paths = match (is_mod_rs, has_detached_child, module.name(sema.db)) {
292            // Go up one level since the anchor is inside the dir we're trying to rename
293            (true, _, Some(mod_name)) => {
294                Some((format!("../{}", mod_name.as_str()), format!("../{}", new_name.as_str())))
295            }
296            // The anchor is on the same level as target dir
297            (false, true, Some(mod_name)) => {
298                Some((mod_name.as_str().to_owned(), new_name.as_str().to_owned()))
299            }
300            _ => None,
301        };
302
303        if let Some((src, dst)) = dir_paths {
304            let src = AnchoredPathBuf { anchor, path: src };
305            let dst = AnchoredPathBuf { anchor, path: dst };
306            source_change.push_file_system_edit(FileSystemEdit::MoveDir {
307                src,
308                src_id: anchor,
309                dst,
310            })
311        }
312    }
313
314    if let Some(src) = module.declaration_source(sema.db) {
315        let file_id = src.file_id.original_file(sema.db);
316        match src.value.name() {
317            Some(name) => {
318                if let Some(file_range) = src
319                    .with_value(name.syntax())
320                    .original_file_range_opt(sema.db)
321                    .map(TupleExt::head)
322                {
323                    let new_name = new_name.display(sema.db, edition).to_string();
324                    source_change.insert_source_edit(
325                        file_id.file_id(sema.db),
326                        TextEdit::replace(file_range.range, new_name),
327                    )
328                };
329            }
330            _ => never!("Module source node is missing a name"),
331        }
332    }
333
334    let def = Definition::Module(module);
335    let usages = def.usages(sema).all();
336    let ref_edits = usages.iter().map(|(file_id, references)| {
337        let edition = file_id.edition(sema.db);
338        (
339            file_id.file_id(sema.db),
340            source_edit_from_references(sema.db, references, def, &new_name, edition),
341        )
342    });
343    source_change.extend(ref_edits);
344
345    Ok(source_change)
346}
347
348fn rename_reference(
349    sema: &Semantics<'_, RootDatabase>,
350    def: Definition,
351    new_name: &str,
352    rename_definition: RenameDefinition,
353    edition: Edition,
354    config: &RenameConfig,
355) -> Result<SourceChange> {
356    let (mut new_name, ident_kind) = IdentifierKind::classify(edition, new_name)?;
357
358    if matches!(
359        def,
360        Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
361    ) {
362        match ident_kind {
363            IdentifierKind::Underscore => {
364                bail!(
365                    "Invalid name `{}`: not a lifetime identifier",
366                    new_name.display(sema.db, edition)
367                );
368            }
369            IdentifierKind::Ident => {
370                new_name = Name::new_lifetime(&format!("'{}", new_name.as_str()))
371            }
372            IdentifierKind::Lifetime => (),
373            IdentifierKind::LowercaseSelf => bail!(
374                "Invalid name `{}`: not a lifetime identifier",
375                new_name.display(sema.db, edition)
376            ),
377        }
378    } else {
379        match ident_kind {
380            IdentifierKind::Lifetime => {
381                cov_mark::hit!(rename_not_an_ident_ref);
382                bail!("Invalid name `{}`: not an identifier", new_name.display(sema.db, edition));
383            }
384            IdentifierKind::Ident => cov_mark::hit!(rename_non_local),
385            IdentifierKind::Underscore => (),
386            IdentifierKind::LowercaseSelf => {
387                bail!(
388                    "Invalid name `{}`: cannot rename to `self`",
389                    new_name.display(sema.db, edition)
390                );
391            }
392        }
393    }
394
395    let def = convert_to_def_in_trait(sema.db, def);
396    let usages = def.usages(sema).all();
397
398    if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
399        cov_mark::hit!(rename_underscore_multiple);
400        bail!("Cannot rename reference to `_` as it is being referenced multiple times");
401    }
402    let mut source_change = SourceChange::default();
403    source_change.extend(usages.iter().map(|(file_id, references)| {
404        let edition = file_id.edition(sema.db);
405        (
406            file_id.file_id(sema.db),
407            source_edit_from_references(sema.db, references, def, &new_name, edition),
408        )
409    }));
410
411    if let Definition::Field(field) = def {
412        rename_field_constructors(sema, field, &new_name, &mut source_change, config);
413    }
414
415    if rename_definition == RenameDefinition::Yes {
416        // This needs to come after the references edits, because we change the annotation of existing edits
417        // if a conflict is detected.
418        let (file_id, edit) =
419            source_edit_from_def(sema, config, def, &new_name, &mut source_change)?;
420        source_change.insert_source_edit(file_id, edit);
421    }
422    Ok(source_change)
423}
424
425fn rename_field_constructors(
426    sema: &Semantics<'_, RootDatabase>,
427    field: hir::Field,
428    new_name: &Name,
429    source_change: &mut SourceChange,
430    config: &RenameConfig,
431) {
432    let db = sema.db;
433    let old_name = field.name(db);
434    let adt = field.parent_def(db).adt(db);
435    adt.ty(db).iterate_assoc_items(db, |assoc_item| {
436        let ctor = assoc_item.as_function()?;
437        if ctor.has_self_param(db) {
438            return None;
439        }
440        if ctor.ret_type(db).as_adt() != Some(adt) {
441            return None;
442        }
443
444        let source = sema.source(ctor);
445        let return_values = sema
446            .fn_return_points(ctor)
447            .into_iter()
448            .filter_map(|ret| ret.value.expr())
449            .chain(source.and_then(|source| source.value.body()?.tail_expr()));
450        // FIXME: We could maybe skip ifs etc..
451
452        let get_renamed_field = |mut expr| {
453            while let ast::Expr::ParenExpr(e) = &expr {
454                expr = e.expr()?;
455            }
456            let ast::Expr::RecordExpr(expr) = expr else { return None };
457            if sema.type_of_expr(&expr.clone().into())?.original.as_adt()? != adt {
458                return None;
459            };
460            expr.record_expr_field_list()?.fields().find_map(|record_field| {
461                if record_field.name_ref().is_none()
462                    && Name::new_root(&record_field.field_name()?.text()) == old_name
463                    && let ast::Expr::PathExpr(field_name) = record_field.expr()?
464                {
465                    field_name.path()
466                } else {
467                    None
468                }
469            })
470        };
471        let renamed_fields = return_values
472            .map(get_renamed_field)
473            .map(|renamed_field| {
474                let renamed_field = renamed_field?;
475                let hir::PathResolution::Local(local) = sema.resolve_path(&renamed_field)? else {
476                    return None;
477                };
478                let range = sema.original_range_opt(renamed_field.syntax())?.range;
479                Some((range, local))
480            })
481            .collect::<Option<Vec<_>>>()?;
482
483        let edition = ctor.krate(db).edition(db);
484        let locals = renamed_fields.iter().map(|&(_, local)| local).collect::<FxHashSet<_>>();
485        let mut all_locals_source_change = SourceChange::default();
486        for local in locals {
487            let mut local_source_change = Definition::Local(local)
488                .rename(sema, new_name.as_str(), RenameDefinition::Yes, config)
489                .ok()?;
490
491            let (edit, _snippet) =
492                local_source_change.source_file_edits.values_mut().exactly_one().ok()?;
493
494            // The struct literal will have an edit `old_name -> old_name: new_name`, and we need to remove
495            // that, as we want an overlapping edit `old_name -> new_name`.
496            for &(field_range, _) in &renamed_fields {
497                edit.cancel_edits_touching(field_range);
498            }
499
500            all_locals_source_change =
501                std::mem::take(&mut all_locals_source_change).merge(local_source_change);
502        }
503        let (edit, _snippet) =
504            all_locals_source_change.source_file_edits.values_mut().exactly_one().ok()?;
505        for &(field_range, _) in &renamed_fields {
506            edit.union(TextEdit::replace(field_range, new_name.display(db, edition).to_string()))
507                .unwrap();
508        }
509
510        let file_id = *all_locals_source_change.source_file_edits.keys().exactly_one().ok()?;
511        if let Some((edit, _snippet)) = source_change.source_file_edits.get_mut(&file_id) {
512            for &(field_range, _) in &renamed_fields {
513                edit.cancel_edits_touching(field_range);
514            }
515        }
516
517        *source_change = std::mem::take(source_change).merge(all_locals_source_change);
518
519        None::<std::convert::Infallible>
520    });
521}
522
523pub fn source_edit_from_references(
524    db: &RootDatabase,
525    references: &[FileReference],
526    def: Definition,
527    new_name: &Name,
528    edition: Edition,
529) -> TextEdit {
530    let name_display = new_name.display(db, edition);
531    let mut edit = TextEdit::builder();
532    // macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far
533    let mut edited_ranges = Vec::new();
534    for &FileReference { range, ref name, .. } in references {
535        let name_range = name.text_range();
536        let has_emitted_edit = match name {
537            // if the ranges differ then the node is inside a macro call, we can't really attempt
538            // to make special rewrites like shorthand syntax and such, so just rename the node in
539            // the macro input
540            FileReferenceNode::NameRef(name_ref) if name_range == range => {
541                source_edit_from_name_ref(&mut edit, name_ref, &name_display, def)
542            }
543            FileReferenceNode::Name(name) if name_range == range => {
544                source_edit_from_name(&mut edit, name, &name_display)
545            }
546            _ => false,
547        };
548        if !has_emitted_edit && !edited_ranges.contains(&range.start()) {
549            edit.replace(range, name_display.to_string());
550            edited_ranges.push(range.start());
551        }
552    }
553
554    edit.finish()
555}
556
557fn source_edit_from_name(
558    edit: &mut TextEditBuilder,
559    name: &ast::Name,
560    new_name: &dyn Display,
561) -> bool {
562    if ast::RecordPatField::for_field_name(name).is_some()
563        && let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast)
564    {
565        cov_mark::hit!(rename_record_pat_field_name_split);
566        // Foo { ref mut field } -> Foo { new_name: ref mut field }
567        //      ^ insert `new_name: `
568
569        // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
570        // other name https://github.com/rust-lang/rust-analyzer/issues/6547
571        edit.insert(ident_pat.syntax().text_range().start(), format!("{new_name}: "));
572        return true;
573    }
574
575    false
576}
577
578fn source_edit_from_name_ref(
579    edit: &mut TextEditBuilder,
580    name_ref: &ast::NameRef,
581    new_name: &dyn Display,
582    def: Definition,
583) -> bool {
584    if name_ref.super_token().is_some() {
585        return true;
586    }
587
588    if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
589        let rcf_name_ref = record_field.name_ref();
590        let rcf_expr = record_field.expr();
591        match &(rcf_name_ref, rcf_expr.and_then(|it| expr_as_name_ref(&it))) {
592            // field: init-expr, check if we can use a field init shorthand
593            (Some(field_name), Some(init)) => {
594                let new_name = new_name.to_string();
595                if field_name == name_ref {
596                    if init.text() == new_name {
597                        cov_mark::hit!(test_rename_field_put_init_shorthand);
598                        // Foo { field: local } -> Foo { local }
599                        //       ^^^^^^^ delete this
600
601                        // same names, we can use a shorthand here instead.
602                        // we do not want to erase attributes hence this range start
603                        let s = field_name.syntax().text_range().start();
604                        let e = init.syntax().text_range().start();
605                        edit.delete(TextRange::new(s, e));
606                        return true;
607                    }
608                } else if init == name_ref && field_name.text() == new_name {
609                    cov_mark::hit!(test_rename_local_put_init_shorthand);
610                    // Foo { field: local } -> Foo { field }
611                    //            ^^^^^^^ delete this
612
613                    // same names, we can use a shorthand here instead.
614                    // we do not want to erase attributes hence this range start
615                    let s = field_name.syntax().text_range().end();
616                    let e = init.syntax().text_range().end();
617                    edit.delete(TextRange::new(s, e));
618                    return true;
619                }
620            }
621            // init shorthand
622            (None, Some(_)) if matches!(def, Definition::Field(_)) => {
623                cov_mark::hit!(test_rename_field_in_field_shorthand);
624                // Foo { field } -> Foo { new_name: field }
625                //       ^ insert `new_name: `
626                let offset = name_ref.syntax().text_range().start();
627                edit.insert(offset, format!("{new_name}: "));
628                return true;
629            }
630            (None, Some(_)) if matches!(def, Definition::Local(_)) => {
631                cov_mark::hit!(test_rename_local_in_field_shorthand);
632                // Foo { field } -> Foo { field: new_name }
633                //            ^ insert `: new_name`
634                let offset = name_ref.syntax().text_range().end();
635                edit.insert(offset, format!(": {new_name}"));
636                return true;
637            }
638            _ => (),
639        }
640    } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
641        let rcf_name_ref = record_field.name_ref();
642        let rcf_pat = record_field.pat();
643        match (rcf_name_ref, rcf_pat) {
644            // field: rename
645            (Some(field_name), Some(ast::Pat::IdentPat(pat)))
646                if field_name == *name_ref && pat.at_token().is_none() =>
647            {
648                // field name is being renamed
649                if let Some(name) = pat.name() {
650                    let new_name = new_name.to_string();
651                    if name.text() == new_name {
652                        cov_mark::hit!(test_rename_field_put_init_shorthand_pat);
653                        // Foo { field: ref mut local } -> Foo { ref mut field }
654                        //       ^^^^^^^ delete this
655                        //                      ^^^^^ replace this with `field`
656
657                        // same names, we can use a shorthand here instead/
658                        // we do not want to erase attributes hence this range start
659                        let s = field_name.syntax().text_range().start();
660                        let e = pat.syntax().text_range().start();
661                        edit.delete(TextRange::new(s, e));
662                        edit.replace(name.syntax().text_range(), new_name);
663                        return true;
664                    }
665                }
666            }
667            _ => (),
668        }
669    }
670    false
671}
672
673fn source_edit_from_def(
674    sema: &Semantics<'_, RootDatabase>,
675    config: &RenameConfig,
676    def: Definition,
677    new_name: &Name,
678    source_change: &mut SourceChange,
679) -> Result<(FileId, TextEdit)> {
680    let mut edit = TextEdit::builder();
681    if let Definition::Local(local) = def {
682        let mut file_id = None;
683
684        let conflict_annotation =
685            if config.show_conflicts && !sema.rename_conflicts(&local, new_name).is_empty() {
686                Some(
687                    source_change.insert_annotation(ChangeAnnotation {
688                        label: "This rename will change the program's meaning".to_owned(),
689                        needs_confirmation: true,
690                        description: Some(
691                            "Some variable(s) will shadow the renamed variable \
692                        or be shadowed by it if the rename is performed"
693                                .to_owned(),
694                        ),
695                    }),
696                )
697            } else {
698                None
699            };
700
701        for source in local.sources(sema.db) {
702            let source = match source.source.clone().original_ast_node_rooted(sema.db) {
703                Some(source) => source,
704                None => {
705                    match source
706                        .as_ident_pat()
707                        .and_then(|x| x.name())
708                        .and_then(|x| sema.original_range_opt(x.syntax()))
709                        .or_else(|| {
710                            source
711                                .source
712                                .syntax()
713                                .original_file_range_opt(sema.db)
714                                .map(TupleExt::head)
715                        }) {
716                        Some(FileRange { file_id: file_id2, range }) => {
717                            file_id = Some(file_id2);
718                            edit.replace(
719                                range,
720                                new_name.display(sema.db, file_id2.edition(sema.db)).to_string(),
721                            );
722                            continue;
723                        }
724                        None => {
725                            bail!("Can't rename local that is defined in a macro declaration")
726                        }
727                    }
728                }
729            };
730            file_id = Some(source.file_id);
731            if let Either::Left(pat) = source.value {
732                let name_range = pat.name().unwrap().syntax().text_range();
733
734                // special cases required for renaming fields/locals in Record patterns
735                if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
736                    if let Some(name_ref) = pat_field.name_ref() {
737                        if new_name.as_str() == name_ref.text().as_str().trim_start_matches("r#")
738                            && pat.at_token().is_none()
739                        {
740                            // Foo { field: ref mut local } -> Foo { ref mut field }
741                            //       ^^^^^^ delete this
742                            //                      ^^^^^ replace this with `field`
743                            cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
744                            edit.delete(
745                                name_ref
746                                    .syntax()
747                                    .text_range()
748                                    .cover_offset(pat.syntax().text_range().start()),
749                            );
750                            edit.replace(name_range, name_ref.text().to_string());
751                        } else {
752                            // Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
753                            // Foo { field: ref mut local } -> Foo { field: ref mut new_name }
754                            //                      ^^^^^ replace this with `new_name`
755                            edit.replace(
756                                name_range,
757                                new_name
758                                    .display(sema.db, source.file_id.edition(sema.db))
759                                    .to_string(),
760                            );
761                        }
762                    } else {
763                        // Foo { ref mut field } -> Foo { field: ref mut new_name }
764                        //   original_ast_node_rootedd: `
765                        //               ^^^^^ replace this with `new_name`
766                        edit.insert(
767                            pat.syntax().text_range().start(),
768                            format!("{}: ", pat_field.field_name().unwrap()),
769                        );
770                        edit.replace(
771                            name_range,
772                            new_name.display(sema.db, source.file_id.edition(sema.db)).to_string(),
773                        );
774                    }
775                } else {
776                    edit.replace(
777                        name_range,
778                        new_name.display(sema.db, source.file_id.edition(sema.db)).to_string(),
779                    );
780                }
781            }
782        }
783        let mut edit = edit.finish();
784
785        for (edit, _) in source_change.source_file_edits.values_mut() {
786            edit.set_annotation(conflict_annotation);
787        }
788        edit.set_annotation(conflict_annotation);
789
790        let Some(file_id) = file_id else { bail!("No file available to rename") };
791        return Ok((file_id.file_id(sema.db), edit));
792    }
793    let FileRange { file_id, range } = def
794        .range_for_rename(sema)
795        .ok_or_else(|| format_err!("No identifier available to rename"))?;
796    let (range, new_name) = match def {
797        Definition::ExternCrateDecl(decl) if decl.alias(sema.db).is_none() => (
798            TextRange::empty(range.end()),
799            format!(" as {}", new_name.display(sema.db, file_id.edition(sema.db)),),
800        ),
801        _ => (range, new_name.display(sema.db, file_id.edition(sema.db)).to_string()),
802    };
803    edit.replace(range, new_name);
804    Ok((file_id.file_id(sema.db), edit.finish()))
805}
806
807#[derive(Copy, Clone, Debug, PartialEq)]
808pub enum IdentifierKind {
809    Ident,
810    Lifetime,
811    Underscore,
812    LowercaseSelf,
813}
814
815impl IdentifierKind {
816    pub fn classify(edition: Edition, new_name: &str) -> Result<(Name, IdentifierKind)> {
817        match parser::LexedStr::single_token(edition, new_name) {
818            Some(res) => match res {
819                (SyntaxKind::IDENT, _) => Ok((Name::new_root(new_name), IdentifierKind::Ident)),
820                (T![_], _) => {
821                    Ok((Name::new_symbol_root(sym::underscore), IdentifierKind::Underscore))
822                }
823                (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
824                    Ok((Name::new_lifetime(new_name), IdentifierKind::Lifetime))
825                }
826                _ if SyntaxKind::from_keyword(new_name, edition).is_some() => match new_name {
827                    "self" => Ok((Name::new_root(new_name), IdentifierKind::LowercaseSelf)),
828                    "crate" | "super" | "Self" => {
829                        bail!("Invalid name `{}`: cannot rename to a keyword", new_name)
830                    }
831                    _ => Ok((Name::new_root(new_name), IdentifierKind::Ident)),
832                },
833                (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
834                (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
835            },
836            None => bail!("Invalid name `{}`: not an identifier", new_name),
837        }
838    }
839}