]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
Use mutable syntax trees in `merge_imports`, `split_imports`
[rust.git] / crates / ide_assists / src / handlers / replace_qualified_name_with_use.rs
index 39f5eb4ff48ac4195945ab4c21b7210f31245f1a..6b196681cab0936d2e0491803fa111ad75e30df8 100644 (file)
@@ -1,5 +1,12 @@
-use ide_db::helpers::insert_use::{insert_use, ImportScope};
-use syntax::{ast, match_ast, ted, AstNode, SyntaxNode};
+use hir::AsAssocItem;
+use ide_db::helpers::{
+    insert_use::{insert_use, ImportScope},
+    mod_path_to_ast,
+};
+use syntax::{
+    ast::{self, make},
+    match_ast, ted, AstNode, SyntaxNode,
+};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
 
 // Adds a use statement for a given fully-qualified name.
 //
 // ```
+// # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
 // fn process(map: std::collections::$0HashMap<String, String>) {}
 // ```
 // ->
 // ```
 // use std::collections::HashMap;
 //
+// # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
 // fn process(map: HashMap<String, String>) {}
 // ```
 pub(crate) fn replace_qualified_name_with_use(
@@ -22,17 +31,47 @@ pub(crate) fn replace_qualified_name_with_use(
 ) -> Option<()> {
     let path: ast::Path = ctx.find_node_at_offset()?;
     // We don't want to mess with use statements
-    if path.syntax().ancestors().find_map(ast::Use::cast).is_some() {
+    if path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() {
+        cov_mark::hit!(not_applicable_in_use);
         return None;
     }
+
     if path.qualifier().is_none() {
         cov_mark::hit!(dont_import_trivial_paths);
         return None;
     }
 
+    // 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 syntax = scope.as_syntax_node();
     acc.add(
         AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
         "Replace qualified path with use",
@@ -40,11 +79,18 @@ pub(crate) fn replace_qualified_name_with_use(
         |builder| {
             // Now that we've brought the name into scope, re-qualify all paths that could be
             // affected (that is, all paths inside the node we added the `use` to).
-            let syntax = builder.make_syntax_mut(syntax.clone());
-            if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
-                shorten_paths(&syntax, &path.clone_for_update());
-                insert_use(import_scope, path, ctx.config.insert_use);
-            }
+            let scope = match scope {
+                ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
+                ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
+                ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
+            };
+            // 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,
+            };
+            shorten_paths(scope.as_syntax_node(), &path.clone_for_update());
+            insert_use(&scope, path, &ctx.config.insert_use);
         },
     )
 }
@@ -58,6 +104,7 @@ fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
                 // import into the use tree.
                 ast::Use(_it) => 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::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
                     shorten_paths(p.syntax(), path);
@@ -69,7 +116,7 @@ fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
 }
 
 fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
-    if !path_eq(path.clone(), target) {
+    if !path_eq_no_generics(path.clone(), target) {
         return None;
     }
 
@@ -84,12 +131,17 @@ fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
     Some(())
 }
 
-fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool {
+fn path_eq_no_generics(lhs: ast::Path, rhs: ast::Path) -> bool {
     let mut lhs_curr = lhs;
     let mut rhs_curr = rhs;
     loop {
-        match (lhs_curr.segment(), rhs_curr.segment()) {
-            (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
+        match lhs_curr.segment().zip(rhs_curr.segment()) {
+            Some((lhs, rhs))
+                if lhs.coloncolon_token().is_some() == rhs.coloncolon_token().is_some()
+                    && lhs
+                        .name_ref()
+                        .zip(rhs.name_ref())
+                        .map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) => {}
             _ => return false,
         }
 
@@ -114,12 +166,16 @@ mod tests {
     fn test_replace_already_imported() {
         check_assist(
             replace_qualified_name_with_use,
-            r"use std::fs;
+            r"
+mod std { pub mod fs { pub struct Path; } }
+use std::fs;
 
 fn main() {
     std::f$0s::Path
 }",
-            r"use std::fs;
+            r"
+mod std { pub mod fs { pub struct Path; } }
+use std::fs;
 
 fn main() {
     fs::Path
@@ -132,387 +188,45 @@ fn test_replace_add_use_no_anchor() {
         check_assist(
             replace_qualified_name_with_use,
             r"
-std::fmt::Debug$0
-    ",
-            r"
-use std::fmt::Debug;
-
-Debug
-    ",
-        );
-    }
-
-    #[test]
-    fn test_replace_add_use_no_anchor_with_item_below() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-std::fmt::Debug$0
-
-fn main() {
-}
-    ",
-            r"
-use std::fmt::Debug;
-
-Debug
-
-fn main() {
-}
-    ",
-        );
-    }
-
-    #[test]
-    fn test_replace_add_use_no_anchor_with_item_above() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-fn main() {
-}
-
-std::fmt::Debug$0
-    ",
-            r"
-use std::fmt::Debug;
-
-fn main() {
-}
-
-Debug
-    ",
-        );
-    }
-
-    #[test]
-    fn test_replace_add_use_no_anchor_2seg() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-std::fmt$0::Debug
-    ",
-            r"
-use std::fmt;
-
-fmt::Debug
-    ",
-        );
-    }
-
-    #[test]
-    fn test_replace_add_use() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use stdx;
-
-impl std::fmt::Debug$0 for Foo {
-}
+mod std { pub mod fs { pub struct Path; } }
+std::fs::Path$0
     ",
             r"
-use std::fmt::Debug;
-
-use stdx;
+use std::fs::Path;
 
-impl Debug for Foo {
-}
+mod std { pub mod fs { pub struct Path; } }
+Path
     ",
         );
     }
 
     #[test]
