]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs
Rollup merge of #99954 - dingxiangfei2009:break-out-let-else-higher-up, r=oli-obk
[rust.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / replace_qualified_name_with_use.rs
1 use hir::AsAssocItem;
2 use ide_db::{
3     helpers::mod_path_to_ast,
4     imports::insert_use::{insert_use, ImportScope},
5 };
6 use syntax::{
7     ast::{self, make},
8     match_ast, ted, AstNode, SyntaxNode,
9 };
10
11 use crate::{AssistContext, AssistId, AssistKind, Assists};
12
13 // Assist: replace_qualified_name_with_use
14 //
15 // Adds a use statement for a given fully-qualified name.
16 //
17 // ```
18 // # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
19 // fn process(map: std::collections::$0HashMap<String, String>) {}
20 // ```
21 // ->
22 // ```
23 // use std::collections::HashMap;
24 //
25 // # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
26 // fn process(map: HashMap<String, String>) {}
27 // ```
28 pub(crate) fn replace_qualified_name_with_use(
29     acc: &mut Assists,
30     ctx: &AssistContext<'_>,
31 ) -> Option<()> {
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);
36         return None;
37     }
38
39     if path.qualifier().is_none() {
40         cov_mark::hit!(dont_import_trivial_paths);
41         return None;
42     }
43
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() => (),
47         _ => return None,
48     }
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,
53         _ => return None,
54     };
55
56     let starts_with_name_ref = !matches!(
57         path.first_segment().and_then(|it| it.kind()),
58         Some(
59             ast::PathSegmentKind::CrateKw
60                 | ast::PathSegmentKind::SuperKw
61                 | ast::PathSegmentKind::SelfKw
62         )
63     );
64     let path_to_qualifier = starts_with_name_ref
65         .then(|| {
66             ctx.sema.scope(path.syntax())?.module().find_use_path_prefixed(
67                 ctx.sema.db,
68                 module,
69                 ctx.config.insert_use.prefix_kind,
70             )
71         })
72         .flatten();
73
74     let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
75     let target = path.syntax().text_range();
76     acc.add(
77         AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
78         "Replace qualified path with use",
79         target,
80         |builder| {
81             // Now that we've brought the name into scope, re-qualify all paths that could be
82             // affected (that is, all paths inside the node we added the `use` to).
83             let scope = match scope {
84                 ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
85                 ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
86                 ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
87             };
88             shorten_paths(scope.as_syntax_node(), &path);
89             let path = drop_generic_args(&path);
90             // stick the found import in front of the to be replaced path
91             let path = match path_to_qualifier.and_then(|it| mod_path_to_ast(&it).qualifier()) {
92                 Some(qualifier) => make::path_concat(qualifier, path),
93                 None => path,
94             };
95             insert_use(&scope, path, &ctx.config.insert_use);
96         },
97     )
98 }
99
100 fn drop_generic_args(path: &ast::Path) -> ast::Path {
101     let path = path.clone_for_update();
102     if let Some(segment) = path.segment() {
103         if let Some(generic_args) = segment.generic_arg_list() {
104             ted::remove(generic_args.syntax());
105         }
106     }
107     path
108 }
109
110 /// Mutates `node` to shorten `path` in all descendants of `node`.
111 fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
112     for child in node.children() {
113         match_ast! {
114             match child {
115                 // Don't modify `use` items, as this can break the `use` item when injecting a new
116                 // import into the use tree.
117                 ast::Use(_) => continue,
118                 // Don't descend into submodules, they don't have the same `use` items in scope.
119                 // FIXME: This isn't true due to `super::*` imports?
120                 ast::Module(_) => continue,
121                 ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
122                     shorten_paths(p.syntax(), path);
123                 },
124                 _ => shorten_paths(&child, path),
125             }
126         }
127     }
128 }
129
130 fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
131     if !path_eq_no_generics(path.clone(), target) {
132         return None;
133     }
134
135     // Shorten `path`, leaving only its last segment.
136     if let Some(parent) = path.qualifier() {
137         ted::remove(parent.syntax());
138     }
139     if let Some(double_colon) = path.coloncolon_token() {
140         ted::remove(&double_colon);
141     }
142
143     Some(())
144 }
145
146 fn path_eq_no_generics(lhs: ast::Path, rhs: ast::Path) -> bool {
147     let mut lhs_curr = lhs;
148     let mut rhs_curr = rhs;
149     loop {
150         match lhs_curr.segment().zip(rhs_curr.segment()) {
151             Some((lhs, rhs))
152                 if lhs.coloncolon_token().is_some() == rhs.coloncolon_token().is_some()
153                     && lhs
154                         .name_ref()
155                         .zip(rhs.name_ref())
156                         .map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) => {}
157             _ => return false,
158         }
159
160         match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
161             (Some(lhs), Some(rhs)) => {
162                 lhs_curr = lhs;
163                 rhs_curr = rhs;
164             }
165             (None, None) => return true,
166             _ => return false,
167         }
168     }
169 }
170
171 #[cfg(test)]
172 mod tests {
173     use crate::tests::{check_assist, check_assist_not_applicable};
174
175     use super::*;
176
177     #[test]
178     fn test_replace_already_imported() {
179         check_assist(
180             replace_qualified_name_with_use,
181             r"
182 mod std { pub mod fs { pub struct Path; } }
183 use std::fs;
184
185 fn main() {
186     std::f$0s::Path
187 }",
188             r"
189 mod std { pub mod fs { pub struct Path; } }
190 use std::fs;
191
192 fn main() {
193     fs::Path
194 }",
195         )
196     }
197
198     #[test]
199     fn test_replace_add_use_no_anchor() {
200         check_assist(
201             replace_qualified_name_with_use,
202             r"
203 mod std { pub mod fs { pub struct Path; } }
204 std::fs::Path$0
205     ",
206             r"
207 use std::fs::Path;
208
209 mod std { pub mod fs { pub struct Path; } }
210 Path
211     ",
212         );
213     }
214
215     #[test]
216     fn test_replace_add_use_no_anchor_middle_segment() {
217         check_assist(
218             replace_qualified_name_with_use,
219             r"
220 mod std { pub mod fs { pub struct Path; } }
221 std::fs$0::Path
222     ",
223             r"
224 use std::fs;
225
226 mod std { pub mod fs { pub struct Path; } }
227 fs::Path
228     ",
229         );
230     }
231
232     #[test]
233     fn dont_import_trivial_paths() {
234         cov_mark::check!(dont_import_trivial_paths);
235         check_assist_not_applicable(replace_qualified_name_with_use, r"impl foo$0 for () {}");
236     }
237
238     #[test]
239     fn test_replace_not_applicable_in_use() {
240         cov_mark::check!(not_applicable_in_use);
241         check_assist_not_applicable(replace_qualified_name_with_use, r"use std::fmt$0;");
242     }
243
244     #[test]
245     fn replaces_all_affected_paths() {
246         check_assist(
247             replace_qualified_name_with_use,
248             r"
249 mod std { pub mod fmt { pub trait Debug {} } }
250 fn main() {
251     std::fmt::Debug$0;
252     let x: std::fmt::Debug = std::fmt::Debug;
253 }
254     ",
255             r"
256 use std::fmt::Debug;
257
258 mod std { pub mod fmt { pub trait Debug {} } }
259 fn main() {
260     Debug;
261     let x: Debug = Debug;
262 }
263     ",
264         );
265     }
266
267     #[test]
268     fn does_not_replace_in_submodules() {
269         check_assist(
270             replace_qualified_name_with_use,
271             r"
272 mod std { pub mod fmt { pub trait Debug {} } }
273 fn main() {
274     std::fmt::Debug$0;
275 }
276
277 mod sub {
278     fn f() {
279         std::fmt::Debug;
280     }
281 }
282     ",
283             r"
284 use std::fmt::Debug;
285
286 mod std { pub mod fmt { pub trait Debug {} } }
287 fn main() {
288     Debug;
289 }
290
291 mod sub {
292     fn f() {
293         std::fmt::Debug;
294     }
295 }
296     ",
297         );
298     }
299
300     #[test]
301     fn does_not_replace_in_use() {
302         check_assist(
303             replace_qualified_name_with_use,
304             r"
305 mod std { pub mod fmt { pub trait Display {} } }
306 use std::fmt::Display;
307
308 fn main() {
309     std::fmt$0;
310 }
311     ",
312             r"
313 mod std { pub mod fmt { pub trait Display {} } }
314 use std::fmt::{Display, self};
315
316 fn main() {
317     fmt;
318 }
319     ",
320         );
321     }
322
323     #[test]
324     fn does_not_replace_assoc_item_path() {
325         check_assist_not_applicable(
326             replace_qualified_name_with_use,
327             r"
328 pub struct Foo;
329 impl Foo {
330     pub fn foo() {}
331 }
332
333 fn main() {
334     Foo::foo$0();
335 }
336 ",
337         );
338     }
339
340     #[test]
341     fn replace_reuses_path_qualifier() {
342         check_assist(
343             replace_qualified_name_with_use,
344             r"
345 pub mod foo {
346     pub struct Foo;
347 }
348
349 mod bar {
350     pub use super::foo::Foo as Bar;
351 }
352
353 fn main() {
354     foo::Foo$0;
355 }
356 ",
357             r"
358 use foo::Foo;
359
360 pub mod foo {
361     pub struct Foo;
362 }
363
364 mod bar {
365     pub use super::foo::Foo as Bar;
366 }
367
368 fn main() {
369     Foo;
370 }
371 ",
372         );
373     }
374
375     #[test]
376     fn replace_does_not_always_try_to_replace_by_full_item_path() {
377         check_assist(
378             replace_qualified_name_with_use,
379             r"
380 use std::mem;
381
382 mod std {
383     pub mod mem {
384         pub fn drop<T>(_: T) {}
385     }
386 }
387
388 fn main() {
389     mem::drop$0(0);
390 }
391 ",
392             r"
393 use std::mem::{self, drop};
394
395 mod std {
396     pub mod mem {
397         pub fn drop<T>(_: T) {}
398     }
399 }
400
401 fn main() {
402     drop(0);
403 }
404 ",
405         );
406     }
407
408     #[test]
409     fn replace_should_drop_generic_args_in_use() {
410         check_assist(
411             replace_qualified_name_with_use,
412             r"
413 mod std {
414     pub mod mem {
415         pub fn drop<T>(_: T) {}
416     }
417 }
418
419 fn main() {
420     std::mem::drop::<usize>$0(0);
421 }
422 ",
423             r"
424 use std::mem::drop;
425
426 mod std {
427     pub mod mem {
428         pub fn drop<T>(_: T) {}
429     }
430 }
431
432 fn main() {
433     drop::<usize>(0);
434 }
435 ",
436         );
437     }
438 }