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};
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",
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);
},
&& 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,
}
",
);
}
- #[test]
+
#[test]
fn dont_import_trivial_paths() {
cov_mark::check!(dont_import_trivial_paths);
",
r"
mod std { pub mod fmt { pub trait Display {} } }
-use std::fmt::{self, Display};
+use std::fmt::{Display, self};
fn main() {
fmt;
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);
+}
",
);
}