-    fn test_replace_file_use_other_anchor() {
+    fn test_replace_add_use_no_anchor_middle_segment() {
         check_assist(
             replace_qualified_name_with_use,
             r"
-impl std::fmt::Debug$0 for Foo {
-}
+mod std { pub mod fs { pub struct Path; } }
+std::fs$0::Path
     ",
             r"
-use std::fmt::Debug;
+use std::fs;
 
-impl Debug for Foo {
-}
+mod std { pub mod fs { pub struct Path; } }
+fs::Path
     ",
         );
     }
-
-    #[test]
-    fn test_replace_add_use_other_anchor_indent() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-    impl std::fmt::Debug$0 for Foo {
-    }
-    ",
-            r"
-    use std::fmt::Debug;
-
-    impl Debug for Foo {
-    }
-    ",
-        );
-    }
-
-    #[test]
-    fn test_replace_split_different() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt;
-
-impl std::io$0 for Foo {
-}
-    ",
-            r"
-use std::{fmt, io};
-
-impl io for Foo {
-}
-    ",
-        );
-    }
-
-    #[test]
-    fn test_replace_split_self_for_use() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt;
-
-impl std::fmt::Debug$0 for Foo {
-}
-    ",
-            r"
-use std::fmt::{self, Debug};
-
-impl Debug for Foo {
-}
-    ",
-        );
-    }
-
-    #[test]
-    fn test_replace_split_self_for_target() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt::Debug;
-
-impl std::fmt$0 for Foo {
-}
-    ",
-            r"
-use std::fmt::{self, Debug};
-
-impl fmt for Foo {
-}
-    ",
-        );
-    }
-
-    #[test]
-    fn test_replace_add_to_nested_self_nested() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt::{Debug, nested::{Display}};
-
-impl std::fmt::nested$0 for Foo {
-}
-",
-            r"
-use std::fmt::{Debug, nested::{self, Display}};
-
-impl nested for Foo {
-}
-",
-        );
-    }
-
-    #[test]
-    fn test_replace_add_to_nested_self_already_included() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt::{Debug, nested::{self, Display}};
-
-impl std::fmt::nested$0 for Foo {
-}
-",
-            r"
-use std::fmt::{Debug, nested::{self, Display}};
-
-impl nested for Foo {
-}
-",
-        );
-    }
-
-    #[test]
-    fn test_replace_add_to_nested_nested() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt::{Debug, nested::{Display}};
-
-impl std::fmt::nested::Debug$0 for Foo {
-}
-",
-            r"
-use std::fmt::{Debug, nested::{Debug, Display}};
-
-impl Debug for Foo {
-}
-",
-        );
-    }
-
-    #[test]
-    fn test_replace_split_common_target_longer() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt::Debug;
-
-impl std::fmt::nested::Display$0 for Foo {
-}
-",
-            r"
-use std::fmt::{Debug, nested::Display};
-
-impl Display for Foo {
-}
-",
-        );
-    }
-
-    #[test]
-    fn test_replace_split_common_use_longer() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt::nested::Debug;
-
-impl std::fmt::Display$0 for Foo {
-}
-",
-            r"
-use std::fmt::{Display, nested::Debug};
-
-impl Display for Foo {
-}
-",
-        );
-    }
-
-    #[test]
-    fn test_replace_use_nested_import() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use crate::{
-    ty::{Substs, Ty},
-    AssocItem,
-};
-
-fn foo() { crate::ty::lower$0::trait_env() }
-",
-            r"
-use crate::{AssocItem, ty::{Substs, Ty, lower}};
-
-fn foo() { lower::trait_env() }
-",
-        );
-    }
-
     #[test]
