aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/release.yaml3
-rw-r--r--crates/hir/src/lib.rs1
-rw-r--r--crates/hir/src/semantics.rs4
-rw-r--r--crates/hir_def/src/body/scope.rs57
-rw-r--r--crates/hir_def/src/resolver.rs6
-rw-r--r--crates/ide_assists/src/handlers/qualify_path.rs1
-rw-r--r--crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs34
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs93
-rw-r--r--crates/ide_completion/src/completions/lifetime.rs106
-rw-r--r--crates/ide_completion/src/context.rs26
-rw-r--r--crates/ide_completion/src/lib.rs28
-rw-r--r--crates/ide_completion/src/render.rs1
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs212
-rw-r--r--crates/ide_db/src/items_locator.rs178
-rw-r--r--crates/ide_db/src/symbol_index.rs16
15 files changed, 510 insertions, 256 deletions
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 7549e998b..ae9dccce9 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -108,9 +108,6 @@ jobs:
108 with: 108 with:
109 node-version: 12.x 109 node-version: 12.x
110 110
111 - name: Print current revision
112 run: git describe --tags
113
114 - name: Dist 111 - name: Dist
115 if: github.ref == 'refs/heads/release' 112 if: github.ref == 'refs/heads/release'
116 run: cargo xtask dist --client 0.2.$GITHUB_RUN_NUMBER 113 run: cargo xtask dist --client 0.2.$GITHUB_RUN_NUMBER
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 30e577671..e34be7e42 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2199,6 +2199,7 @@ pub enum ScopeDef {
2199 ImplSelfType(Impl), 2199 ImplSelfType(Impl),
2200 AdtSelfType(Adt), 2200 AdtSelfType(Adt),
2201 Local(Local), 2201 Local(Local),
2202 Label(Label),
2202 Unknown, 2203 Unknown,
2203} 2204}
2204 2205
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 15651bb22..1198e3f0b 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -839,6 +839,10 @@ impl<'a> SemanticsScope<'a> {
839 let parent = resolver.body_owner().unwrap(); 839 let parent = resolver.body_owner().unwrap();
840 ScopeDef::Local(Local { parent, pat_id }) 840 ScopeDef::Local(Local { parent, pat_id })
841 } 841 }
842 resolver::ScopeDef::Label(label_id) => {
843 let parent = resolver.body_owner().unwrap();
844 ScopeDef::Label(Label { parent, label_id })
845 }
842 }; 846 };
843 f(name, def) 847 f(name, def)
844 }) 848 })
diff --git a/crates/hir_def/src/body/scope.rs b/crates/hir_def/src/body/scope.rs
index 1bbb54fc6..bd7005ca6 100644
--- a/crates/hir_def/src/body/scope.rs
+++ b/crates/hir_def/src/body/scope.rs
@@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
8use crate::{ 8use crate::{
9 body::Body, 9 body::Body,
10 db::DefDatabase, 10 db::DefDatabase,
11 expr::{Expr, ExprId, Pat, PatId, Statement}, 11 expr::{Expr, ExprId, LabelId, Pat, PatId, Statement},
12 BlockId, DefWithBodyId, 12 BlockId, DefWithBodyId,
13}; 13};
14 14
@@ -40,6 +40,7 @@ impl ScopeEntry {
40pub struct ScopeData { 40pub struct ScopeData {
41 parent: Option<ScopeId>, 41 parent: Option<ScopeId>,
42 block: Option<BlockId>, 42 block: Option<BlockId>,
43 label: Option<(LabelId, Name)>,
43 entries: Vec<ScopeEntry>, 44 entries: Vec<ScopeEntry>,
44} 45}
45 46
@@ -67,6 +68,11 @@ impl ExprScopes {
67 self.scopes[scope].block 68 self.scopes[scope].block
68 } 69 }
69 70
71 /// If `scope` refers to a labeled expression scope, returns the corresponding `Label`.
72 pub fn label(&self, scope: ScopeId) -> Option<(LabelId, Name)> {
73 self.scopes[scope].label.clone()
74 }
75
70 pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ { 76 pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
71 std::iter::successors(scope, move |&scope| self.scopes[scope].parent) 77 std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
72 } 78 }
@@ -85,15 +91,34 @@ impl ExprScopes {
85 } 91 }
86 92
87 fn root_scope(&mut self) -> ScopeId { 93 fn root_scope(&mut self) -> ScopeId {
88 self.scopes.alloc(ScopeData { parent: None, block: None, entries: vec![] }) 94 self.scopes.alloc(ScopeData { parent: None, block: None, label: None, entries: vec![] })
89 } 95 }
90 96
91 fn new_scope(&mut self, parent: ScopeId) -> ScopeId { 97 fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
92 self.scopes.alloc(ScopeData { parent: Some(parent), block: None, entries: vec![] }) 98 self.scopes.alloc(ScopeData {
99 parent: Some(parent),
100 block: None,
101 label: None,
102 entries: vec![],
103 })
93 } 104 }
94 105
95 fn new_block_scope(&mut self, parent: ScopeId, block: BlockId) -> ScopeId { 106 fn new_labeled_scope(&mut self, parent: ScopeId, label: Option<(LabelId, Name)>) -> ScopeId {
96 self.scopes.alloc(ScopeData { parent: Some(parent), block: Some(block), entries: vec![] }) 107 self.scopes.alloc(ScopeData { parent: Some(parent), block: None, label, entries: vec![] })
108 }
109
110 fn new_block_scope(
111 &mut self,
112 parent: ScopeId,
113 block: BlockId,
114 label: Option<(LabelId, Name)>,
115 ) -> ScopeId {
116 self.scopes.alloc(ScopeData {
117 parent: Some(parent),
118 block: Some(block),
119 label,
120 entries: vec![],
121 })
97 } 122 }
98 123
99 fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) { 124 fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
@@ -144,21 +169,33 @@ fn compute_block_scopes(
144} 169}
145 170
146fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { 171fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
172 let make_label =
173 |label: &Option<_>| label.map(|label| (label, body.labels[label].name.clone()));
174
147 scopes.set_scope(expr, scope); 175 scopes.set_scope(expr, scope);
148 match &body[expr] { 176 match &body[expr] {
149 Expr::Block { statements, tail, id, .. } => { 177 Expr::Block { statements, tail, id, label } => {
150 let scope = scopes.new_block_scope(scope, *id); 178 let scope = scopes.new_block_scope(scope, *id, make_label(label));
151 // Overwrite the old scope for the block expr, so that every block scope can be found 179 // Overwrite the old scope for the block expr, so that every block scope can be found
152 // via the block itself (important for blocks that only contain items, no expressions). 180 // via the block itself (important for blocks that only contain items, no expressions).
153 scopes.set_scope(expr, scope); 181 scopes.set_scope(expr, scope);
154 compute_block_scopes(&statements, *tail, body, scopes, scope); 182 compute_block_scopes(statements, *tail, body, scopes, scope);
155 } 183 }
156 Expr::For { iterable, pat, body: body_expr, .. } => { 184 Expr::For { iterable, pat, body: body_expr, label } => {
157 compute_expr_scopes(*iterable, body, scopes, scope); 185 compute_expr_scopes(*iterable, body, scopes, scope);
158 let scope = scopes.new_scope(scope); 186 let scope = scopes.new_labeled_scope(scope, make_label(label));
159 scopes.add_bindings(body, scope, *pat); 187 scopes.add_bindings(body, scope, *pat);
160 compute_expr_scopes(*body_expr, body, scopes, scope); 188 compute_expr_scopes(*body_expr, body, scopes, scope);
161 } 189 }
190 Expr::While { condition, body: body_expr, label } => {
191 let scope = scopes.new_labeled_scope(scope, make_label(label));
192 compute_expr_scopes(*condition, body, scopes, scope);
193 compute_expr_scopes(*body_expr, body, scopes, scope);
194 }
195 Expr::Loop { body: body_expr, label } => {
196 let scope = scopes.new_labeled_scope(scope, make_label(label));
197 compute_expr_scopes(*body_expr, body, scopes, scope);
198 }
162 Expr::Lambda { args, body: body_expr, .. } => { 199 Expr::Lambda { args, body: body_expr, .. } => {
163 let scope = scopes.new_scope(scope); 200 let scope = scopes.new_scope(scope);
164 scopes.add_params_bindings(body, scope, &args); 201 scopes.add_params_bindings(body, scope, &args);
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index 42736171e..4a2d1c087 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -12,7 +12,7 @@ use crate::{
12 body::scope::{ExprScopes, ScopeId}, 12 body::scope::{ExprScopes, ScopeId},
13 builtin_type::BuiltinType, 13 builtin_type::BuiltinType,
14 db::DefDatabase, 14 db::DefDatabase,
15 expr::{ExprId, PatId}, 15 expr::{ExprId, LabelId, PatId},
16 generics::GenericParams, 16 generics::GenericParams,
17 item_scope::{BuiltinShadowMode, BUILTIN_SCOPE}, 17 item_scope::{BuiltinShadowMode, BUILTIN_SCOPE},
18 nameres::DefMap, 18 nameres::DefMap,
@@ -409,6 +409,7 @@ pub enum ScopeDef {
409 AdtSelfType(AdtId), 409 AdtSelfType(AdtId),
410 GenericParam(GenericParamId), 410 GenericParam(GenericParamId),
411 Local(PatId), 411 Local(PatId),
412 Label(LabelId),
412} 413}
413 414
414impl Scope { 415impl Scope {
@@ -470,6 +471,9 @@ impl Scope {
470 f(name![Self], ScopeDef::AdtSelfType(*i)); 471 f(name![Self], ScopeDef::AdtSelfType(*i));
471 } 472 }
472 Scope::ExprScope(scope) => { 473 Scope::ExprScope(scope) => {
474 if let Some((label, name)) = scope.expr_scopes.label(scope.scope_id) {
475 f(name.clone(), ScopeDef::Label(label))
476 }
473 scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| { 477 scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| {
474 f(e.name().clone(), ScopeDef::Local(e.pat())); 478 f(e.name().clone(), ScopeDef::Local(e.pat()));
475 }); 479 });
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs
index e7444f7db..f91770a76 100644
--- a/crates/ide_assists/src/handlers/qualify_path.rs
+++ b/crates/ide_assists/src/handlers/qualify_path.rs
@@ -536,6 +536,7 @@ fn main() {
536 } 536 }
537 537
538 #[test] 538 #[test]
539 #[ignore = "FIXME: non-trait assoc items completion is unsupported yet, see FIXME in the import_assets.rs for more details"]
539 fn associated_struct_const_unqualified() { 540 fn associated_struct_const_unqualified() {
540 check_assist( 541 check_assist(
541 qualify_path, 542 qualify_path,
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
index 88fe2fe90..4f0ef52ca 100644
--- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -1,5 +1,5 @@
1use hir::ModuleDef; 1use hir::ModuleDef;
2use ide_db::helpers::mod_path_to_ast; 2use ide_db::helpers::{import_assets::NameToImport, mod_path_to_ast};
3use ide_db::items_locator; 3use ide_db::items_locator;
4use itertools::Itertools; 4use itertools::Itertools;
5use syntax::{ 5use syntax::{
@@ -65,20 +65,24 @@ pub(crate) fn replace_derive_with_manual_impl(
65 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; 65 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
66 let current_crate = current_module.krate(); 66 let current_crate = current_module.krate();
67 67
68 let found_traits = 68 let found_traits = items_locator::items_with_name(
69 items_locator::with_exact_name(&ctx.sema, current_crate, trait_token.text().to_string()) 69 &ctx.sema,
70 .into_iter() 70 current_crate,
71 .filter_map(|item| match ModuleDef::from(item.as_module_def_id()?) { 71 NameToImport::Exact(trait_token.text().to_string()),
72 ModuleDef::Trait(trait_) => Some(trait_), 72 items_locator::AssocItemSearch::Exclude,
73 _ => None, 73 Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT),
74 }) 74 )
75 .flat_map(|trait_| { 75 .filter_map(|item| match ModuleDef::from(item.as_module_def_id()?) {
76 current_module 76 ModuleDef::Trait(trait_) => Some(trait_),
77 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) 77 _ => None,
78 .as_ref() 78 })
79 .map(mod_path_to_ast) 79 .flat_map(|trait_| {
80 .zip(Some(trait_)) 80 current_module
81 }); 81 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
82 .as_ref()
83 .map(mod_path_to_ast)
84 .zip(Some(trait_))
85 });
82 86
83 let mut no_traits_found = true; 87 let mut no_traits_found = true;
84 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { 88 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index 08df2df3f..1ad017198 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -1,8 +1,10 @@
1//! Feature: completion with imports-on-the-fly 1//! Feature: completion with imports-on-the-fly
2//! 2//!
3//! When completing names in the current scope, proposes additional imports from other modules or crates, 3//! When completing names in the current scope, proposes additional imports from other modules or crates,
4//! if they can be qualified in the scope and their name contains all symbols from the completion input 4//! if they can be qualified in the scope and their name contains all symbols from the completion input.
5//! (case-insensitive, in any order or places). 5//!
6//! To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
7//! If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the contaning is checked case-insensitively.
6//! 8//!
7//! ``` 9//! ```
8//! fn main() { 10//! fn main() {
@@ -942,9 +944,94 @@ mod foo {
942} 944}
943 945
944fn main() { 946fn main() {
945 bar::Ass$0 947 bar::ASS$0
946}"#, 948}"#,
947 expect![[]], 949 expect![[]],
948 ) 950 )
949 } 951 }
952
953 #[test]
954 fn unqualified_assoc_items_are_omitted() {
955 check(
956 r#"
957mod something {
958 pub trait BaseTrait {
959 fn test_function() -> i32;
960 }
961
962 pub struct Item1;
963 pub struct Item2;
964
965 impl BaseTrait for Item1 {
966 fn test_function() -> i32 {
967 1
968 }
969 }
970
971 impl BaseTrait for Item2 {
972 fn test_function() -> i32 {
973 2
974 }
975 }
976}
977
978fn main() {
979 test_f$0
980}"#,
981 expect![[]],
982 )
983 }
984
985 #[test]
986 fn case_matters() {
987 check(
988 r#"
989mod foo {
990 pub const TEST_CONST: usize = 3;
991 pub fn test_function() -> i32 {
992 4
993 }
994}
995
996fn main() {
997 TE$0
998}"#,
999 expect![[r#"
1000 ct foo::TEST_CONST
1001 "#]],
1002 );
1003
1004 check(
1005 r#"
1006mod foo {
1007 pub const TEST_CONST: usize = 3;
1008 pub fn test_function() -> i32 {
1009 4
1010 }
1011}
1012
1013fn main() {
1014 te$0
1015}"#,
1016 expect![[r#"
1017 ct foo::TEST_CONST
1018 fn test_function() (foo::test_function) fn() -> i32
1019 "#]],
1020 );
1021
1022 check(
1023 r#"
1024mod foo {
1025 pub const TEST_CONST: usize = 3;
1026 pub fn test_function() -> i32 {
1027 4
1028 }
1029}
1030
1031fn main() {
1032 Te$0
1033}"#,
1034 expect![[]],
1035 );
1036 }
950} 1037}
diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs
index 74eb23360..628c1fb9b 100644
--- a/crates/ide_completion/src/completions/lifetime.rs
+++ b/crates/ide_completion/src/completions/lifetime.rs
@@ -1,4 +1,4 @@
1//! Completes lifetimes. 1//! Completes lifetimes and labels.
2use hir::ScopeDef; 2use hir::ScopeDef;
3 3
4use crate::{completions::Completions, context::CompletionContext}; 4use crate::{completions::Completions, context::CompletionContext};
@@ -29,6 +29,18 @@ pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext)
29 } 29 }
30} 30}
31 31
32/// Completes labels.
33pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) {
34 if !ctx.is_label_ref {
35 return;
36 }
37 ctx.scope.process_all_names(&mut |name, res| {
38 if let ScopeDef::Label(_) = res {
39 acc.add_resolution(ctx, name.to_string(), &res);
40 }
41 });
42}
43
32#[cfg(test)] 44#[cfg(test)]
33mod tests { 45mod tests {
34 use expect_test::{expect, Expect}; 46 use expect_test::{expect, Expect};
@@ -178,4 +190,96 @@ fn foo<'footime, 'lifetime: 'a$0>() {}
178 "#]], 190 "#]],
179 ); 191 );
180 } 192 }
193
194 #[test]
195 fn complete_label_in_loop() {
196 check(
197 r#"
198fn foo() {
199 'foop: loop {
200 break '$0
201 }
202}
203"#,
204 expect![[r#"
205 lb 'foop
206 "#]],
207 );
208 check(
209 r#"
210fn foo() {
211 'foop: loop {
212 continue '$0
213 }
214}
215"#,
216 expect![[r#"
217 lb 'foop
218 "#]],
219 );
220 }
221
222 #[test]
223 fn complete_label_in_block_nested() {
224 check(
225 r#"
226fn foo() {
227 'foop: {
228 'baap: {
229 break '$0
230 }
231 }
232}
233"#,
234 expect![[r#"
235 lb 'baap
236 lb 'foop
237 "#]],
238 );
239 }
240
241 #[test]
242 fn complete_label_in_loop_with_value() {
243 check(
244 r#"
245fn foo() {
246 'foop: loop {
247 break '$0 i32;
248 }
249}
250"#,
251 expect![[r#"
252 lb 'foop
253 "#]],
254 );
255 }
256
257 #[test]
258 fn complete_label_in_while_cond() {
259 check(
260 r#"
261fn foo() {
262 'outer: while { 'inner: loop { break '$0 } } {}
263}
264"#,
265 expect![[r#"
266 lb 'inner
267 lb 'outer
268 "#]],
269 );
270 }
271
272 #[test]
273 fn complete_label_in_for_iterable() {
274 check(
275 r#"
276fn foo() {
277 'outer: for _ in [{ 'inner: loop { break '$0 } }] {}
278}
279"#,
280 expect![[r#"
281 lb 'inner
282 "#]],
283 );
284 }
181} 285}
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 4c2b31084..67e2d6f6c 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -53,6 +53,7 @@ pub(crate) struct CompletionContext<'a> {
53 /// FIXME: `ActiveParameter` is string-based, which is very very wrong 53 /// FIXME: `ActiveParameter` is string-based, which is very very wrong
54 pub(super) active_parameter: Option<ActiveParameter>, 54 pub(super) active_parameter: Option<ActiveParameter>,
55 pub(super) is_param: bool, 55 pub(super) is_param: bool,
56 pub(super) is_label_ref: bool,
56 /// If a name-binding or reference to a const in a pattern. 57 /// If a name-binding or reference to a const in a pattern.
57 /// Irrefutable patterns (like let) are excluded. 58 /// Irrefutable patterns (like let) are excluded.
58 pub(super) is_pat_binding_or_const: bool, 59 pub(super) is_pat_binding_or_const: bool,
@@ -155,6 +156,7 @@ impl<'a> CompletionContext<'a> {
155 record_field_syntax: None, 156 record_field_syntax: None,
156 impl_def: None, 157 impl_def: None,
157 active_parameter: ActiveParameter::at(db, position), 158 active_parameter: ActiveParameter::at(db, position),
159 is_label_ref: false,
158 is_param: false, 160 is_param: false,
159 is_pat_binding_or_const: false, 161 is_pat_binding_or_const: false,
160 is_irrefutable_pat_binding: false, 162 is_irrefutable_pat_binding: false,
@@ -468,12 +470,24 @@ impl<'a> CompletionContext<'a> {
468 ) { 470 ) {
469 self.lifetime_syntax = 471 self.lifetime_syntax =
470 find_node_at_offset(original_file, lifetime.syntax().text_range().start()); 472 find_node_at_offset(original_file, lifetime.syntax().text_range().start());
471 if lifetime.syntax().parent().map_or(false, |p| p.kind() != syntax::SyntaxKind::ERROR) { 473 if let Some(parent) = lifetime.syntax().parent() {
472 self.lifetime_allowed = true; 474 if parent.kind() == syntax::SyntaxKind::ERROR {
473 } 475 return;
474 if let Some(_) = lifetime.syntax().parent().and_then(ast::LifetimeParam::cast) { 476 }
475 self.lifetime_param_syntax = 477
476 self.sema.find_node_at_offset_with_macros(original_file, offset); 478 match_ast! {
479 match parent {
480 ast::LifetimeParam(_it) => {
481 self.lifetime_allowed = true;
482 self.lifetime_param_syntax =
483 self.sema.find_node_at_offset_with_macros(original_file, offset);
484 },
485 ast::BreakExpr(_it) => self.is_label_ref = true,
486 ast::ContinueExpr(_it) => self.is_label_ref = true,
487 ast::Label(_it) => (),
488 _ => self.lifetime_allowed = true,
489 }
490 }
477 } 491 }
478 } 492 }
479 493
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index 7a0eb6a96..5ac1cb48d 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -14,7 +14,10 @@ mod completions;
14use completions::flyimport::position_for_import; 14use completions::flyimport::position_for_import;
15use ide_db::{ 15use ide_db::{
16 base_db::FilePosition, 16 base_db::FilePosition,
17 helpers::{import_assets::LocatedImport, insert_use::ImportScope}, 17 helpers::{
18 import_assets::{LocatedImport, NameToImport},
19 insert_use::ImportScope,
20 },
18 items_locator, RootDatabase, 21 items_locator, RootDatabase,
19}; 22};
20use text_edit::TextEdit; 23use text_edit::TextEdit;
@@ -131,6 +134,7 @@ pub fn completions(
131 completions::mod_::complete_mod(&mut acc, &ctx); 134 completions::mod_::complete_mod(&mut acc, &ctx);
132 completions::flyimport::import_on_the_fly(&mut acc, &ctx); 135 completions::flyimport::import_on_the_fly(&mut acc, &ctx);
133 completions::lifetime::complete_lifetime(&mut acc, &ctx); 136 completions::lifetime::complete_lifetime(&mut acc, &ctx);
137 completions::lifetime::complete_label(&mut acc, &ctx);
134 138
135 Some(acc) 139 Some(acc)
136} 140}
@@ -150,15 +154,19 @@ pub fn resolve_completion_edits(
150 let current_module = ctx.sema.scope(position_for_import).module()?; 154 let current_module = ctx.sema.scope(position_for_import).module()?;
151 let current_crate = current_module.krate(); 155 let current_crate = current_module.krate();
152 156
153 let (import_path, item_to_import) = 157 let (import_path, item_to_import) = items_locator::items_with_name(
154 items_locator::with_exact_name(&ctx.sema, current_crate, imported_name) 158 &ctx.sema,
155 .into_iter() 159 current_crate,
156 .filter_map(|candidate| { 160 NameToImport::Exact(imported_name),
157 current_module 161 items_locator::AssocItemSearch::Include,
158 .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind) 162 Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT),
159 .zip(Some(candidate)) 163 )
160 }) 164 .filter_map(|candidate| {
161 .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?; 165 current_module
166 .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
167 .zip(Some(candidate))
168 })
169 .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?;
162 let import = 170 let import =
163 LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path)); 171 LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path));
164 172
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs
index 2b6e9ebd1..23e00aa47 100644
--- a/crates/ide_completion/src/render.rs
+++ b/crates/ide_completion/src/render.rs
@@ -219,6 +219,7 @@ impl<'a> Render<'a> {
219 hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam, 219 hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
220 }), 220 }),
221 ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local), 221 ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local),
222 ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label),
222 ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => { 223 ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => {
223 CompletionItemKind::SymbolKind(SymbolKind::SelfParam) 224 CompletionItemKind::SymbolKind(SymbolKind::SelfParam)
224 } 225 }
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index 7c8844e95..1881c746f 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -61,7 +61,7 @@ pub struct FirstSegmentUnresolved {
61} 61}
62 62
63/// A name that will be used during item lookups. 63/// A name that will be used during item lookups.
64#[derive(Debug)] 64#[derive(Debug, Clone)]
65pub enum NameToImport { 65pub enum NameToImport {
66 /// Requires items with names that exactly match the given string, case-sensitive. 66 /// Requires items with names that exactly match the given string, case-sensitive.
67 Exact(String), 67 Exact(String),
@@ -201,129 +201,101 @@ impl ImportAssets {
201 sema: &Semantics<RootDatabase>, 201 sema: &Semantics<RootDatabase>,
202 prefixed: Option<PrefixKind>, 202 prefixed: Option<PrefixKind>,
203 ) -> Vec<LocatedImport> { 203 ) -> Vec<LocatedImport> {
204 let items_with_candidate_name = match self.name_to_import() { 204 let _p = profile::span("import_assets::search_for");
205 NameToImport::Exact(exact_name) => items_locator::with_exact_name(
206 sema,
207 self.module_with_candidate.krate(),
208 exact_name.clone(),
209 ),
210 // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items:
211 // instead, we need to look up all trait impls for a certain struct and search through them only
212 // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032
213 // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup
214 // for the details
215 NameToImport::Fuzzy(fuzzy_name) => {
216 let (assoc_item_search, limit) = if self.import_candidate.is_trait_candidate() {
217 (AssocItemSearch::AssocItemsOnly, None)
218 } else {
219 (AssocItemSearch::Include, Some(DEFAULT_QUERY_SEARCH_LIMIT))
220 };
221
222 items_locator::with_similar_name(
223 sema,
224 self.module_with_candidate.krate(),
225 fuzzy_name.clone(),
226 assoc_item_search,
227 limit,
228 )
229 }
230 };
231 205
232 let scope_definitions = self.scope_definitions(sema); 206 let scope_definitions = self.scope_definitions(sema);
233 self.applicable_defs(sema.db, prefixed, items_with_candidate_name)
234 .into_iter()
235 .filter(|import| import.import_path.len() > 1)
236 .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import)))
237 .sorted_by_key(|import| import.import_path.clone())
238 .collect()
239 }
240
241 fn scope_definitions(&self, sema: &Semantics<RootDatabase>) -> FxHashSet<ScopeDef> {
242 let mut scope_definitions = FxHashSet::default();
243 sema.scope(&self.candidate_node).process_all_names(&mut |_, scope_def| {
244 scope_definitions.insert(scope_def);
245 });
246 scope_definitions
247 }
248
249 fn name_to_import(&self) -> &NameToImport {
250 match &self.import_candidate {
251 ImportCandidate::Path(candidate) => &candidate.name,
252 ImportCandidate::TraitAssocItem(candidate)
253 | ImportCandidate::TraitMethod(candidate) => &candidate.assoc_item_name,
254 }
255 }
256
257 fn applicable_defs(
258 &self,
259 db: &RootDatabase,
260 prefixed: Option<PrefixKind>,
261 items_with_candidate_name: FxHashSet<ItemInNs>,
262 ) -> FxHashSet<LocatedImport> {
263 let _p = profile::span("import_assets::applicable_defs");
264 let current_crate = self.module_with_candidate.krate(); 207 let current_crate = self.module_with_candidate.krate();
265
266 let mod_path = |item| { 208 let mod_path = |item| {
267 get_mod_path(db, item_for_path_search(db, item)?, &self.module_with_candidate, prefixed) 209 get_mod_path(
210 sema.db,
211 item_for_path_search(sema.db, item)?,
212 &self.module_with_candidate,
213 prefixed,
214 )
268 }; 215 };
269 216
270 match &self.import_candidate { 217 match &self.import_candidate {
271 ImportCandidate::Path(path_candidate) => { 218 ImportCandidate::Path(path_candidate) => {
272 path_applicable_imports(db, path_candidate, mod_path, items_with_candidate_name) 219 path_applicable_imports(sema, current_crate, path_candidate, mod_path)
220 }
221 ImportCandidate::TraitAssocItem(trait_candidate) => {
222 trait_applicable_items(sema, current_crate, trait_candidate, true, mod_path)
223 }
224 ImportCandidate::TraitMethod(trait_candidate) => {
225 trait_applicable_items(sema, current_crate, trait_candidate, false, mod_path)
273 } 226 }
274 ImportCandidate::TraitAssocItem(trait_candidate) => trait_applicable_items(
275 db,
276 current_crate,
277 trait_candidate,
278 true,
279 mod_path,
280 items_with_candidate_name,
281 ),
282 ImportCandidate::TraitMethod(trait_candidate) => trait_applicable_items(
283 db,
284 current_crate,
285 trait_candidate,
286 false,
287 mod_path,
288 items_with_candidate_name,
289 ),
290 } 227 }
228 .into_iter()
229 .filter(|import| import.import_path.len() > 1)
230 .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import)))
231 .sorted_by_key(|import| import.import_path.clone())
232 .collect()
233 }
234
235 fn scope_definitions(&self, sema: &Semantics<RootDatabase>) -> FxHashSet<ScopeDef> {
236 let _p = profile::span("import_assets::scope_definitions");
237 let mut scope_definitions = FxHashSet::default();
238 sema.scope(&self.candidate_node).process_all_names(&mut |_, scope_def| {
239 scope_definitions.insert(scope_def);
240 });
241 scope_definitions
291 } 242 }
292} 243}
293 244
294fn path_applicable_imports( 245fn path_applicable_imports(
295 db: &RootDatabase, 246 sema: &Semantics<RootDatabase>,
247 current_crate: Crate,
296 path_candidate: &PathImportCandidate, 248 path_candidate: &PathImportCandidate,
297 mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy, 249 mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy,
298 items_with_candidate_name: FxHashSet<ItemInNs>,
299) -> FxHashSet<LocatedImport> { 250) -> FxHashSet<LocatedImport> {
300 let _p = profile::span("import_assets::path_applicable_imports"); 251 let _p = profile::span("import_assets::path_applicable_imports");
301 252
302 let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier { 253 match &path_candidate.qualifier {
303 None => { 254 None => {
304 return items_with_candidate_name 255 items_locator::items_with_name(
305 .into_iter() 256 sema,
306 .filter_map(|item| { 257 current_crate,
307 let mut mod_path = mod_path(item)?; 258 path_candidate.name.clone(),
308 if let Some(assoc_item) = item_as_assoc(db, item) { 259 // FIXME: we could look up assoc items by the input and propose those in completion,
309 mod_path.push_segment(assoc_item.name(db)?); 260 // but that requries more preparation first:
310 } 261 // * store non-trait assoc items in import_map to fully enable this lookup
311 Some(LocatedImport::new(mod_path.clone(), item, item, Some(mod_path))) 262 // * ensure that does not degrade the performance (bencmark it)
312 }) 263 // * write more logic to check for corresponding trait presence requirement (we're unable to flyimport multiple item right now)
313 .collect(); 264 // * improve the associated completion item matching and/or scoring to ensure no noisy completions appear
265 //
266 // see also an ignored test under FIXME comment in the qualify_path.rs module
267 AssocItemSearch::Exclude,
268 Some(DEFAULT_QUERY_SEARCH_LIMIT),
269 )
270 .filter_map(|item| {
271 let mod_path = mod_path(item)?;
272 Some(LocatedImport::new(mod_path.clone(), item, item, Some(mod_path)))
273 })
274 .collect()
314 } 275 }
315 Some(first_segment_unresolved) => ( 276 Some(first_segment_unresolved) => {
316 first_segment_unresolved.fist_segment.to_string(), 277 let unresolved_qualifier =
317 path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier), 278 path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier);
318 ), 279 let unresolved_first_segment = first_segment_unresolved.fist_segment.text();
319 }; 280 items_locator::items_with_name(
320 281 sema,
321 items_with_candidate_name 282 current_crate,
322 .into_iter() 283 path_candidate.name.clone(),
323 .filter_map(|item| { 284 AssocItemSearch::Include,
324 import_for_item(db, mod_path, &unresolved_first_segment, &unresolved_qualifier, item) 285 Some(DEFAULT_QUERY_SEARCH_LIMIT),
325 }) 286 )
326 .collect() 287 .filter_map(|item| {
288 import_for_item(
289 sema.db,
290 mod_path,
291 unresolved_first_segment,
292 &unresolved_qualifier,
293 item,
294 )
295 })
296 .collect()
297 }
298 }
327} 299}
328 300
329fn import_for_item( 301fn import_for_item(
@@ -438,25 +410,31 @@ fn module_with_segment_name(
438} 410}
439 411
440fn trait_applicable_items( 412fn trait_applicable_items(
441 db: &RootDatabase, 413 sema: &Semantics<RootDatabase>,
442 current_crate: Crate, 414 current_crate: Crate,
443 trait_candidate: &TraitImportCandidate, 415 trait_candidate: &TraitImportCandidate,
444 trait_assoc_item: bool, 416 trait_assoc_item: bool,
445 mod_path: impl Fn(ItemInNs) -> Option<ModPath>, 417 mod_path: impl Fn(ItemInNs) -> Option<ModPath>,
446 items_with_candidate_name: FxHashSet<ItemInNs>,
447) -> FxHashSet<LocatedImport> { 418) -> FxHashSet<LocatedImport> {
448 let _p = profile::span("import_assets::trait_applicable_items"); 419 let _p = profile::span("import_assets::trait_applicable_items");
449 let mut required_assoc_items = FxHashSet::default();
450 420
451 let trait_candidates = items_with_candidate_name 421 let db = sema.db;
452 .into_iter() 422
453 .filter_map(|input| item_as_assoc(db, input)) 423 let mut required_assoc_items = FxHashSet::default();
454 .filter_map(|assoc| { 424 let trait_candidates = items_locator::items_with_name(
455 let assoc_item_trait = assoc.containing_trait(db)?; 425 sema,
456 required_assoc_items.insert(assoc); 426 current_crate,
457 Some(assoc_item_trait.into()) 427 trait_candidate.assoc_item_name.clone(),
458 }) 428 AssocItemSearch::AssocItemsOnly,
459 .collect(); 429 Some(DEFAULT_QUERY_SEARCH_LIMIT),
430 )
431 .filter_map(|input| item_as_assoc(db, input))
432 .filter_map(|assoc| {
433 let assoc_item_trait = assoc.containing_trait(db)?;
434 required_assoc_items.insert(assoc);
435 Some(assoc_item_trait.into())
436 })
437 .collect();
460 438
461 let mut located_imports = FxHashSet::default(); 439 let mut located_imports = FxHashSet::default();
462 440
@@ -565,10 +543,6 @@ impl ImportCandidate {
565 ) -> Option<Self> { 543 ) -> Option<Self> {
566 path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name)) 544 path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name))
567 } 545 }
568
569 fn is_trait_candidate(&self) -> bool {
570 matches!(self, ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_))
571 }
572} 546}
573 547
574fn path_import_candidate( 548fn path_import_candidate(
diff --git a/crates/ide_db/src/items_locator.rs b/crates/ide_db/src/items_locator.rs
index 8a7f02935..ef796b6f7 100644
--- a/crates/ide_db/src/items_locator.rs
+++ b/crates/ide_db/src/items_locator.rs
@@ -1,6 +1,7 @@
1//! This module contains an import search functionality that is provided to the assists module. 1//! This module has the functionality to search the project and its dependencies for a certain item,
2//! Later, this should be moved away to a separate crate that is accessible from the assists module. 2//! by its name and a few criteria.
3 3//! The main reason for this module to exist is the fact that project's items and dependencies' items
4//! are located in different caches, with different APIs.
4use either::Either; 5use either::Either;
5use hir::{ 6use hir::{
6 import_map::{self, ImportKind}, 7 import_map::{self, ImportKind},
@@ -10,122 +11,121 @@ use syntax::{ast, AstNode, SyntaxKind::NAME};
10 11
11use crate::{ 12use crate::{
12 defs::{Definition, NameClass}, 13 defs::{Definition, NameClass},
14 helpers::import_assets::NameToImport,
13 symbol_index::{self, FileSymbol}, 15 symbol_index::{self, FileSymbol},
14 RootDatabase, 16 RootDatabase,
15}; 17};
16use rustc_hash::FxHashSet;
17
18pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40;
19 18
20pub fn with_exact_name( 19/// A value to use, when uncertain which limit to pick.
21 sema: &Semantics<'_, RootDatabase>, 20pub const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40;
22 krate: Crate,
23 exact_name: String,
24) -> FxHashSet<ItemInNs> {
25 let _p = profile::span("find_exact_imports");
26 find_items(
27 sema,
28 krate,
29 {
30 let mut local_query = symbol_index::Query::new(exact_name.clone());
31 local_query.exact();
32 local_query.limit(DEFAULT_QUERY_SEARCH_LIMIT);
33 local_query
34 },
35 import_map::Query::new(exact_name)
36 .limit(DEFAULT_QUERY_SEARCH_LIMIT)
37 .name_only()
38 .search_mode(import_map::SearchMode::Equals)
39 .case_sensitive(),
40 )
41}
42 21
43#[derive(Debug)] 22/// Three possible ways to search for the name in associated and/or other items.
23#[derive(Debug, Clone, Copy)]
44pub enum AssocItemSearch { 24pub enum AssocItemSearch {
25 /// Search for the name in both associated and other items.
45 Include, 26 Include,
27 /// Search for the name in other items only.
46 Exclude, 28 Exclude,
29 /// Search for the name in the associated items only.
47 AssocItemsOnly, 30 AssocItemsOnly,
48} 31}
49 32
50pub fn with_similar_name( 33/// Searches for importable items with the given name in the crate and its dependencies.
51 sema: &Semantics<'_, RootDatabase>, 34pub fn items_with_name<'a>(
35 sema: &'a Semantics<'_, RootDatabase>,
52 krate: Crate, 36 krate: Crate,
53 fuzzy_search_string: String, 37 name: NameToImport,
54 assoc_item_search: AssocItemSearch, 38 assoc_item_search: AssocItemSearch,
55 limit: Option<usize>, 39 limit: Option<usize>,
56) -> FxHashSet<ItemInNs> { 40) -> impl Iterator<Item = ItemInNs> + 'a {
57 let _p = profile::span("find_similar_imports"); 41 let _p = profile::span("items_with_name").detail(|| {
42 format!(
43 "Name: {}, crate: {:?}, assoc items: {:?}, limit: {:?}",
44 name.text(),
45 assoc_item_search,
46 krate.display_name(sema.db).map(|name| name.to_string()),
47 limit,
48 )
49 });
50
51 let (mut local_query, mut external_query) = match name {
52 NameToImport::Exact(exact_name) => {
53 let mut local_query = symbol_index::Query::new(exact_name.clone());
54 local_query.exact();
58 55
59 let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) 56 let external_query = import_map::Query::new(exact_name)
60 .search_mode(import_map::SearchMode::Fuzzy) 57 .name_only()
61 .name_only(); 58 .search_mode(import_map::SearchMode::Equals)
59 .case_sensitive();
62 60
63 match assoc_item_search { 61 (local_query, external_query)
64 AssocItemSearch::Include => {}
65 AssocItemSearch::Exclude => {
66 external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem);
67 } 62 }
68 AssocItemSearch::AssocItemsOnly => { 63 NameToImport::Fuzzy(fuzzy_search_string) => {
69 external_query = external_query.assoc_items_only(); 64 let mut local_query = symbol_index::Query::new(fuzzy_search_string.clone());
65
66 let mut external_query = import_map::Query::new(fuzzy_search_string.clone())
67 .search_mode(import_map::SearchMode::Fuzzy)
68 .name_only();
69 match assoc_item_search {
70 AssocItemSearch::Include => {}
71 AssocItemSearch::Exclude => {
72 external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem);
73 }
74 AssocItemSearch::AssocItemsOnly => {
75 external_query = external_query.assoc_items_only();
76 }
77 }
78
79 if fuzzy_search_string.to_lowercase() != fuzzy_search_string {
80 local_query.case_sensitive();
81 external_query = external_query.case_sensitive();
82 }
83
84 (local_query, external_query)
70 } 85 }
71 } 86 };
72
73 let mut local_query = symbol_index::Query::new(fuzzy_search_string);
74 87
75 if let Some(limit) = limit { 88 if let Some(limit) = limit {
76 external_query = external_query.limit(limit); 89 external_query = external_query.limit(limit);
77 local_query.limit(limit); 90 local_query.limit(limit);
78 } 91 }
79 92
80 find_items(sema, krate, local_query, external_query) 93 find_items(sema, krate, assoc_item_search, local_query, external_query)
81 .into_iter()
82 .filter(move |&item| match assoc_item_search {
83 AssocItemSearch::Include => true,
84 AssocItemSearch::Exclude => !is_assoc_item(item, sema.db),
85 AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db),
86 })
87 .collect()
88} 94}
89 95
90fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool { 96fn find_items<'a>(
91 item.as_module_def_id() 97 sema: &'a Semantics<'_, RootDatabase>,
92 .and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db))
93 .is_some()
94}
95
96fn find_items(
97 sema: &Semantics<'_, RootDatabase>,
98 krate: Crate, 98 krate: Crate,
99 assoc_item_search: AssocItemSearch,
99 local_query: symbol_index::Query, 100 local_query: symbol_index::Query,
100 external_query: import_map::Query, 101 external_query: import_map::Query,
101) -> FxHashSet<ItemInNs> { 102) -> impl Iterator<Item = ItemInNs> + 'a {
102 let _p = profile::span("find_similar_imports"); 103 let _p = profile::span("find_items");
103 let db = sema.db; 104 let db = sema.db;
104 105
105 // Query dependencies first. 106 let external_importables =
106 let mut candidates = krate 107 krate.query_external_importables(db, external_query).map(|external_importable| {
107 .query_external_importables(db, external_query) 108 match external_importable {
108 .map(|external_importable| match external_importable { 109 Either::Left(module_def) => ItemInNs::from(module_def),
109 Either::Left(module_def) => ItemInNs::from(module_def), 110 Either::Right(macro_def) => ItemInNs::from(macro_def),
110 Either::Right(macro_def) => ItemInNs::from(macro_def), 111 }
111 }) 112 });
112 .collect::<FxHashSet<_>>();
113 113
114 // Query the local crate using the symbol index. 114 // Query the local crate using the symbol index.
115 let local_results = symbol_index::crate_symbols(db, krate.into(), local_query); 115 let local_results = symbol_index::crate_symbols(db, krate.into(), local_query)
116 116 .into_iter()
117 candidates.extend( 117 .filter_map(move |local_candidate| get_name_definition(sema, &local_candidate))
118 local_results 118 .filter_map(|name_definition_to_import| match name_definition_to_import {
119 .into_iter() 119 Definition::ModuleDef(module_def) => Some(ItemInNs::from(module_def)),
120 .filter_map(|local_candidate| get_name_definition(sema, &local_candidate)) 120 Definition::Macro(macro_def) => Some(ItemInNs::from(macro_def)),
121 .filter_map(|name_definition_to_import| match name_definition_to_import { 121 _ => None,
122 Definition::ModuleDef(module_def) => Some(ItemInNs::from(module_def)), 122 });
123 Definition::Macro(macro_def) => Some(ItemInNs::from(macro_def)), 123
124 _ => None, 124 external_importables.chain(local_results).filter(move |&item| match assoc_item_search {
125 }), 125 AssocItemSearch::Include => true,
126 ); 126 AssocItemSearch::Exclude => !is_assoc_item(item, sema.db),
127 127 AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db),
128 candidates 128 })
129} 129}
130 130
131fn get_name_definition( 131fn get_name_definition(
@@ -144,3 +144,9 @@ fn get_name_definition(
144 let name = ast::Name::cast(candidate_name_node)?; 144 let name = ast::Name::cast(candidate_name_node)?;
145 NameClass::classify(sema, &name)?.defined(sema.db) 145 NameClass::classify(sema, &name)?.defined(sema.db)
146} 146}
147
148fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool {
149 item.as_module_def_id()
150 .and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db))
151 .is_some()
152}
diff --git a/crates/ide_db/src/symbol_index.rs b/crates/ide_db/src/symbol_index.rs
index 9ed9568ce..35e382b5c 100644
--- a/crates/ide_db/src/symbol_index.rs
+++ b/crates/ide_db/src/symbol_index.rs
@@ -52,6 +52,7 @@ pub struct Query {
52 only_types: bool, 52 only_types: bool,
53 libs: bool, 53 libs: bool,
54 exact: bool, 54 exact: bool,
55 case_sensitive: bool,
55 limit: usize, 56 limit: usize,
56} 57}
57 58
@@ -64,6 +65,7 @@ impl Query {
64 only_types: false, 65 only_types: false,
65 libs: false, 66 libs: false,
66 exact: false, 67 exact: false,
68 case_sensitive: false,
67 limit: usize::max_value(), 69 limit: usize::max_value(),
68 } 70 }
69 } 71 }
@@ -80,6 +82,10 @@ impl Query {
80 self.exact = true; 82 self.exact = true;
81 } 83 }
82 84
85 pub fn case_sensitive(&mut self) {
86 self.case_sensitive = true;
87 }
88
83 pub fn limit(&mut self, limit: usize) { 89 pub fn limit(&mut self, limit: usize) {
84 self.limit = limit 90 self.limit = limit
85 } 91 }
@@ -326,8 +332,14 @@ impl Query {
326 if self.only_types && !symbol.kind.is_type() { 332 if self.only_types && !symbol.kind.is_type() {
327 continue; 333 continue;
328 } 334 }
329 if self.exact && symbol.name != self.query { 335 if self.exact {
330 continue; 336 if symbol.name != self.query {
337 continue;
338 }
339 } else if self.case_sensitive {
340 if self.query.chars().any(|c| !symbol.name.contains(c)) {
341 continue;
342 }
331 } 343 }
332 344
333 res.push(symbol.clone()); 345 res.push(symbol.clone());