]> 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 8096dfa0ea785c4fe967ebbd6b42e643fd8440ca..a7ebba82b03eaeb54f2efd7f0f61ff864e376d9b 100644 (file)
@@ -3,6 +3,7 @@
 //! This is mostly front-end for [`ide_db::rename`], but it also includes the
 //! tests. This module also implements a couple of magic tricks, like renaming
 //! `self` and to `self` (to switch between associated function and method).
+
 use hir::{AsAssocItem, InFile, Semantics};
 use ide_db::{
     base_db::FileId,
@@ -10,7 +11,8 @@
     rename::{bail, format_err, source_edit_from_references, IdentifierKind},
     RootDatabase,
 };
-use stdx::never;
+use itertools::Itertools;
+use stdx::{always, never};
 use syntax::{ast, AstNode, SyntaxNode};
 
 use text_edit::TextEdit;
@@ -31,11 +33,33 @@ pub(crate) fn prepare_rename(
     let source_file = sema.parse(position.file_id);
     let syntax = source_file.syntax();
 
-    let def = find_definition(&sema, syntax, position)?;
-    let frange = def
-        .range_for_rename(&sema)
-        .ok_or_else(|| format_err!("No references found at position"))?;
-    Ok(RangeInfo::new(frange.range, ()))
+    let res = find_definitions(&sema, syntax, position)?
+        .map(|(name_like, def)| {
+            // ensure all ranges are valid
+
+            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
@@ -55,31 +79,30 @@ pub(crate) fn rename(
     new_name: &str,
 ) -> RenameResult<SourceChange> {
     let sema = Semantics::new(db);
-    rename_with_semantics(&sema, position, new_name)
-}
-
-pub(crate) fn rename_with_semantics(
-    sema: &Semantics<RootDatabase>,
-    position: FilePosition,
-    new_name: &str,
-) -> RenameResult<SourceChange> {
     let source_file = sema.parse(position.file_id);
     let syntax = source_file.syntax();
 
-    let def = find_definition(sema, syntax, position)?;
+    let defs = find_definitions(&sema, syntax, position)?;
 
-    if let Definition::Local(local) = def {
-        if let Some(self_param) = local.as_self_param(sema.db) {
-            cov_mark::hit!(rename_self_to_param);
-            return rename_self_to_param(sema, local, self_param, new_name);
-        }
-        if new_name == "self" {
-            cov_mark::hit!(rename_to_self);
-            return rename_to_self(sema, local);
-        }
-    }
+    let ops: RenameResult<Vec<SourceChange>> = defs
+        .map(|(_namelike, def)| {
+            if let Definition::Local(local) = def {
+                if let Some(self_param) = local.as_self_param(sema.db) {
+                    cov_mark::hit!(rename_self_to_param);
+                    return rename_self_to_param(&sema, local, self_param, new_name);
+                }
+                if new_name == "self" {
+                    cov_mark::hit!(rename_to_self);
+                    return rename_to_self(&sema, local);
+                }
+            }
+            def.rename(&sema, new_name)
+        })
+        .collect();
 
-    def.rename(sema, new_name)
+    ops?.into_iter()
+        .reduce(|acc, elem| acc.merge(elem))
+        .ok_or_else(|| format_err!("No references found at position"))
 }
 
 /// Called by the client when it is about to rename a file.
@@ -96,45 +119,86 @@ pub(crate) fn will_rename_file(
     Some(change)
 }
 
-fn find_definition(
+fn find_definitions(
     sema: &Semantics<RootDatabase>,
     syntax: &SyntaxNode,
     position: FilePosition,
-) -> RenameResult<Definition> {
-    match sema
-        .find_node_at_offset_with_descend(syntax, position.offset)
-        .ok_or_else(|| format_err!("No references found at position"))?
-    {
-        // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
-        ast::NameLike::Name(name)
-            if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
-        {
-            bail!("Renaming aliases is currently unsupported")
-        }
-        ast::NameLike::Name(name) => {
-            NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db))
-        }
-        ast::NameLike::NameRef(name_ref) => {
-            if let Some(def) =
-                NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db))
-            {
-                // 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()) {
-                    bail!("Renaming aliases is currently unsupported");
+) -> RenameResult<impl Iterator<Item = (ast::NameLike, Definition)>> {
+    let symbols = sema
+        .find_nodes_at_offset_with_descend::<ast::NameLike>(syntax, position.offset)
+        .map(|name_like| {
+            let res = match &name_like {
+                // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
+                ast::NameLike::Name(name)
+                    if name
+                        .syntax()
+                        .parent()
+                        .map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
+                {
+                    bail!("Renaming aliases is currently unsupported")
+                }
+                ast::NameLike::Name(name) => NameClass::classify(sema, name)
+                    .map(|class| match class {
+                        NameClass::Definition(it) | NameClass::ConstReference(it) => it,
+                        NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
+                            Definition::Local(local_def)
+                        }
+                    })
+                    .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)
+                            }
+                        })
+                        .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))
+                            }
+                        })
                 }
