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, 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 let edition = if let Some(krate) = self.krate(sema.db) {
98 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 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 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 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 let dir_paths = match (is_mod_rs, has_detached_child, module.name(sema.db)) {
292 (true, _, Some(mod_name)) => {
294 Some((format!("../{}", mod_name.as_str()), format!("../{}", new_name.as_str())))
295 }
296 (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 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 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 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 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 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 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 (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 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 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 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
623 cov_mark::hit!(test_rename_field_in_field_shorthand);
624 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 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 (Some(field_name), Some(ast::Pat::IdentPat(pat)))
646 if field_name == *name_ref && pat.at_token().is_none() =>
647 {
648 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 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 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 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 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 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}