3 helpers::mod_path_to_ast,
4 imports::insert_use::{insert_use, ImportScope},
8 match_ast, ted, AstNode, SyntaxNode,
11 use crate::{AssistContext, AssistId, AssistKind, Assists};
13 // Assist: replace_qualified_name_with_use
15 // Adds a use statement for a given fully-qualified name.
18 // # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
19 // fn process(map: std::collections::$0HashMap<String, String>) {}
23 // use std::collections::HashMap;
25 // # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
26 // fn process(map: HashMap<String, String>) {}
28 pub(crate) fn replace_qualified_name_with_use(
30 ctx: &AssistContext<'_>,
32 let path: ast::Path = ctx.find_node_at_offset()?;
33 // We don't want to mess with use statements
34 if path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() {
35 cov_mark::hit!(not_applicable_in_use);
39 if path.qualifier().is_none() {
40 cov_mark::hit!(dont_import_trivial_paths);
44 // only offer replacement for non assoc items
45 match ctx.sema.resolve_path(&path)? {
46 hir::PathResolution::Def(def) if def.as_assoc_item(ctx.sema.db).is_none() => (),
49 // then search for an import for the first path segment of what we want to replace
50 // that way it is less likely that we import the item from a different location due re-exports
51 let module = match ctx.sema.resolve_path(&path.first_qualifier_or_self())? {
52 hir::PathResolution::Def(module @ hir::ModuleDef::Module(_)) => module,
56 let starts_with_name_ref = !matches!(
57 path.first_segment().and_then(|it| it.kind()),
59 ast::PathSegmentKind::CrateKw
60 | ast::PathSegmentKind::SuperKw
61 | ast::PathSegmentKind::SelfKw
64 let path_to_qualifier = starts_with_name_ref
66 ctx.sema.scope(path.syntax())?.module().find_use_path_prefixed(
69 ctx.config.insert_use.prefix_kind,
70 ctx.config.prefer_no_std,
75 let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
76 let target = path.syntax().text_range();
78 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
79 "Replace qualified path with use",
82 // Now that we've brought the name into scope, re-qualify all paths that could be
83 // affected (that is, all paths inside the node we added the `use` to).
84 let scope = match scope {
85 ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
86 ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
87 ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
89 shorten_paths(scope.as_syntax_node(), &path);
90 let path = drop_generic_args(&path);
91 // stick the found import in front of the to be replaced path
92 let path = match path_to_qualifier.and_then(|it| mod_path_to_ast(&it).qualifier()) {
93 Some(qualifier) => make::path_concat(qualifier, path),
96 insert_use(&scope, path, &ctx.config.insert_use);
101 fn drop_generic_args(path: &ast::Path) -> ast::Path {
102 let path = path.clone_for_update();
103 if let Some(segment) = path.segment() {
104 if let Some(generic_args) = segment.generic_arg_list() {
105 ted::remove(generic_args.syntax());
111 /// Mutates `node` to shorten `path` in all descendants of `node`.
112 fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
113 for child in node.children() {
116 // Don't modify `use` items, as this can break the `use` item when injecting a new
117 // import into the use tree.
118 ast::Use(_) => continue,
119 // Don't descend into submodules, they don't have the same `use` items in scope.
120 // FIXME: This isn't true due to `super::*` imports?
121 ast::Module(_) => continue,
122 ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
123 shorten_paths(p.syntax(), path);
125 _ => shorten_paths(&child, path),
131 fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
132 if !path_eq_no_generics(path.clone(), target) {
136 // Shorten `path`, leaving only its last segment.
137 if let Some(parent) = path.qualifier() {
138 ted::remove(parent.syntax());
140 if let Some(double_colon) = path.coloncolon_token() {
141 ted::remove(&double_colon);
147 fn path_eq_no_generics(lhs: ast::Path, rhs: ast::Path) -> bool {
148 let mut lhs_curr = lhs;
149 let mut rhs_curr = rhs;
151 match lhs_curr.segment().zip(rhs_curr.segment()) {
153 if lhs.coloncolon_token().is_some() == rhs.coloncolon_token().is_some()
157 .map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) => {}
161 match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
162 (Some(lhs), Some(rhs)) => {
166 (None, None) => return true,
174 use crate::tests::{check_assist, check_assist_not_applicable};
179 fn test_replace_already_imported() {
181 replace_qualified_name_with_use,
183 mod std { pub mod fs { pub struct Path; } }
190 mod std { pub mod fs { pub struct Path; } }
200 fn test_replace_add_use_no_anchor() {
202 replace_qualified_name_with_use,
204 mod std { pub mod fs { pub struct Path; } }
210 mod std { pub mod fs { pub struct Path; } }
217 fn test_replace_add_use_no_anchor_middle_segment() {
219 replace_qualified_name_with_use,
221 mod std { pub mod fs { pub struct Path; } }
227 mod std { pub mod fs { pub struct Path; } }
234 fn dont_import_trivial_paths() {
235 cov_mark::check!(dont_import_trivial_paths);
236 check_assist_not_applicable(replace_qualified_name_with_use, r"impl foo$0 for () {}");
240 fn test_replace_not_applicable_in_use() {
241 cov_mark::check!(not_applicable_in_use);
242 check_assist_not_applicable(replace_qualified_name_with_use, r"use std::fmt$0;");
246 fn replaces_all_affected_paths() {
248 replace_qualified_name_with_use,
250 mod std { pub mod fmt { pub trait Debug {} } }
253 let x: std::fmt::Debug = std::fmt::Debug;
259 mod std { pub mod fmt { pub trait Debug {} } }
262 let x: Debug = Debug;
269 fn does_not_replace_in_submodules() {
271 replace_qualified_name_with_use,
273 mod std { pub mod fmt { pub trait Debug {} } }
287 mod std { pub mod fmt { pub trait Debug {} } }
302 fn does_not_replace_in_use() {
304 replace_qualified_name_with_use,
306 mod std { pub mod fmt { pub trait Display {} } }
307 use std::fmt::Display;
314 mod std { pub mod fmt { pub trait Display {} } }
315 use std::fmt::{Display, self};
325 fn does_not_replace_assoc_item_path() {
326 check_assist_not_applicable(
327 replace_qualified_name_with_use,
342 fn replace_reuses_path_qualifier() {
344 replace_qualified_name_with_use,
351 pub use super::foo::Foo as Bar;
366 pub use super::foo::Foo as Bar;
377 fn replace_does_not_always_try_to_replace_by_full_item_path() {
379 replace_qualified_name_with_use,
385 pub fn drop<T>(_: T) {}
394 use std::mem::{self, drop};
398 pub fn drop<T>(_: T) {}
410 fn replace_should_drop_generic_args_in_use() {
412 replace_qualified_name_with_use,
416 pub fn drop<T>(_: T) {}
421 std::mem::drop::<usize>$0(0);
429 pub fn drop<T>(_: T) {}