-                Some(def)
+                ast::NameLike::Lifetime(lifetime) => {
+                    NameRefClass::classify_lifetime(sema, lifetime)
+                        .and_then(|class| match class {
+                            NameRefClass::Definition(def) => Some(def),
+                            _ => None,
+                        })
+                        .or_else(|| {
+                            NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {
+                                NameClass::Definition(it) => Some(it),
+                                _ => None,
+                            })
+                        })
+                        .map(|def| (name_like, def))
+                        .ok_or_else(|| format_err!("No references found at position"))
+                }
+            };
+            res
+        });
+
+    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 {
-                None
+                // remove duplicates, comparing `Definition`s
+                Ok(v.into_iter().unique_by(|t| t.1))
             }
         }
-        ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
-            .map(|class| NameRefClass::referenced(class, sema.db))
-            .or_else(|| {
-                NameClass::classify_lifetime(sema, &lifetime)
-                    .map(|it| it.referenced_or_defined(sema.db))
-            }),
+        Err(e) => Err(e),
     }
-    .ok_or_else(|| format_err!("No references found at position"))
 }
 
 fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {
@@ -147,7 +211,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
         _ => bail!("Cannot rename local to self outside of function"),
     };
 
-    if let Some(_) = fn_def.self_param(sema.db) {
+    if fn_def.self_param(sema.db).is_some() {
         bail!("Method already has a self parameter");
     }
 
@@ -265,6 +329,7 @@ mod tests {
 
     use super::{RangeInfo, RenameError};
 
+    #[track_caller]
     fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
         let ra_fixture_after = &trim_indent(ra_fixture_after);
         let (analysis, position) = fixture::position(ra_fixture_before);
@@ -295,7 +360,6 @@ fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
                         .skip("error:".len())
                         .collect::<String>();
                     assert_eq!(error_message.trim(), err.to_string());
-                    return;
                 } else {
                     panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
                 }
@@ -328,7 +392,7 @@ fn check_prepare(ra_fixture: &str, expect: Expect) {
     fn test_prepare_rename_namelikes() {
         check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);
         check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]);
-        check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"3..7: name"#]]);
+        check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);
     }
 
     #[test]
