diff options
-rw-r--r-- | .github/workflows/release.yaml | 3 | ||||
-rw-r--r-- | crates/hir/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/hir/src/semantics.rs | 4 | ||||
-rw-r--r-- | crates/hir_def/src/body/scope.rs | 57 | ||||
-rw-r--r-- | crates/hir_def/src/resolver.rs | 6 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/qualify_path.rs | 1 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | 34 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/flyimport.rs | 93 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/lifetime.rs | 106 | ||||
-rw-r--r-- | crates/ide_completion/src/context.rs | 26 | ||||
-rw-r--r-- | crates/ide_completion/src/lib.rs | 28 | ||||
-rw-r--r-- | crates/ide_completion/src/render.rs | 1 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/import_assets.rs | 212 | ||||
-rw-r--r-- | crates/ide_db/src/items_locator.rs | 178 | ||||
-rw-r--r-- | crates/ide_db/src/symbol_index.rs | 16 |
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; | |||
8 | use crate::{ | 8 | use 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 { | |||
40 | pub struct ScopeData { | 40 | pub 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 | ||
146 | fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { | 171 | fn 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 | ||
414 | impl Scope { | 415 | impl 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 @@ | |||
1 | use hir::ModuleDef; | 1 | use hir::ModuleDef; |
2 | use ide_db::helpers::mod_path_to_ast; | 2 | use ide_db::helpers::{import_assets::NameToImport, mod_path_to_ast}; |
3 | use ide_db::items_locator; | 3 | use ide_db::items_locator; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use syntax::{ | 5 | use 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 | ||
944 | fn main() { | 946 | fn 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#" | ||
957 | mod 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 | |||
978 | fn main() { | ||
979 | test_f$0 | ||
980 | }"#, | ||
981 | expect![[]], | ||
982 | ) | ||
983 | } | ||
984 | |||
985 | #[test] | ||
986 | fn case_matters() { | ||
987 | check( | ||
988 | r#" | ||
989 | mod foo { | ||
990 | pub const TEST_CONST: usize = 3; | ||
991 | pub fn test_function() -> i32 { | ||
992 | 4 | ||
993 | } | ||
994 | } | ||
995 | |||
996 | fn main() { | ||
997 | TE$0 | ||
998 | }"#, | ||
999 | expect![[r#" | ||
1000 | ct foo::TEST_CONST | ||
1001 | "#]], | ||
1002 | ); | ||
1003 | |||
1004 | check( | ||
1005 | r#" | ||
1006 | mod foo { | ||
1007 | pub const TEST_CONST: usize = 3; | ||
1008 | pub fn test_function() -> i32 { | ||
1009 | 4 | ||
1010 | } | ||
1011 | } | ||
1012 | |||
1013 | fn 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#" | ||
1024 | mod foo { | ||
1025 | pub const TEST_CONST: usize = 3; | ||
1026 | pub fn test_function() -> i32 { | ||
1027 | 4 | ||
1028 | } | ||
1029 | } | ||
1030 | |||
1031 | fn 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. |
2 | use hir::ScopeDef; | 2 | use hir::ScopeDef; |
3 | 3 | ||
4 | use crate::{completions::Completions, context::CompletionContext}; | 4 | use 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. | ||
33 | pub(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)] |
33 | mod tests { | 45 | mod 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#" | ||
198 | fn foo() { | ||
199 | 'foop: loop { | ||
200 | break '$0 | ||
201 | } | ||
202 | } | ||
203 | "#, | ||
204 | expect![[r#" | ||
205 | lb 'foop | ||
206 | "#]], | ||
207 | ); | ||
208 | check( | ||
209 | r#" | ||
210 | fn 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#" | ||
226 | fn 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#" | ||
245 | fn 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#" | ||
261 | fn 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#" | ||
276 | fn 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; | |||
14 | use completions::flyimport::position_for_import; | 14 | use completions::flyimport::position_for_import; |
15 | use ide_db::{ | 15 | use 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 | }; |
20 | use text_edit::TextEdit; | 23 | use 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)] |
65 | pub enum NameToImport { | 65 | pub 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 | ||
294 | fn path_applicable_imports( | 245 | fn 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 | ||
329 | fn import_for_item( | 301 | fn import_for_item( |
@@ -438,25 +410,31 @@ fn module_with_segment_name( | |||
438 | } | 410 | } |
439 | 411 | ||
440 | fn trait_applicable_items( | 412 | fn 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 | ||
574 | fn path_import_candidate( | 548 | fn 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. | ||
4 | use either::Either; | 5 | use either::Either; |
5 | use hir::{ | 6 | use hir::{ |
6 | import_map::{self, ImportKind}, | 7 | import_map::{self, ImportKind}, |
@@ -10,122 +11,121 @@ use syntax::{ast, AstNode, SyntaxKind::NAME}; | |||
10 | 11 | ||
11 | use crate::{ | 12 | use 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 | }; |
16 | use rustc_hash::FxHashSet; | ||
17 | |||
18 | pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40; | ||
19 | 18 | ||
20 | pub fn with_exact_name( | 19 | /// A value to use, when uncertain which limit to pick. |
21 | sema: &Semantics<'_, RootDatabase>, | 20 | pub 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)] | ||
44 | pub enum AssocItemSearch { | 24 | pub 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 | ||
50 | pub fn with_similar_name( | 33 | /// Searches for importable items with the given name in the crate and its dependencies. |
51 | sema: &Semantics<'_, RootDatabase>, | 34 | pub 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 | ||
90 | fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool { | 96 | fn 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 | |||
96 | fn 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 | ||
131 | fn get_name_definition( | 131 | fn 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 | |||
148 | fn 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()); |