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, 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        // self.krate() returns None if
86        // self is a built-in attr, built-in type or tool module.
87        // it is not allowed for these defs to be renamed.
88        // cases where self.krate() is None is handled below.
89        let edition = if let Some(krate) = self.krate(sema.db) {
90            // Can we not rename non-local items?
91            // Then bail if non-local
92            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    /// Textual range of the identifier which will change when renaming this
120    /// `Definition`. Note that builtin types can't be
121    /// renamed and extern crate names will report its range, though a rename will introduce
122    /// an alias instead.
123    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            // FIXME: This should be doable in theory
223            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        // Module exists in a named file
269        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        // Rename the dir if:
276        //  - Module source is in mod.rs
277        //  - Module has submodules defined in separate files
278        let dir_paths = match (is_mod_rs, has_detached_child, module.name(sema.db)) {
279            // Go up one level since the anchor is inside the dir we're trying to rename
280            (true, _, Some(mod_name)) => {
281                Some((format!("../{}", mod_name.as_str()), format!("../{}", new_name.as_str())))
282            }
283            // The anchor is on the same level as target dir
284            (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        // This needs to come after the references edits, because we change the annotation of existing edits
398        // if a conflict is detected.
399        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    // macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far
415    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            // if the ranges differ then the node is inside a macro call, we can't really attempt
420            // to make special rewrites like shorthand syntax and such, so just rename the node in
421            // the macro input
422            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        // Foo { ref mut field } -> Foo { new_name: ref mut field }
449        //      ^ insert `new_name: `
450
451        // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
452        // other name https://github.com/rust-lang/rust-analyzer/issues/6547
453        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            // field: init-expr, check if we can use a field init shorthand
475            (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                        // Foo { field: local } -> Foo { local }
481                        //       ^^^^^^^ delete this
482
483                        // same names, we can use a shorthand here instead.
484                        // we do not want to erase attributes hence this range start
485                        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                    // Foo { field: local } -> Foo { field }
493                    //            ^^^^^^^ delete this
494
495                    // same names, we can use a shorthand here instead.
496                    // we do not want to erase attributes hence this range start
497                    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            // init shorthand
504            (None, Some(_)) if matches!(def, Definition::Field(_)) => {
505                cov_mark::hit!(test_rename_field_in_field_shorthand);
506                // Foo { field } -> Foo { new_name: field }
507                //       ^ insert `new_name: `
508                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                // Foo { field } -> Foo { field: new_name }
515                //            ^ insert `: new_name`
516                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            // field: rename
527            (Some(field_name), Some(ast::Pat::IdentPat(pat)))
528                if field_name == *name_ref && pat.at_token().is_none() =>
529            {
530                // field name is being renamed
531                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                        // Foo { field: ref mut local } -> Foo { ref mut field }
536                        //       ^^^^^^^ delete this
537                        //                      ^^^^^ replace this with `field`
538
539                        // same names, we can use a shorthand here instead/
540                        // we do not want to erase attributes hence this range start
541                        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                // special cases required for renaming fields/locals in Record patterns
607                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                            // Foo { field: ref mut local } -> Foo { ref mut field }
613                            //       ^^^^^^ delete this
614                            //                      ^^^^^ replace this with `field`
615                            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                            // Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
625                            // Foo { field: ref mut local } -> Foo { field: ref mut new_name }
626                            //                      ^^^^^ replace this with `new_name`
627                            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                        // Foo { ref mut field } -> Foo { field: ref mut new_name }
636                        //   original_ast_node_rootedd: `
637                        //               ^^^^^ replace this with `new_name`
638                        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}