@@ -506,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(
@@ -608,22 +702,22 @@ fn test_rename_for_mut_param() {
     #[test]
     fn test_rename_struct_field() {
         check(
-            "j",
+            "foo",
             r#"
-struct Foo { i$0: i32 }
+struct Foo { field$0: i32 }
 
 impl Foo {
     fn new(i: i32) -> Self {
-        Self { i: i }
+        Self { field: i }
     }
 }
 "#,
             r#"
-struct Foo { j: i32 }
+struct Foo { foo: i32 }
 
 impl Foo {
     fn new(i: i32) -> Self {
-        Self { j: i }
+        Self { foo: i }
     }
 }
 "#,
@@ -634,22 +728,22 @@ fn new(i: i32) -> Self {
     fn test_rename_field_in_field_shorthand() {
         cov_mark::check!(test_rename_field_in_field_shorthand);
         check(
-            "j",
+            "field",
             r#"
-struct Foo { i$0: i32 }
+struct Foo { foo$0: i32 }
 
 impl Foo {
-    fn new(i: i32) -> Self {
-        Self { i }
+    fn new(foo: i32) -> Self {
+        Self { foo }
     }
 }
 "#,
             r#"
-struct Foo { j: i32 }
+struct Foo { field: i32 }
 
 impl Foo {
-    fn new(i: i32) -> Self {
-        Self { j: i }
+    fn new(foo: i32) -> Self {
+        Self { field: foo }
     }
 }
 "#,
@@ -936,12 +1030,18 @@ fn test_module_rename_in_path() {
         check(
             "baz",
             r#"
-mod $0foo { pub fn bar() {} }
+mod $0foo {
+    pub use self::bar as qux;
+    pub fn bar() {}
+}
 
 fn main() { foo::bar(); }
 "#,
             r#"
-mod baz { pub fn bar() {} }
+mod baz {
+    pub use self::bar as qux;
+    pub fn bar() {}
+}
 
 fn main() { baz::bar(); }
 "#,
@@ -1317,9 +1417,71 @@ fn foo(foo: Foo) {
 struct Foo { baz: i32 }
 
 fn foo(foo: Foo) {
-    let Foo { ref baz @ qux } = foo;
+    let Foo { baz: ref baz @ qux } = foo;
+    let _ = qux;
+}
+"#,
+        );
+        check(
+            "baz",
+            r#"
+struct Foo { i$0: i32 }
+
+fn foo(foo: Foo) {
+    let Foo { i: ref baz } = foo;
+    let _ = qux;
+}
+"#,
+            r#"
+struct Foo { baz: i32 }
+
+fn foo(foo: Foo) {
+    let Foo { ref baz } = foo;
+    let _ = qux;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_struct_local_pat_into_shorthand() {
+        cov_mark::check!(test_rename_local_put_init_shorthand_pat);
+        check(
+            "field",
+            r#"
+struct Foo { field: i32 }
+
+fn foo(foo: Foo) {
+    let Foo { field: qux$0 } = foo;
     let _ = qux;
 }
+"#,
+            r#"
+struct Foo { field: i32 }
+
+fn foo(foo: Foo) {
+    let Foo { field } = foo;
+    let _ = field;
+}
+"#,
+        );
+        check(
+            "field",
+            r#"
+struct Foo { field: i32 }
+
+fn foo(foo: Foo) {
+    let Foo { field: x @ qux$0 } = foo;
+    let _ = qux;
+}
+"#,
+            r#"
+struct Foo { field: i32 }
+
+fn foo(foo: Foo) {
+    let Foo { field: x @ field } = foo;
+    let _ = field;
+}
 "#,
         );
     }
@@ -1375,7 +1537,7 @@ struct Foo {
     i: i32
 }
 
-fn foo(Foo { i }: foo) -> i32 {
+fn foo(Foo { i }: Foo) -> i32 {
     i$0
 }
 "#,
@@ -1384,7 +1546,7 @@ struct Foo {
     i: i32
 }
 
-fn foo(Foo { i: bar }: foo) -> i32 {
+fn foo(Foo { i: bar }: Foo) -> i32 {
     bar
 }
 "#,
@@ -1393,6 +1555,7 @@ fn foo(Foo { i: bar }: foo) -> i32 {
 
     #[test]
     fn test_struct_field_complex_ident_pat() {
+        cov_mark::check!(rename_record_pat_field_name_split);
         check(
             "baz",
             r#"
@@ -1718,6 +1881,25 @@ fn bar(&self) {
         );
     }
 
+    #[test]
+    fn test_rename_trait_method_prefix_of_second() {
+        check(
+            "qux",
+            r#"
+trait Foo {
+    fn foo$0() {}
+    fn foobar() {}
+}
+"#,
+            r#"
+trait Foo {
+    fn qux() {}
+    fn foobar() {}
+}
+"#,
+        );
+    }
+
     #[test]
     fn test_rename_trait_const() {
         let res = r"
@@ -1771,19 +1953,81 @@ impl Foo for () {
     }
 
     #[test]
-    fn macros_are_broken_lol() {
-        cov_mark::check!(macros_are_broken_lol);
+    fn defs_from_macros_arent_renamed() {
         check(
             "lol",
             r#"
 macro_rules! m { () => { fn f() {} } }
 m!();
 fn main() { f$0()  }
+"#,
+            "error: No identifier available to rename",
+        )
+    }
+
+    #[test]
+    fn attributed_item() {
+        check(
+            "function",
+            r#"
+//- proc_macros: identity
+
+#[proc_macros::identity]
+fn func$0() {
+    func();
+}
 "#,
             r#"
-macro_rules! m { () => { fn f() {} } }
-lol
-fn main() { lol()  }
+
+#[proc_macros::identity]
+fn function() {
+    function();
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn in_macro_multi_mapping() {
+        check(
+            "a",
+            r#"
+fn foo() {
+    macro_rules! match_ast2 {
+        ($node:ident {
+            $( $res:expr, )*
+        }) => {{
+            $( if $node { $res } else )*
+            { loop {} }
+        }};
+    }
+    let $0d = 3;
+    match_ast2! {
+        d {
+            d,
+            d,
+        }
+    };
+}
+"#,
+            r#"
+fn foo() {
+    macro_rules! match_ast2 {
+        ($node:ident {
+            $( $res:expr, )*
+        }) => {{
+            $( if $node { $res } else )*
+            { loop {} }
+        }};
+    }
+    let a = 3;
+    match_ast2! {
+        a {
+            a,
+            a,
+        }
+    };
+}
 "#,
         )
     }