]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
Drop generic args in path before insert use
[rust.git] / crates / ide_assists / src / handlers / replace_qualified_name_with_use.rs
index 7aad4a44b6af6ab6906d960d8d0e0ae3457b3e1d..71c674a8dd745fdad9641239d8290f6941c9c90c 100644 (file)
@@ -3,7 +3,10 @@
     insert_use::{insert_use, ImportScope},
     mod_path_to_ast,
 };
-use syntax::{ast, match_ast, ted, AstNode, SyntaxNode};
+use syntax::{
+    ast::{self, make},
+    match_ast, ted, AstNode, SyntaxNode,
+};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
 
@@ -38,20 +41,37 @@ pub(crate) fn replace_qualified_name_with_use(
         return None;
     }
 
-    let res = ctx.sema.resolve_path(&path)?;
-    let def: hir::ItemInNs = match res {
-        hir::PathResolution::Def(def) if def.as_assoc_item(ctx.sema.db).is_none() => def.into(),
-        hir::PathResolution::Macro(mac) => mac.into(),
+    // only offer replacement for non assoc items
+    match ctx.sema.resolve_path(&path)? {
+        hir::PathResolution::Def(def) if def.as_assoc_item(ctx.sema.db).is_none() => (),
+        hir::PathResolution::Macro(_) => (),
+        _ => return None,
+    }
+    // then search for an import for the first path segment of what we want to replace
+    // that way it is less likely that we import the item from a different location due re-exports
+    let module = match ctx.sema.resolve_path(&path.first_qualifier_or_self())? {
+        hir::PathResolution::Def(module @ hir::ModuleDef::Module(_)) => module,
         _ => return None,
     };
 
+    let starts_with_name_ref = !matches!(
+        path.first_segment().and_then(|it| it.kind()),
+        Some(
+            ast::PathSegmentKind::CrateKw
+                | ast::PathSegmentKind::SuperKw
+                | ast::PathSegmentKind::SelfKw
+        )
+    );
+    let path_to_qualifier = starts_with_name_ref
+        .then(|| {
+            ctx.sema.scope(path.syntax()).module().and_then(|m| {
+                m.find_use_path_prefixed(ctx.sema.db, module, ctx.config.insert_use.prefix_kind)
+            })
+        })
+        .flatten();
+
+    let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
     let target = path.syntax().text_range();
-    let scope = ImportScope::find_insert_use_container_with_macros(path.syntax(), &ctx.sema)?;
-    let mod_path = ctx.sema.scope(path.syntax()).module()?.find_use_path_prefixed(
-        ctx.sema.db,
-        def,
-        ctx.config.insert_use.prefix_kind,
-    )?;
     acc.add(
         AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
         "Replace qualified path with use",
@@ -64,24 +84,39 @@ pub(crate) fn replace_qualified_name_with_use(
                 ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
                 ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
             };
-            let path = mod_path_to_ast(&mod_path);
-            shorten_paths(scope.as_syntax_node(), &path.clone_for_update());
+            shorten_paths(scope.as_syntax_node(), &path);
+            let path = drop_generic_args(&path);
+            // stick the found import in front of the to be replaced path
+            let path = match path_to_qualifier.and_then(|it| mod_path_to_ast(&it).qualifier()) {
+                Some(qualifier) => make::path_concat(qualifier, path),
+                None => path,
+            };
             insert_use(&scope, path, &ctx.config.insert_use);
         },
     )
 }
 
-/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
+fn drop_generic_args(path: &ast::Path) -> ast::Path {
+    let path = path.clone_for_update();
+    if let Some(segment) = path.segment() {
+        if let Some(generic_args) = segment.generic_arg_list() {
+            ted::remove(generic_args.syntax());
+        }
+    }
+    path
+}
+
+/// Mutates `node` to shorten `path` in all descendants of `node`.
 fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
     for child in node.children() {
         match_ast! {
             match child {
                 // Don't modify `use` items, as this can break the `use` item when injecting a new
                 // import into the use tree.
-                ast::Use(_it) => continue,
+                ast::Use(_) => continue,
                 // Don't descend into submodules, they don't have the same `use` items in scope.
                 // FIXME: This isn't true due to `super::*` imports?
-                ast::Module(_it) => continue,
+                ast::Module(_) => continue,
                 ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
                     shorten_paths(p.syntax(), path);
                 },
@@ -117,10 +152,7 @@ fn path_eq_no_generics(lhs: ast::Path, rhs: ast::Path) -> bool {
                     && lhs
                         .name_ref()
                         .zip(rhs.name_ref())
-                        .map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) =>
-            {
-                ()
-            }
+                        .map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) => {}
             _ => return false,
         }
 
@@ -195,7 +227,7 @@ fn test_replace_add_use_no_anchor_middle_segment() {
     ",
         );
     }
-    #[test]
+
     #[test]
     fn dont_import_trivial_paths() {
         cov_mark::check!(dont_import_trivial_paths);
@@ -278,7 +310,7 @@ fn main() {
     ",
             r"
 mod std { pub mod fmt { pub trait Display {} } }
-use std::fmt::{self, Display};
+use std::fmt::{Display, self};
 
 fn main() {
     fmt;
@@ -300,6 +332,105 @@ pub fn foo() {}
 fn main() {
     Foo::foo$0();
 }
+",
+        );
+    }
+
+    #[test]
+    fn replace_reuses_path_qualifier() {
+        check_assist(
+            replace_qualified_name_with_use,
+            r"
+pub mod foo {
+    pub struct Foo;
+}
+
+mod bar {
+    pub use super::foo::Foo as Bar;
+}
+
+fn main() {
+    foo::Foo$0;
+}
+",
+            r"
+use foo::Foo;
+
+pub mod foo {
+    pub struct Foo;
+}
+
+mod bar {
+    pub use super::foo::Foo as Bar;
+}
+
+fn main() {
+    Foo;
+}
+",
+        );
+    }
+
+    #[test]
+    fn replace_does_not_always_try_to_replace_by_full_item_path() {
+        check_assist(
+            replace_qualified_name_with_use,
+            r"
+use std::mem;
+
+mod std {
+    pub mod mem {
+        pub fn drop<T>(_: T) {}
+    }
+}
+
+fn main() {
+    mem::drop$0(0);
+}
+",
+            r"
+use std::mem::{self, drop};
+
+mod std {
+    pub mod mem {
+        pub fn drop<T>(_: T) {}
+    }
+}
+
+fn main() {
+    drop(0);
+}
+",
+        );
+    }
+
+    #[test]
+    fn replace_should_drop_generic_args_in_use() {
+        check_assist(
+            replace_qualified_name_with_use,
+            r"
+mod std {
+    pub mod mem {
+        pub fn drop<T>(_: T) {}
+    }
+}
+
+fn main() {
+    std::mem::drop::<usize>$0(0);
+}
+",
+            r"
+use std::mem::drop;
+
+mod std {
+    pub mod mem {
+        pub fn drop<T>(_: T) {}
+    }
+}
+
+fn main() {
+    drop::<usize>(0);
+}
 ",
         );
     }