aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/references.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-02-14 16:17:03 +0000
committerGitHub <[email protected]>2021-02-14 16:17:03 +0000
commita0322defc375d7fd3890225768a85a31f2bdb0be (patch)
treee09243414c566cde0ecdb500e7441bf21ead1d0e /crates/ide/src/references.rs
parentd50a37d3aa473937919030b39587df3d93f9bd8c (diff)
parent8ac6041bcf7c970104939bdbdda5af4873ebd472 (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]>
Diffstat (limited to 'crates/ide/src/references.rs')
-rw-r--r--crates/ide/src/references.rs270
1 files changed, 170 insertions, 100 deletions
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
12pub(crate) mod rename; 12pub(crate) mod rename;
13 13
14use hir::Semantics; 14use hir::{PathResolution, Semantics};
15use ide_db::{ 15use 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;
22use syntax::{ 22use 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
28use crate::{display::TryToNav, FilePosition, NavigationTarget}; 28use 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
137fn get_struct_def_name_for_struct_literal_search( 148fn 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
164fn get_enum_def_name_for_struct_literal_search( 184fn 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
191fn 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
205fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { 211fn 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
216fn is_enum_lit_name_ref(name_ref: &ast::NameRef) -> bool { 217fn 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#"
320union Foo $0{
321 x: u32
322}
323
324fn 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#"
319enum Foo $0{ 341enum Foo $0{
320 A, 342 A,
321 B, 343 B(),
344 C{},
322} 345}
323fn main() { 346fn 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#"
367enum Foo {
368 A $0{ n: i32 },
369 B,
370}
371fn 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#"
388enum Foo {
389 A$0(i32),
390 B,
391}
392fn 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 }