diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-02-14 16:17:03 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-02-14 16:17:03 +0000 |
commit | a0322defc375d7fd3890225768a85a31f2bdb0be (patch) | |
tree | e09243414c566cde0ecdb500e7441bf21ead1d0e | |
parent | d50a37d3aa473937919030b39587df3d93f9bd8c (diff) | |
parent | 8ac6041bcf7c970104939bdbdda5af4873ebd472 (diff) |
Merge #7656
7656: Implement constructor usage search for almost all items r=matklad a=Veykril
This PR moves the filering for enum constructors to the HIR, with this unprefixed variants as well as when the enum has been renamed via use will then still show up properly.
We now walk the ast of the `NameRef` up until we find a `PathExpr`(which also handles `CallExpr` for tuple-type structs and variants already) or a `RecordExpr`. For enum search we then take the `path` out of that expression and do a resolution on it to compare it with the definition enum.
With this PR we now support searching for all constructor literals, Unit-, Tuple- and Record-Structs, Unit-, Tuple- and Record-Variants as well as Unions.
There is one shortcoming due to how the search is triggered. Unit Variants constructors can't be searched as we have no position for it to kick off the search(since a comma doesn't have to exist for the last variant).
Closes #2549 though it doesn't implement it as outlined in the issue since the reference kind was removed recently, though I believe the approach taken here is better personally.
Co-authored-by: Lukas Wirth <[email protected]>
-rw-r--r-- | crates/assists/src/handlers/inline_local_variable.rs | 5 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 270 |
2 files changed, 172 insertions, 103 deletions
diff --git a/crates/assists/src/handlers/inline_local_variable.rs b/crates/assists/src/handlers/inline_local_variable.rs index 8d28431cf..9b228443f 100644 --- a/crates/assists/src/handlers/inline_local_variable.rs +++ b/crates/assists/src/handlers/inline_local_variable.rs | |||
@@ -1,6 +1,5 @@ | |||
1 | use std::collections::HashMap; | ||
2 | |||
3 | use ide_db::{defs::Definition, search::FileReference}; | 1 | use ide_db::{defs::Definition, search::FileReference}; |
2 | use rustc_hash::FxHashMap; | ||
4 | use syntax::{ | 3 | use syntax::{ |
5 | ast::{self, AstNode, AstToken}, | 4 | ast::{self, AstNode, AstToken}, |
6 | TextRange, | 5 | TextRange, |
@@ -111,7 +110,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
111 | .collect::<Result<_, _>>() | 110 | .collect::<Result<_, _>>() |
112 | .map(|b| (file_id, b)) | 111 | .map(|b| (file_id, b)) |
113 | }) | 112 | }) |
114 | .collect::<Result<HashMap<_, Vec<_>>, _>>()?; | 113 | .collect::<Result<FxHashMap<_, Vec<_>>, _>>()?; |
115 | 114 | ||
116 | let init_str = initializer_expr.syntax().text().to_string(); | 115 | let init_str = initializer_expr.syntax().text().to_string(); |
117 | let init_in_paren = format!("({})", &init_str); | 116 | let init_in_paren = format!("({})", &init_str); |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 17086f7d4..a83b82f1b 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -11,7 +11,7 @@ | |||
11 | 11 | ||
12 | pub(crate) mod rename; | 12 | pub(crate) mod rename; |
13 | 13 | ||
14 | use hir::Semantics; | 14 | use hir::{PathResolution, Semantics}; |
15 | use ide_db::{ | 15 | use ide_db::{ |
16 | base_db::FileId, | 16 | base_db::FileId, |
17 | defs::{Definition, NameClass, NameRefClass}, | 17 | defs::{Definition, NameClass, NameRefClass}, |
@@ -22,7 +22,7 @@ use rustc_hash::FxHashMap; | |||
22 | use syntax::{ | 22 | use syntax::{ |
23 | algo::find_node_at_offset, | 23 | algo::find_node_at_offset, |
24 | ast::{self, NameOwner}, | 24 | ast::{self, NameOwner}, |
25 | AstNode, SyntaxNode, TextRange, TokenAtOffset, T, | 25 | match_ast, AstNode, SyntaxNode, TextRange, T, |
26 | }; | 26 | }; |
27 | 27 | ||
28 | use crate::{display::TryToNav, FilePosition, NavigationTarget}; | 28 | use crate::{display::TryToNav, FilePosition, NavigationTarget}; |
@@ -47,29 +47,40 @@ pub(crate) fn find_all_refs( | |||
47 | let _p = profile::span("find_all_refs"); | 47 | let _p = profile::span("find_all_refs"); |
48 | let syntax = sema.parse(position.file_id).syntax().clone(); | 48 | let syntax = sema.parse(position.file_id).syntax().clone(); |
49 | 49 | ||
50 | let (opt_name, ctor_filter): (_, Option<fn(&_) -> bool>) = if let Some(name) = | 50 | let (def, is_literal_search) = |
51 | get_struct_def_name_for_struct_literal_search(&sema, &syntax, position) | 51 | if let Some(name) = get_name_of_item_declaration(&syntax, position) { |
52 | { | 52 | (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true) |
53 | ( | 53 | } else { |
54 | Some(name), | 54 | (find_def(&sema, &syntax, position)?, false) |
55 | Some(|name_ref| is_record_lit_name_ref(name_ref) || is_call_expr_name_ref(name_ref)), | 55 | }; |
56 | ) | ||
57 | } else if let Some(name) = get_enum_def_name_for_struct_literal_search(&sema, &syntax, position) | ||
58 | { | ||
59 | (Some(name), Some(is_enum_lit_name_ref)) | ||
60 | } else { | ||
61 | (sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset), None) | ||
62 | }; | ||
63 | |||
64 | let def = find_def(&sema, &syntax, position, opt_name)?; | ||
65 | 56 | ||
66 | let mut usages = def.usages(sema).set_scope(search_scope).all(); | 57 | let mut usages = def.usages(sema).set_scope(search_scope).all(); |
67 | if let Some(ctor_filter) = ctor_filter { | 58 | if is_literal_search { |
68 | // filter for constructor-literals | 59 | // filter for constructor-literals |
69 | usages.references.values_mut().for_each(|it| { | 60 | let refs = usages.references.values_mut(); |
70 | it.retain(|reference| reference.name.as_name_ref().map_or(false, ctor_filter)); | 61 | match def { |
71 | }); | 62 | Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(enum_))) => { |
72 | usages.references.retain(|_, it| !it.is_empty()); | 63 | refs.for_each(|it| { |
64 | it.retain(|reference| { | ||
65 | reference | ||
66 | .name | ||
67 | .as_name_ref() | ||
68 | .map_or(false, |name_ref| is_enum_lit_name_ref(sema, enum_, name_ref)) | ||
69 | }) | ||
70 | }); | ||
71 | usages.references.retain(|_, it| !it.is_empty()); | ||
72 | } | ||
73 | Definition::ModuleDef(hir::ModuleDef::Adt(_)) | ||
74 | | Definition::ModuleDef(hir::ModuleDef::Variant(_)) => { | ||
75 | refs.for_each(|it| { | ||
76 | it.retain(|reference| { | ||
77 | reference.name.as_name_ref().map_or(false, is_lit_name_ref) | ||
78 | }) | ||
79 | }); | ||
80 | usages.references.retain(|_, it| !it.is_empty()); | ||
81 | } | ||
82 | _ => {} | ||
83 | } | ||
73 | } | 84 | } |
74 | let nav = def.try_to_nav(sema.db)?; | 85 | let nav = def.try_to_nav(sema.db)?; |
75 | let decl_range = nav.focus_or_full_range(); | 86 | let decl_range = nav.focus_or_full_range(); |
@@ -89,9 +100,9 @@ fn find_def( | |||
89 | sema: &Semantics<RootDatabase>, | 100 | sema: &Semantics<RootDatabase>, |
90 | syntax: &SyntaxNode, | 101 | syntax: &SyntaxNode, |
91 | position: FilePosition, | 102 | position: FilePosition, |
92 | opt_name: Option<ast::Name>, | ||
93 | ) -> Option<Definition> { | 103 | ) -> Option<Definition> { |
94 | if let Some(name) = opt_name { | 104 | if let Some(name) = sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset) |
105 | { | ||
95 | let class = NameClass::classify(sema, &name)?; | 106 | let class = NameClass::classify(sema, &name)?; |
96 | Some(class.referenced_or_defined(sema.db)) | 107 | Some(class.referenced_or_defined(sema.db)) |
97 | } else if let Some(lifetime) = | 108 | } else if let Some(lifetime) = |
@@ -134,95 +145,85 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio | |||
134 | None | 145 | None |
135 | } | 146 | } |
136 | 147 | ||
137 | fn get_struct_def_name_for_struct_literal_search( | 148 | fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> { |
138 | sema: &Semantics<RootDatabase>, | 149 | let token = syntax.token_at_offset(position.offset).right_biased()?; |
139 | syntax: &SyntaxNode, | 150 | let kind = token.kind(); |
140 | position: FilePosition, | 151 | if kind == T![;] { |
141 | ) -> Option<ast::Name> { | 152 | ast::Struct::cast(token.parent()) |
142 | if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { | 153 | .filter(|struct_| struct_.field_list().is_none()) |
143 | if right.kind() != T!['{'] && right.kind() != T!['('] { | 154 | .and_then(|struct_| struct_.name()) |
144 | return None; | 155 | } else if kind == T!['{'] { |
145 | } | 156 | match_ast! { |
146 | if let Some(name) = | 157 | match (token.parent()) { |
147 | sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) | 158 | ast::RecordFieldList(rfl) => match_ast! { |
148 | { | 159 | match (rfl.syntax().parent()?) { |
149 | return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); | 160 | ast::Variant(it) => it.name(), |
161 | ast::Struct(it) => it.name(), | ||
162 | ast::Union(it) => it.name(), | ||
163 | _ => None, | ||
164 | } | ||
165 | }, | ||
166 | ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(), | ||
167 | _ => None, | ||
168 | } | ||
150 | } | 169 | } |
151 | if sema | 170 | } else if kind == T!['('] { |
152 | .find_node_at_offset_with_descend::<ast::GenericParamList>( | 171 | let tfl = ast::TupleFieldList::cast(token.parent())?; |
153 | &syntax, | 172 | match_ast! { |
154 | left.text_range().start(), | 173 | match (tfl.syntax().parent()?) { |
155 | ) | 174 | ast::Variant(it) => it.name(), |
156 | .is_some() | 175 | ast::Struct(it) => it.name(), |
157 | { | 176 | _ => None, |
158 | return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); | 177 | } |
159 | } | 178 | } |
179 | } else { | ||
180 | None | ||
160 | } | 181 | } |
161 | None | ||
162 | } | 182 | } |
163 | 183 | ||
164 | fn get_enum_def_name_for_struct_literal_search( | 184 | fn is_enum_lit_name_ref( |
165 | sema: &Semantics<RootDatabase>, | 185 | sema: &Semantics<RootDatabase>, |
166 | syntax: &SyntaxNode, | 186 | enum_: hir::Enum, |
167 | position: FilePosition, | 187 | name_ref: &ast::NameRef, |
168 | ) -> Option<ast::Name> { | 188 | ) -> bool { |
169 | if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { | 189 | let path_is_variant_of_enum = |path: ast::Path| { |
170 | if right.kind() != T!['{'] && right.kind() != T!['('] { | 190 | matches!( |
171 | return None; | 191 | sema.resolve_path(&path), |
172 | } | 192 | Some(PathResolution::Def(hir::ModuleDef::Variant(variant))) |
173 | if let Some(name) = | 193 | if variant.parent_enum(sema.db) == enum_ |
174 | sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) | 194 | ) |
175 | { | 195 | }; |
176 | return name.syntax().ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); | ||
177 | } | ||
178 | if sema | ||
179 | .find_node_at_offset_with_descend::<ast::GenericParamList>( | ||
180 | &syntax, | ||
181 | left.text_range().start(), | ||
182 | ) | ||
183 | .is_some() | ||
184 | { | ||
185 | return left.ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); | ||
186 | } | ||
187 | } | ||
188 | None | ||
189 | } | ||
190 | |||
191 | fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { | ||
192 | name_ref | 196 | name_ref |
193 | .syntax() | 197 | .syntax() |
194 | .ancestors() | 198 | .ancestors() |
195 | .find_map(ast::CallExpr::cast) | 199 | .find_map(|ancestor| { |
196 | .and_then(|c| match c.expr()? { | 200 | match_ast! { |
197 | ast::Expr::PathExpr(p) => { | 201 | match ancestor { |
198 | Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref)) | 202 | ast::PathExpr(path_expr) => path_expr.path().map(path_is_variant_of_enum), |
203 | ast::RecordExpr(record_expr) => record_expr.path().map(path_is_variant_of_enum), | ||
204 | _ => None, | ||
205 | } | ||
199 | } | 206 | } |
200 | _ => None, | ||
201 | }) | 207 | }) |
202 | .unwrap_or(false) | 208 | .unwrap_or(false) |
203 | } | 209 | } |
204 | 210 | ||
205 | fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { | 211 | fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool { |
206 | name_ref | 212 | path.and_then(|path| path.segment()) |
207 | .syntax() | 213 | .and_then(|segment| segment.name_ref()) |
208 | .ancestors() | 214 | .map_or(false, |segment| segment == *name_ref) |
209 | .find_map(ast::RecordExpr::cast) | ||
210 | .and_then(|l| l.path()) | ||
211 | .and_then(|p| p.segment()) | ||
212 | .map(|p| p.name_ref().as_ref() == Some(name_ref)) | ||
213 | .unwrap_or(false) | ||
214 | } | 215 | } |
215 | 216 | ||
216 | fn is_enum_lit_name_ref(name_ref: &ast::NameRef) -> bool { | 217 | fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool { |
217 | name_ref | 218 | name_ref.syntax().ancestors().find_map(|ancestor| { |
218 | .syntax() | 219 | match_ast! { |
219 | .ancestors() | 220 | match ancestor { |
220 | .find_map(ast::PathExpr::cast) | 221 | ast::PathExpr(path_expr) => Some(path_ends_with(path_expr.path(), name_ref)), |
221 | .and_then(|p| p.path()) | 222 | ast::RecordExpr(record_expr) => Some(path_ends_with(record_expr.path(), name_ref)), |
222 | .and_then(|p| p.qualifier()) | 223 | _ => None, |
223 | .and_then(|p| p.segment()) | 224 | } |
224 | .map(|p| p.name_ref().as_ref() == Some(name_ref)) | 225 | } |
225 | .unwrap_or(false) | 226 | }).unwrap_or(false) |
226 | } | 227 | } |
227 | 228 | ||
228 | #[cfg(test)] | 229 | #[cfg(test)] |
@@ -313,22 +314,91 @@ fn main() { | |||
313 | } | 314 | } |
314 | 315 | ||
315 | #[test] | 316 | #[test] |
317 | fn test_struct_literal_for_union() { | ||
318 | check( | ||
319 | r#" | ||
320 | union Foo $0{ | ||
321 | x: u32 | ||
322 | } | ||
323 | |||
324 | fn main() { | ||
325 | let f: Foo; | ||
326 | f = Foo { x: 1 }; | ||
327 | } | ||
328 | "#, | ||
329 | expect![[r#" | ||
330 | Foo Union FileId(0) 0..24 6..9 | ||
331 | |||
332 | FileId(0) 62..65 | ||
333 | "#]], | ||
334 | ); | ||
335 | } | ||
336 | |||
337 | #[test] | ||
316 | fn test_enum_after_space() { | 338 | fn test_enum_after_space() { |
317 | check( | 339 | check( |
318 | r#" | 340 | r#" |
319 | enum Foo $0{ | 341 | enum Foo $0{ |
320 | A, | 342 | A, |
321 | B, | 343 | B(), |
344 | C{}, | ||
322 | } | 345 | } |
323 | fn main() { | 346 | fn main() { |
324 | let f: Foo; | 347 | let f: Foo; |
325 | f = Foo::A; | 348 | f = Foo::A; |
349 | f = Foo::B(); | ||
350 | f = Foo::C{}; | ||
326 | } | 351 | } |
327 | "#, | 352 | "#, |
328 | expect![[r#" | 353 | expect![[r#" |
329 | Foo Enum FileId(0) 0..26 5..8 | 354 | Foo Enum FileId(0) 0..37 5..8 |
330 | 355 | ||
331 | FileId(0) 63..66 | 356 | FileId(0) 74..77 |
357 | FileId(0) 90..93 | ||
358 | FileId(0) 108..111 | ||
359 | "#]], | ||
360 | ); | ||
361 | } | ||
362 | |||
363 | #[test] | ||
364 | fn test_variant_record_after_space() { | ||
365 | check( | ||
366 | r#" | ||
367 | enum Foo { | ||
368 | A $0{ n: i32 }, | ||
369 | B, | ||
370 | } | ||
371 | fn main() { | ||
372 | let f: Foo; | ||
373 | f = Foo::B; | ||
374 | f = Foo::A { n: 92 }; | ||
375 | } | ||
376 | "#, | ||
377 | expect![[r#" | ||
378 | A Variant FileId(0) 15..27 15..16 | ||
379 | |||
380 | FileId(0) 95..96 | ||
381 | "#]], | ||
382 | ); | ||
383 | } | ||
384 | #[test] | ||
385 | fn test_variant_tuple_before_paren() { | ||
386 | check( | ||
387 | r#" | ||
388 | enum Foo { | ||
389 | A$0(i32), | ||
390 | B, | ||
391 | } | ||
392 | fn main() { | ||
393 | let f: Foo; | ||
394 | f = Foo::B; | ||
395 | f = Foo::A(92); | ||
396 | } | ||
397 | "#, | ||
398 | expect![[r#" | ||
399 | A Variant FileId(0) 15..21 15..16 | ||
400 | |||
401 | FileId(0) 89..90 | ||
332 | "#]], | 402 | "#]], |
333 | ); | 403 | ); |
334 | } | 404 | } |