]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide/src/rename.rs
Replace more Name::to_string usages with Name::to_smol_str
[rust.git] / crates / ide / src / rename.rs
index bb23699cad4df873fde806bc534006a94d73473b..a7ebba82b03eaeb54f2efd7f0f61ff864e376d9b 100644 (file)
@@ -33,17 +33,33 @@ pub(crate) fn prepare_rename(
     let source_file = sema.parse(position.file_id);
     let syntax = source_file.syntax();
 
-    let mut defs = find_definitions(&sema, syntax, position)?;
+    let res = find_definitions(&sema, syntax, position)?
+        .map(|(name_like, def)| {
+            // ensure all ranges are valid
 
-    // TODO:
-    // - `find_definitions` is implemented so that it returns a non-empty vec
-    //   in the `Ok` case. But that's not expressed by the type signature, hence `unwrap()`
-    //   here which ... wart.
-    // - is "just take the first `name_like`" correct? If not, what do?
-    let (name_like, _def) = defs.next().unwrap();
-    let frange = sema.original_range(name_like.syntax());
-    always!(frange.range.contains_inclusive(position.offset) && frange.file_id == position.file_id);
-    Ok(RangeInfo::new(frange.range, ()))
+            if def.range_for_rename(&sema).is_none() {
+                bail!("No references found at position")
+            }
+            let frange = sema.original_range(name_like.syntax());
+
+            always!(
+                frange.range.contains_inclusive(position.offset)
+                    && frange.file_id == position.file_id
+            );
+            Ok(frange.range)
+        })
+        .reduce(|acc, cur| match (acc, cur) {
+            // ensure all ranges are the same
+            (Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),
+            (Err(e), _) => Err(e),
+            _ => bail!("inconsistent text range"),
+        });
+
+    match res {
+        // ensure at least one definition was found
+        Some(res) => res.map(|range| RangeInfo::new(range, ())),
+        None => bail!("No references found at position"),
+    }
 }
 
 // Feature: Rename
@@ -83,6 +99,7 @@ pub(crate) fn rename(
             def.rename(&sema, new_name)
         })
         .collect();
+
     ops?.into_iter()
         .reduce(|acc, elem| acc.merge(elem))
         .ok_or_else(|| format_err!("No references found at position"))
@@ -129,22 +146,27 @@ fn find_definitions(
                     })
                     .map(|def| (name_like.clone(), def))
                     .ok_or_else(|| format_err!("No references found at position")),
-                ast::NameLike::NameRef(name_ref) => NameRefClass::classify(sema, name_ref)
-                    .map(|class| match class {
-                        NameRefClass::Definition(def) => def,
-                        NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
-                            Definition::Local(local_ref)
-                        }
-                    })
-                    .and_then(|def| {
-                        // if the name differs from the definitions name it has to be an alias
-                        if def.name(sema.db).map_or(false, |it| it.to_string() != name_ref.text()) {
-                            None
-                        } else {
-                            Some((name_like.clone(), def))
-                        }
-                    })
-                    .ok_or_else(|| format_err!("Renaming aliases is currently unsupported")),
+                ast::NameLike::NameRef(name_ref) => {
+                    NameRefClass::classify(sema, name_ref)
+                        .map(|class| match class {
+                            NameRefClass::Definition(def) => def,
+                            NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
+                                Definition::Local(local_ref)
+                            }
+                        })
+                        .ok_or_else(|| format_err!("No references found at position"))
+                        .and_then(|def| {
+                            // if the name differs from the definitions name it has to be an alias
+                            if def
+                                .name(sema.db)
+                                .map_or(false, |it| it.to_smol_str() != name_ref.text().as_str())
+                            {
+                                Err(format_err!("Renaming aliases is currently unsupported"))
+                            } else {
+                                Ok((name_like.clone(), def))
+                            }
+                        })
+                }
                 ast::NameLike::Lifetime(lifetime) => {
                     NameRefClass::classify_lifetime(sema, lifetime)
                         .and_then(|class| match class {
@@ -164,15 +186,14 @@ fn find_definitions(
             res
         });
 
-    // TODO avoid collect() somehow?
-    let v: RenameResult<Vec<(ast::NameLike, Definition)>> = symbols.collect();
-    match v {
-        // remove duplicates
-        // TODO is "unique by `Definition`" correct?
+    let res: RenameResult<Vec<_>> = symbols.collect();
+    match res {
         Ok(v) => {
             if v.is_empty() {
+                // FIXME: some semantic duplication between "empty vec" and "Err()"
                 Err(format_err!("No references found at position"))
             } else {
+                // remove duplicates, comparing `Definition`s
                 Ok(v.into_iter().unique_by(|t| t.1))
             }
         }
@@ -549,6 +570,36 @@ fn test_rename_unresolved_reference() {
         );
     }
 
+    #[test]
+    fn test_rename_macro_multiple_occurrences() {
+        check(
+            "Baaah",
+            r#"macro_rules! foo {
+    ($ident:ident) => {
+        const $ident: () = ();
+        struct $ident {}
+    };
+}
+
+foo!($0Foo);
+const _: () = Foo;
+const _: Foo = Foo {};
+    "#,
+            r#"
+macro_rules! foo {
+    ($ident:ident) => {
+        const $ident: () = ();
+        struct $ident {}
+    };
+}
+
+foo!(Baaah);
+const _: () = Baaah;
+const _: Baaah = Baaah {};
+    "#,
+        )
+    }
+
     #[test]
     fn test_rename_for_macro_args() {
         check(