aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/assist_config.rs8
-rw-r--r--crates/assists/src/assist_context.rs5
-rw-r--r--crates/assists/src/handlers/auto_import.rs9
-rw-r--r--crates/assists/src/handlers/inline_local_variable.rs45
-rw-r--r--crates/assists/src/handlers/qualify_path.rs40
-rw-r--r--crates/assists/src/handlers/unmerge_use.rs231
-rw-r--r--crates/assists/src/lib.rs4
-rw-r--r--crates/assists/src/tests.rs9
-rw-r--r--crates/assists/src/tests/generated.rs14
-rw-r--r--crates/assists/src/utils.rs1
-rw-r--r--crates/assists/src/utils/import_assets.rs265
11 files changed, 306 insertions, 325 deletions
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs
index 4fe8ea761..9cabf037c 100644
--- a/crates/assists/src/assist_config.rs
+++ b/crates/assists/src/assist_config.rs
@@ -4,7 +4,7 @@
4//! module, and we use to statically check that we only produce snippet 4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to. 5//! assists if we are allowed to.
6 6
7use ide_db::helpers::{insert_use::MergeBehavior, SnippetCap}; 7use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
8 8
9use crate::AssistKind; 9use crate::AssistKind;
10 10
@@ -14,9 +14,3 @@ pub struct AssistConfig {
14 pub allowed: Option<Vec<AssistKind>>, 14 pub allowed: Option<Vec<AssistKind>>,
15 pub insert_use: InsertUseConfig, 15 pub insert_use: InsertUseConfig,
16} 16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct InsertUseConfig {
20 pub merge: Option<MergeBehavior>,
21 pub prefix_kind: hir::PrefixKind,
22}
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index 321fe77f3..8d93edba2 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -2,7 +2,6 @@
2 2
3use std::mem; 3use std::mem;
4 4
5use algo::find_covering_element;
6use hir::Semantics; 5use hir::Semantics;
7use ide_db::{ 6use ide_db::{
8 base_db::{AnchoredPathBuf, FileId, FileRange}, 7 base_db::{AnchoredPathBuf, FileId, FileRange},
@@ -94,11 +93,11 @@ impl<'a> AssistContext<'a> {
94 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset()) 93 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
95 } 94 }
96 pub(crate) fn covering_element(&self) -> SyntaxElement { 95 pub(crate) fn covering_element(&self) -> SyntaxElement {
97 find_covering_element(self.source_file.syntax(), self.frange.range) 96 self.source_file.syntax().covering_element(self.frange.range)
98 } 97 }
99 // FIXME: remove 98 // FIXME: remove
100 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { 99 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
101 find_covering_element(self.source_file.syntax(), range) 100 self.source_file.syntax().covering_element(range)
102 } 101 }
103} 102}
104 103
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index 55620f0f3..4e2a4fcd9 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -1,13 +1,11 @@
1use ide_db::helpers::{ 1use ide_db::helpers::{
2 import_assets::{ImportAssets, ImportCandidate},
2 insert_use::{insert_use, ImportScope}, 3 insert_use::{insert_use, ImportScope},
3 mod_path_to_ast, 4 mod_path_to_ast,
4}; 5};
5use syntax::ast; 6use syntax::ast;
6 7
7use crate::{ 8use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
8 utils::import_assets::{ImportAssets, ImportCandidate},
9 AssistContext, AssistId, AssistKind, Assists, GroupLabel,
10};
11 9
12// Feature: Auto Import 10// Feature: Auto Import
13// 11//
@@ -121,8 +119,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
121 119
122fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel { 120fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
123 let name = match import_candidate { 121 let name = match import_candidate {
124 ImportCandidate::UnqualifiedName(candidate) 122 ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name),
125 | ImportCandidate::QualifierStart(candidate) => format!("Import {}", &candidate.name),
126 ImportCandidate::TraitAssocItem(candidate) => { 123 ImportCandidate::TraitAssocItem(candidate) => {
127 format!("Import a trait for item {}", &candidate.name) 124 format!("Import a trait for item {}", &candidate.name)
128 } 125 }
diff --git a/crates/assists/src/handlers/inline_local_variable.rs b/crates/assists/src/handlers/inline_local_variable.rs
index dc798daaa..0e63a60e8 100644
--- a/crates/assists/src/handlers/inline_local_variable.rs
+++ b/crates/assists/src/handlers/inline_local_variable.rs
@@ -79,29 +79,30 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
79 None => return Ok(false), 79 None => return Ok(false),
80 }; 80 };
81 81
82 Ok(!matches!((&initializer_expr, usage_parent), 82 Ok(!matches!(
83 (&initializer_expr, usage_parent),
83 (ast::Expr::CallExpr(_), _) 84 (ast::Expr::CallExpr(_), _)
84 | (ast::Expr::IndexExpr(_), _) 85 | (ast::Expr::IndexExpr(_), _)
85 | (ast::Expr::MethodCallExpr(_), _) 86 | (ast::Expr::MethodCallExpr(_), _)
86 | (ast::Expr::FieldExpr(_), _) 87 | (ast::Expr::FieldExpr(_), _)
87 | (ast::Expr::TryExpr(_), _) 88 | (ast::Expr::TryExpr(_), _)
88 | (ast::Expr::RefExpr(_), _) 89 | (ast::Expr::RefExpr(_), _)
89 | (ast::Expr::Literal(_), _) 90 | (ast::Expr::Literal(_), _)
90 | (ast::Expr::TupleExpr(_), _) 91 | (ast::Expr::TupleExpr(_), _)
91 | (ast::Expr::ArrayExpr(_), _) 92 | (ast::Expr::ArrayExpr(_), _)
92 | (ast::Expr::ParenExpr(_), _) 93 | (ast::Expr::ParenExpr(_), _)
93 | (ast::Expr::PathExpr(_), _) 94 | (ast::Expr::PathExpr(_), _)
94 | (ast::Expr::BlockExpr(_), _) 95 | (ast::Expr::BlockExpr(_), _)
95 | (ast::Expr::EffectExpr(_), _) 96 | (ast::Expr::EffectExpr(_), _)
96 | (_, ast::Expr::CallExpr(_)) 97 | (_, ast::Expr::CallExpr(_))
97 | (_, ast::Expr::TupleExpr(_)) 98 | (_, ast::Expr::TupleExpr(_))
98 | (_, ast::Expr::ArrayExpr(_)) 99 | (_, ast::Expr::ArrayExpr(_))
99 | (_, ast::Expr::ParenExpr(_)) 100 | (_, ast::Expr::ParenExpr(_))
100 | (_, ast::Expr::ForExpr(_)) 101 | (_, ast::Expr::ForExpr(_))
101 | (_, ast::Expr::WhileExpr(_)) 102 | (_, ast::Expr::WhileExpr(_))
102 | (_, ast::Expr::BreakExpr(_)) 103 | (_, ast::Expr::BreakExpr(_))
103 | (_, ast::Expr::ReturnExpr(_)) 104 | (_, ast::Expr::ReturnExpr(_))
104 | (_, ast::Expr::MatchExpr(_)) 105 | (_, ast::Expr::MatchExpr(_))
105 )) 106 ))
106 }) 107 })
107 .collect::<Result<Vec<_>, _>>()?; 108 .collect::<Result<Vec<_>, _>>()?;
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs
index f7fbf37f4..a7d9fd4dc 100644
--- a/crates/assists/src/handlers/qualify_path.rs
+++ b/crates/assists/src/handlers/qualify_path.rs
@@ -1,7 +1,10 @@
1use std::iter; 1use std::iter;
2 2
3use hir::AsName; 3use hir::AsName;
4use ide_db::helpers::mod_path_to_ast; 4use ide_db::helpers::{
5 import_assets::{ImportAssets, ImportCandidate},
6 mod_path_to_ast,
7};
5use ide_db::RootDatabase; 8use ide_db::RootDatabase;
6use syntax::{ 9use syntax::{
7 ast, 10 ast,
@@ -12,7 +15,6 @@ use test_utils::mark;
12 15
13use crate::{ 16use crate::{
14 assist_context::{AssistContext, Assists}, 17 assist_context::{AssistContext, Assists},
15 utils::import_assets::{ImportAssets, ImportCandidate},
16 AssistId, AssistKind, GroupLabel, 18 AssistId, AssistKind, GroupLabel,
17}; 19};
18 20
@@ -53,17 +55,18 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
53 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; 55 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
54 56
55 let qualify_candidate = match candidate { 57 let qualify_candidate = match candidate {
56 ImportCandidate::QualifierStart(_) => { 58 ImportCandidate::Path(candidate) => {
57 mark::hit!(qualify_path_qualifier_start); 59 if candidate.qualifier.is_some() {
58 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 60 mark::hit!(qualify_path_qualifier_start);
59 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); 61 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
60 QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list()) 62 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
61 } 63 QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
62 ImportCandidate::UnqualifiedName(_) => { 64 } else {
63 mark::hit!(qualify_path_unqualified_name); 65 mark::hit!(qualify_path_unqualified_name);
64 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; 66 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
65 let generics = path.segment()?.generic_arg_list(); 67 let generics = path.segment()?.generic_arg_list();
66 QualifyCandidate::UnqualifiedName(generics) 68 QualifyCandidate::UnqualifiedName(generics)
69 }
67 } 70 }
68 ImportCandidate::TraitAssocItem(_) => { 71 ImportCandidate::TraitAssocItem(_) => {
69 mark::hit!(qualify_path_trait_assoc_item); 72 mark::hit!(qualify_path_trait_assoc_item);
@@ -186,7 +189,7 @@ fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> {
186 189
187fn group_label(candidate: &ImportCandidate) -> GroupLabel { 190fn group_label(candidate: &ImportCandidate) -> GroupLabel {
188 let name = match candidate { 191 let name = match candidate {
189 ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name, 192 ImportCandidate::Path(it) => &it.name,
190 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, 193 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
191 }; 194 };
192 GroupLabel(format!("Qualify {}", name)) 195 GroupLabel(format!("Qualify {}", name))
@@ -194,8 +197,13 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
194 197
195fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { 198fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
196 match candidate { 199 match candidate {
197 ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import), 200 ImportCandidate::Path(candidate) => {
198 ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import), 201 if candidate.qualifier.is_some() {
202 format!("Qualify with `{}`", &import)
203 } else {
204 format!("Qualify as `{}`", &import)
205 }
206 }
199 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import), 207 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import),
200 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import), 208 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import),
201 } 209 }
diff --git a/crates/assists/src/handlers/unmerge_use.rs b/crates/assists/src/handlers/unmerge_use.rs
new file mode 100644
index 000000000..3dbef8e51
--- /dev/null
+++ b/crates/assists/src/handlers/unmerge_use.rs
@@ -0,0 +1,231 @@
1use syntax::{
2 algo::SyntaxRewriter,
3 ast::{self, edit::AstNodeEdit, VisibilityOwner},
4 AstNode, SyntaxKind,
5};
6use test_utils::mark;
7
8use crate::{
9 assist_context::{AssistContext, Assists},
10 AssistId, AssistKind,
11};
12
13// Assist: unmerge_use
14//
15// Extracts single use item from use list.
16//
17// ```
18// use std::fmt::{Debug, Display$0};
19// ```
20// ->
21// ```
22// use std::fmt::{Debug};
23// use std::fmt::Display;
24// ```
25pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let tree: ast::UseTree = ctx.find_node_at_offset()?;
27
28 let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
29 if tree_list.use_trees().count() < 2 {
30 mark::hit!(skip_single_use_item);
31 return None;
32 }
33
34 let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
35 let path = resolve_full_path(&tree)?;
36
37 let target = tree.syntax().text_range();
38 acc.add(
39 AssistId("unmerge_use", AssistKind::RefactorRewrite),
40 "Unmerge use",
41 target,
42 |builder| {
43 let new_use = ast::make::use_(
44 use_.visibility(),
45 ast::make::use_tree(
46 path,
47 tree.use_tree_list(),
48 tree.rename(),
49 tree.star_token().is_some(),
50 ),
51 );
52
53 let mut rewriter = SyntaxRewriter::default();
54 rewriter += tree.remove();
55 rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
56 if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
57 rewriter.insert_after(
58 use_.syntax(),
59 &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
60 );
61 }
62 rewriter.insert_after(use_.syntax(), new_use.syntax());
63
64 builder.rewrite(rewriter);
65 },
66 )
67}
68
69fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
70 let mut paths = tree
71 .syntax()
72 .ancestors()
73 .take_while(|n| n.kind() != SyntaxKind::USE_KW)
74 .filter_map(ast::UseTree::cast)
75 .filter_map(|t| t.path());
76
77 let mut final_path = paths.next()?;
78 for path in paths {
79 final_path = ast::make::path_concat(path, final_path)
80 }
81 Some(final_path)
82}
83
84#[cfg(test)]
85mod tests {
86 use crate::tests::{check_assist, check_assist_not_applicable};
87
88 use super::*;
89
90 #[test]
91 fn skip_single_use_item() {
92 mark::check!(skip_single_use_item);
93 check_assist_not_applicable(
94 unmerge_use,
95 r"
96use std::fmt::Debug$0;
97",
98 );
99 check_assist_not_applicable(
100 unmerge_use,
101 r"
102use std::fmt::{Debug$0};
103",
104 );
105 check_assist_not_applicable(
106 unmerge_use,
107 r"
108use std::fmt::Debug as Dbg$0;
109",
110 );
111 }
112
113 #[test]
114 fn skip_single_glob_import() {
115 check_assist_not_applicable(
116 unmerge_use,
117 r"
118use std::fmt::*$0;
119",
120 );
121 }
122
123 #[test]
124 fn unmerge_use_item() {
125 check_assist(
126 unmerge_use,
127 r"
128use std::fmt::{Debug, Display$0};
129",
130 r"
131use std::fmt::{Debug};
132use std::fmt::Display;
133",
134 );
135
136 check_assist(
137 unmerge_use,
138 r"
139use std::fmt::{Debug, format$0, Display};
140",
141 r"
142use std::fmt::{Debug, Display};
143use std::fmt::format;
144",
145 );
146 }
147
148 #[test]
149 fn unmerge_glob_import() {
150 check_assist(
151 unmerge_use,
152 r"
153use std::fmt::{*$0, Display};
154",
155 r"
156use std::fmt::{Display};
157use std::fmt::*;
158",
159 );
160 }
161
162 #[test]
163 fn unmerge_renamed_use_item() {
164 check_assist(
165 unmerge_use,
166 r"
167use std::fmt::{Debug, Display as Disp$0};
168",
169 r"
170use std::fmt::{Debug};
171use std::fmt::Display as Disp;
172",
173 );
174 }
175
176 #[test]
177 fn unmerge_indented_use_item() {
178 check_assist(
179 unmerge_use,
180 r"
181mod format {
182 use std::fmt::{Debug, Display$0 as Disp, format};
183}
184",
185 r"
186mod format {
187 use std::fmt::{Debug, format};
188 use std::fmt::Display as Disp;
189}
190",
191 );
192 }
193
194 #[test]
195 fn unmerge_nested_use_item() {
196 check_assist(
197 unmerge_use,
198 r"
199use foo::bar::{baz::{qux$0, foobar}, barbaz};
200",
201 r"
202use foo::bar::{baz::{foobar}, barbaz};
203use foo::bar::baz::qux;
204",
205 );
206 check_assist(
207 unmerge_use,
208 r"
209use foo::bar::{baz$0::{qux, foobar}, barbaz};
210",
211 r"
212use foo::bar::{barbaz};
213use foo::bar::baz::{qux, foobar};
214",
215 );
216 }
217
218 #[test]
219 fn unmerge_use_item_with_visibility() {
220 check_assist(
221 unmerge_use,
222 r"
223pub use std::fmt::{Debug, Display$0};
224",
225 r"
226pub use std::fmt::{Debug};
227pub use std::fmt::Display;
228",
229 );
230 }
231}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 1080294ab..14178a651 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -24,7 +24,7 @@ use syntax::TextRange;
24 24
25pub(crate) use crate::assist_context::{AssistContext, Assists}; 25pub(crate) use crate::assist_context::{AssistContext, Assists};
26 26
27pub use assist_config::{AssistConfig, InsertUseConfig}; 27pub use assist_config::AssistConfig;
28 28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)] 29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AssistKind { 30pub enum AssistKind {
@@ -156,6 +156,7 @@ mod handlers {
156 mod replace_unwrap_with_match; 156 mod replace_unwrap_with_match;
157 mod split_import; 157 mod split_import;
158 mod toggle_ignore; 158 mod toggle_ignore;
159 mod unmerge_use;
159 mod unwrap_block; 160 mod unwrap_block;
160 mod wrap_return_type_in_result; 161 mod wrap_return_type_in_result;
161 162
@@ -213,6 +214,7 @@ mod handlers {
213 replace_unwrap_with_match::replace_unwrap_with_match, 214 replace_unwrap_with_match::replace_unwrap_with_match,
214 split_import::split_import, 215 split_import::split_import,
215 toggle_ignore::toggle_ignore, 216 toggle_ignore::toggle_ignore,
217 unmerge_use::unmerge_use,
216 unwrap_block::unwrap_block, 218 unwrap_block::unwrap_block,
217 wrap_return_type_in_result::wrap_return_type_in_result, 219 wrap_return_type_in_result::wrap_return_type_in_result,
218 // These are manually sorted for better priorities 220 // These are manually sorted for better priorities
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
index 71431b406..32bd8698b 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -3,16 +3,17 @@ mod generated;
3use hir::Semantics; 3use hir::Semantics;
4use ide_db::{ 4use ide_db::{
5 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}, 5 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
6 helpers::{insert_use::MergeBehavior, SnippetCap}, 6 helpers::{
7 insert_use::{InsertUseConfig, MergeBehavior},
8 SnippetCap,
9 },
7 source_change::FileSystemEdit, 10 source_change::FileSystemEdit,
8 RootDatabase, 11 RootDatabase,
9}; 12};
10use syntax::TextRange; 13use syntax::TextRange;
11use test_utils::{assert_eq_text, extract_offset, extract_range}; 14use test_utils::{assert_eq_text, extract_offset, extract_range};
12 15
13use crate::{ 16use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
14 handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists, InsertUseConfig,
15};
16use stdx::{format_to, trim_indent}; 17use stdx::{format_to, trim_indent};
17 18
18pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { 19pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 217f577eb..d48d063b4 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -1138,6 +1138,20 @@ fn arithmetics {
1138} 1138}
1139 1139
1140#[test] 1140#[test]
1141fn doctest_unmerge_use() {
1142 check_doc_test(
1143 "unmerge_use",
1144 r#####"
1145use std::fmt::{Debug, Display$0};
1146"#####,
1147 r#####"
1148use std::fmt::{Debug};
1149use std::fmt::Display;
1150"#####,
1151 )
1152}
1153
1154#[test]
1141fn doctest_unwrap_block() { 1155fn doctest_unwrap_block() {
1142 check_doc_test( 1156 check_doc_test(
1143 "unwrap_block", 1157 "unwrap_block",
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 9ea96eb73..fc9f83bab 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -1,5 +1,4 @@
1//! Assorted functions shared by several assists. 1//! Assorted functions shared by several assists.
2pub(crate) mod import_assets;
3 2
4use std::ops; 3use std::ops;
5 4
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs
deleted file mode 100644
index 4ce82c1ba..000000000
--- a/crates/assists/src/utils/import_assets.rs
+++ /dev/null
@@ -1,265 +0,0 @@
1//! Look up accessible paths for items.
2use either::Either;
3use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics};
4use ide_db::{imports_locator, RootDatabase};
5use rustc_hash::FxHashSet;
6use syntax::{ast, AstNode, SyntaxNode};
7
8use crate::assist_config::InsertUseConfig;
9
10#[derive(Debug)]
11pub(crate) enum ImportCandidate {
12 /// Simple name like 'HashMap'
13 UnqualifiedName(PathImportCandidate),
14 /// First part of the qualified name.
15 /// For 'std::collections::HashMap', that will be 'std'.
16 QualifierStart(PathImportCandidate),
17 /// A trait associated function (with no self parameter) or associated constant.
18 /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type
19 /// and `name` is the `test_function`
20 TraitAssocItem(TraitImportCandidate),
21 /// A trait method with self parameter.
22 /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type
23 /// and `name` is the `test_method`
24 TraitMethod(TraitImportCandidate),
25}
26
27#[derive(Debug)]
28pub(crate) struct TraitImportCandidate {
29 pub(crate) ty: hir::Type,
30 pub(crate) name: ast::NameRef,
31}
32
33#[derive(Debug)]
34pub(crate) struct PathImportCandidate {
35 pub(crate) name: ast::NameRef,
36}
37
38#[derive(Debug)]
39pub(crate) struct ImportAssets {
40 import_candidate: ImportCandidate,
41 module_with_name_to_import: hir::Module,
42 syntax_under_caret: SyntaxNode,
43}
44
45impl ImportAssets {
46 pub(crate) fn for_method_call(
47 method_call: ast::MethodCallExpr,
48 sema: &Semantics<RootDatabase>,
49 ) -> Option<Self> {
50 let syntax_under_caret = method_call.syntax().to_owned();
51 let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
52 Some(Self {
53 import_candidate: ImportCandidate::for_method_call(sema, &method_call)?,
54 module_with_name_to_import,
55 syntax_under_caret,
56 })
57 }
58
59 pub(crate) fn for_regular_path(
60 path_under_caret: ast::Path,
61 sema: &Semantics<RootDatabase>,
62 ) -> Option<Self> {
63 let syntax_under_caret = path_under_caret.syntax().to_owned();
64 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
65 return None;
66 }
67
68 let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?;
69 Some(Self {
70 import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?,
71 module_with_name_to_import,
72 syntax_under_caret,
73 })
74 }
75
76 pub(crate) fn syntax_under_caret(&self) -> &SyntaxNode {
77 &self.syntax_under_caret
78 }
79
80 pub(crate) fn import_candidate(&self) -> &ImportCandidate {
81 &self.import_candidate
82 }
83
84 fn get_search_query(&self) -> &str {
85 match &self.import_candidate {
86 ImportCandidate::UnqualifiedName(candidate)
87 | ImportCandidate::QualifierStart(candidate) => candidate.name.text(),
88 ImportCandidate::TraitAssocItem(candidate)
89 | ImportCandidate::TraitMethod(candidate) => candidate.name.text(),
90 }
91 }
92
93 pub(crate) fn search_for_imports(
94 &self,
95 sema: &Semantics<RootDatabase>,
96 config: &InsertUseConfig,
97 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
98 let _p = profile::span("import_assists::search_for_imports");
99 self.search_for(sema, Some(config.prefix_kind))
100 }
101
102 /// This may return non-absolute paths if a part of the returned path is already imported into scope.
103 #[allow(dead_code)]
104 pub(crate) fn search_for_relative_paths(
105 &self,
106 sema: &Semantics<RootDatabase>,
107 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
108 let _p = profile::span("import_assists::search_for_relative_paths");
109 self.search_for(sema, None)
110 }
111
112 fn search_for(
113 &self,
114 sema: &Semantics<RootDatabase>,
115 prefixed: Option<hir::PrefixKind>,
116 ) -> Vec<(hir::ModPath, hir::ItemInNs)> {
117 let db = sema.db;
118 let mut trait_candidates = FxHashSet::default();
119 let current_crate = self.module_with_name_to_import.krate();
120
121 let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| {
122 trait_candidates.clear();
123 match &self.import_candidate {
124 ImportCandidate::TraitAssocItem(trait_candidate) => {
125 let located_assoc_item = match candidate {
126 Either::Left(ModuleDef::Function(located_function)) => {
127 located_function.as_assoc_item(db)
128 }
129 Either::Left(ModuleDef::Const(located_const)) => {
130 located_const.as_assoc_item(db)
131 }
132 _ => None,
133 }
134 .map(|assoc| assoc.container(db))
135 .and_then(Self::assoc_to_trait)?;
136
137 trait_candidates.insert(located_assoc_item.into());
138
139 trait_candidate
140 .ty
141 .iterate_path_candidates(
142 db,
143 current_crate,
144 &trait_candidates,
145 None,
146 |_, assoc| Self::assoc_to_trait(assoc.container(db)),
147 )
148 .map(ModuleDef::from)
149 .map(Either::Left)
150 }
151 ImportCandidate::TraitMethod(trait_candidate) => {
152 let located_assoc_item =
153 if let Either::Left(ModuleDef::Function(located_function)) = candidate {
154 located_function
155 .as_assoc_item(db)
156 .map(|assoc| assoc.container(db))
157 .and_then(Self::assoc_to_trait)
158 } else {
159 None
160 }?;
161
162 trait_candidates.insert(located_assoc_item.into());
163
164 trait_candidate
165 .ty
166 .iterate_method_candidates(
167 db,
168 current_crate,
169 &trait_candidates,
170 None,
171 |_, function| {
172 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
173 },
174 )
175 .map(ModuleDef::from)
176 .map(Either::Left)
177 }
178 _ => Some(candidate),
179 }
180 };
181
182 let mut res = imports_locator::find_exact_imports(
183 sema,
184 current_crate,
185 self.get_search_query().to_string(),
186 )
187 .filter_map(filter)
188 .filter_map(|candidate| {
189 let item: hir::ItemInNs = candidate.either(Into::into, Into::into);
190 if let Some(prefix_kind) = prefixed {
191 self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind)
192 } else {
193 self.module_with_name_to_import.find_use_path(db, item)
194 }
195 .map(|path| (path, item))
196 })
197 .filter(|(use_path, _)| use_path.len() > 1)
198 .take(20)
199 .collect::<Vec<_>>();
200 res.sort_by_key(|(path, _)| path.clone());
201 res
202 }
203
204 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> {
205 if let AssocItemContainer::Trait(extracted_trait) = assoc {
206 Some(extracted_trait)
207 } else {
208 None
209 }
210 }
211}
212
213impl ImportCandidate {
214 fn for_method_call(
215 sema: &Semantics<RootDatabase>,
216 method_call: &ast::MethodCallExpr,
217 ) -> Option<Self> {
218 match sema.resolve_method_call(method_call) {
219 Some(_) => None,
220 None => Some(Self::TraitMethod(TraitImportCandidate {
221 ty: sema.type_of_expr(&method_call.receiver()?)?,
222 name: method_call.name_ref()?,
223 })),
224 }
225 }
226
227 fn for_regular_path(
228 sema: &Semantics<RootDatabase>,
229 path_under_caret: &ast::Path,
230 ) -> Option<Self> {
231 if sema.resolve_path(path_under_caret).is_some() {
232 return None;
233 }
234
235 let segment = path_under_caret.segment()?;
236 let candidate = if let Some(qualifier) = path_under_caret.qualifier() {
237 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
238 let qualifier_start_path =
239 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
240 if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
241 let qualifier_resolution = if qualifier_start_path == qualifier {
242 qualifier_start_resolution
243 } else {
244 sema.resolve_path(&qualifier)?
245 };
246 match qualifier_resolution {
247 hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => {
248 ImportCandidate::TraitAssocItem(TraitImportCandidate {
249 ty: assoc_item_path.ty(sema.db),
250 name: segment.name_ref()?,
251 })
252 }
253 _ => return None,
254 }
255 } else {
256 ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start })
257 }
258 } else {
259 ImportCandidate::UnqualifiedName(PathImportCandidate {
260 name: segment.syntax().descendants().find_map(ast::NameRef::cast)?,
261 })
262 };
263 Some(candidate)
264 }
265}