-    fn test_replace_alias() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt as foo;
-
-impl foo::Debug$0 for Foo {
-}
-",
-            r"
-use std::fmt as foo;
-
-use foo::Debug;
-
-impl Debug for Foo {
-}
-",
-        );
-    }
-
     #[test]
     fn dont_import_trivial_paths() {
         cov_mark::check!(dont_import_trivial_paths);
-        check_assist_not_applicable(
-            replace_qualified_name_with_use,
-            r"
-impl foo$0 for Foo {
-}
-",
-        );
+        check_assist_not_applicable(replace_qualified_name_with_use, r"impl foo$0 for () {}");
     }
 
     #[test]
     fn test_replace_not_applicable_in_use() {
-        check_assist_not_applicable(
-            replace_qualified_name_with_use,
-            r"
-use std::fmt$0;
-",
-        );
-    }
-
-    #[test]
-    fn test_replace_add_use_no_anchor_in_mod_mod() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-mod foo {
-    mod bar {
-        std::fmt::Debug$0
-    }
-}
-    ",
-            r"
-mod foo {
-    mod bar {
-        use std::fmt::Debug;
-
-        Debug
-    }
-}
-    ",
-        );
-    }
-
-    #[test]
-    fn inserts_imports_after_inner_attributes() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-#![allow(dead_code)]
-
-fn main() {
-    std::fmt::Debug$0
-}
-    ",
-            r"
-#![allow(dead_code)]
-
-use std::fmt::Debug;
-
-fn main() {
-    Debug
-}
-    ",
-        );
+        cov_mark::check!(not_applicable_in_use);
+        check_assist_not_applicable(replace_qualified_name_with_use, r"use std::fmt$0;");
     }
 
     #[test]
@@ -520,6 +234,7 @@ fn replaces_all_affected_paths() {
         check_assist(
             replace_qualified_name_with_use,
             r"
+mod std { pub mod fmt { pub trait Debug {} } }
 fn main() {
     std::fmt::Debug$0;
     let x: std::fmt::Debug = std::fmt::Debug;
@@ -528,6 +243,7 @@ fn main() {
             r"
 use std::fmt::Debug;
 
+mod std { pub mod fmt { pub trait Debug {} } }
 fn main() {
     Debug;
     let x: Debug = Debug;
@@ -536,50 +252,12 @@ fn main() {
         );
     }
 
-    #[test]
-    fn replaces_all_affected_paths_mod() {
-        check_assist(
-            replace_qualified_name_with_use,
-            r"
-mod m {
-    fn f() {
-        std::fmt::Debug$0;
-        let x: std::fmt::Debug = std::fmt::Debug;
-    }
-    fn g() {
-        std::fmt::Debug;
-    }
-}
-
-fn f() {
-    std::fmt::Debug;
-}
-    ",
-            r"
-mod m {
-    use std::fmt::Debug;
-
-    fn f() {
-        Debug;
-        let x: Debug = Debug;
-    }
-    fn g() {
-        Debug;
-    }
-}
-
-fn f() {
-    std::fmt::Debug;
-}
-    ",
-        );
-    }
-
     #[test]
     fn does_not_replace_in_submodules() {
         check_assist(
             replace_qualified_name_with_use,
             r"
+mod std { pub mod fmt { pub trait Debug {} } }
 fn main() {
     std::fmt::Debug$0;
 }
@@ -593,6 +271,7 @@ fn f() {
             r"
 use std::fmt::Debug;
 
+mod std { pub mod fmt { pub trait Debug {} } }
 fn main() {
     Debug;
 }
@@ -611,6 +290,7 @@ fn does_not_replace_in_use() {
         check_assist(
             replace_qualified_name_with_use,
             r"
+mod std { pub mod fmt { pub trait Display {} } }
 use std::fmt::Display;
 
 fn main() {
@@ -618,7 +298,8 @@ fn main() {
 }
     ",
             r"
-use std::fmt::{self, Display};
+mod std { pub mod fmt { pub trait Display {} } }
+use std::fmt::{Display, self};
 
 fn main() {
     fmt;
@@ -628,42 +309,54 @@ fn main() {
     }
 
     #[test]
-    fn does_not_replace_pub_use() {
-        check_assist(
+    fn does_not_replace_assoc_item_path() {
+        check_assist_not_applicable(
             replace_qualified_name_with_use,
             r"
-pub use std::fmt;
-
-impl std::io$0 for Foo {
+pub struct Foo;
+impl Foo {
+    pub fn foo() {}
 }
-    ",
-            r"
-pub use std::fmt;
-use std::io;
 
-impl io for Foo {
+fn main() {
+    Foo::foo$0();
 }
-    ",
+",
         );
     }
 
     #[test]
-    fn does_not_replace_pub_crate_use() {
+    fn replace_reuses_path_qualifier() {
         check_assist(
             replace_qualified_name_with_use,
             r"
-pub(crate) use std::fmt;
+pub mod foo {
+    pub struct Foo;
+}
 
-impl std::io$0 for Foo {
+mod bar {
+    pub use super::foo::Foo as Bar;
 }
-    ",
+
+fn main() {
+    foo::Foo$0;
+}
+",
             r"
-pub(crate) use std::fmt;
-use std::io;
+use foo::Foo;
 
-impl io for Foo {
+pub mod foo {
+    pub struct Foo;
 }
-    ",
+
+mod bar {
+    pub use super::foo::Foo as Bar;
+}
+
+fn main() {
+    Foo;
+}
+",
         );
     }
 }