aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/references.rs
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2021-02-12 20:30:55 +0000
committerLukas Wirth <[email protected]>2021-02-12 20:30:55 +0000
commitc395dd1032b66e28995189a26ed688b243d3cef8 (patch)
treebe88733f10b9cd71bcff79b9c9a4dba7f6cbdf55 /crates/ide/src/references.rs
parent88253907f4bc3beaa7b8f2e58cb652f653f92d56 (diff)
Implement constructor usage search for almost all items
For all struct kinds, unions and enums, as well as for record- and tuple-variants but not for unit-variants, as these have no trailing character we can anchor the search to. Functionality wise it is implemented though.
Diffstat (limited to 'crates/ide/src/references.rs')
-rw-r--r--crates/ide/src/references.rs294
1 files changed, 189 insertions, 105 deletions
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index c7cefb3b6..8a491f077 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,43 @@ 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(def @ hir::ModuleDef::Adt(_))
74 | Definition::ModuleDef(def @ hir::ModuleDef::Variant(_)) => {
75 refs.for_each(|it| {
76 it.retain(|reference| {
77 reference
78 .name
79 .as_name_ref()
80 .map_or(false, |name_ref| is_lit_name_ref(sema, def, name_ref))
81 })
82 });
83 usages.references.retain(|_, it| !it.is_empty());
84 }
85 _ => {}
86 }
73 } 87 }
74 let nav = def.try_to_nav(sema.db)?; 88 let nav = def.try_to_nav(sema.db)?;
75 let decl_range = nav.focus_or_full_range(); 89 let decl_range = nav.focus_or_full_range();
@@ -89,9 +103,9 @@ fn find_def(
89 sema: &Semantics<RootDatabase>, 103 sema: &Semantics<RootDatabase>,
90 syntax: &SyntaxNode, 104 syntax: &SyntaxNode,
91 position: FilePosition, 105 position: FilePosition,
92 opt_name: Option<ast::Name>,
93) -> Option<Definition> { 106) -> Option<Definition> {
94 if let Some(name) = opt_name { 107 if let Some(name) = sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset)
108 {
95 let class = NameClass::classify(sema, &name)?; 109 let class = NameClass::classify(sema, &name)?;
96 Some(class.referenced_or_defined(sema.db)) 110 Some(class.referenced_or_defined(sema.db))
97 } else if let Some(lifetime) = 111 } else if let Some(lifetime) =
@@ -134,95 +148,96 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio
134 None 148 None
135} 149}
136 150
137fn get_struct_def_name_for_struct_literal_search( 151fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> {
138 sema: &Semantics<RootDatabase>, 152 let token = syntax.token_at_offset(position.offset).right_biased()?;
139 syntax: &SyntaxNode, 153 let kind = token.kind();
140 position: FilePosition, 154 if kind == T![;] {
141) -> Option<ast::Name> { 155 ast::Struct::cast(token.parent())
142 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { 156 .filter(|struct_| struct_.field_list().is_none())
143 if right.kind() != T!['{'] && right.kind() != T!['('] { 157 .and_then(|struct_| struct_.name())
144 return None; 158 } else if kind == T!['{'] {
145 } 159 match_ast! {
146 if let Some(name) = 160 match (token.parent()) {
147 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) 161 ast::RecordFieldList(rfl) => match_ast! {
148 { 162 match (rfl.syntax().parent()?) {
149 return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); 163 ast::Variant(it) => it.name(),
164 ast::Struct(it) => it.name(),
165 ast::Union(it) => it.name(),
166 _ => None,
167 }
168 },
169 ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(),
170 _ => None,
171 }
150 } 172 }
151 if sema 173 } else if kind == T!['('] {
152 .find_node_at_offset_with_descend::<ast::GenericParamList>( 174 let tfl = ast::TupleFieldList::cast(token.parent())?;
153 &syntax, 175 match_ast! {
154 left.text_range().start(), 176 match (tfl.syntax().parent()?) {
155 ) 177 ast::Variant(it) => it.name(),
156 .is_some() 178 ast::Struct(it) => it.name(),
157 { 179 _ => None,
158 return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); 180 }
159 } 181 }
182 } else {
183 None
160 } 184 }
161 None
162} 185}
163 186
164fn get_enum_def_name_for_struct_literal_search( 187fn is_enum_lit_name_ref(
165 sema: &Semantics<RootDatabase>, 188 sema: &Semantics<RootDatabase>,
166 syntax: &SyntaxNode, 189 enum_: hir::Enum,
167 position: FilePosition, 190 name_ref: &ast::NameRef,
168) -> Option<ast::Name> { 191) -> bool {
169 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { 192 for ancestor in name_ref.syntax().ancestors() {
170 if right.kind() != T!['{'] && right.kind() != T!['('] { 193 match_ast! {
171 return None; 194 match ancestor {
172 } 195 ast::PathExpr(path_expr) => {
173 if let Some(name) = 196 return matches!(
174 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) 197 path_expr.path().and_then(|p| sema.resolve_path(&p)),
175 { 198 Some(PathResolution::Def(hir::ModuleDef::Variant(variant)))
176 return name.syntax().ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); 199 if variant.parent_enum(sema.db) == enum_
177 } 200 )
178 if sema 201 },
179 .find_node_at_offset_with_descend::<ast::GenericParamList>( 202 ast::RecordExpr(record_expr) => {
180 &syntax, 203 return matches!(
181 left.text_range().start(), 204 record_expr.path().and_then(|p| sema.resolve_path(&p)),
182 ) 205 Some(PathResolution::Def(hir::ModuleDef::Variant(variant)))
183 .is_some() 206 if variant.parent_enum(sema.db) == enum_
184 { 207 )
185 return left.ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); 208 },
209 _ => (),
210 }
186 } 211 }
187 } 212 }
188 None 213 false
189} 214}
190 215
191fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { 216fn is_lit_name_ref(
192 name_ref 217 sema: &Semantics<RootDatabase>,
193 .syntax() 218 def: hir::ModuleDef,
194 .ancestors() 219 name_ref: &ast::NameRef,
195 .find_map(ast::CallExpr::cast) 220) -> bool {
196 .and_then(|c| match c.expr()? { 221 for ancestor in name_ref.syntax().ancestors() {
197 ast::Expr::PathExpr(p) => { 222 match_ast! {
198 Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref)) 223 match ancestor {
224 ast::PathExpr(path_expr) => {
225 return matches!(
226 path_expr.path().and_then(|p| sema.resolve_path(&p)),
227 Some(PathResolution::Def(def2)) if def == def2
228 )
229 },
230 ast::RecordExpr(record_expr) => {
231 return matches!(
232 record_expr.path().and_then(|p| sema.resolve_path(&p)),
233 Some(PathResolution::Def(def2)) if def == def2
234 )
235 },
236 _ => (),
199 } 237 }
200 _ => None, 238 }
201 }) 239 }
202 .unwrap_or(false) 240 false
203}
204
205fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool {
206 name_ref
207 .syntax()
208 .ancestors()
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
216fn is_enum_lit_name_ref(name_ref: &ast::NameRef) -> bool {
217 name_ref
218 .syntax()
219 .ancestors()
220 .find_map(ast::PathExpr::cast)
221 .and_then(|p| p.path())
222 .and_then(|p| p.qualifier())
223 .and_then(|p| p.segment())
224 .map(|p| p.name_ref().as_ref() == Some(name_ref))
225 .unwrap_or(false)
226} 241}
227 242
228#[cfg(test)] 243#[cfg(test)]
@@ -313,22 +328,91 @@ fn main() {
313 } 328 }
314 329
315 #[test] 330 #[test]
331 fn test_struct_literal_for_union() {
332 check(
333 r#"
334union Foo $0{
335 x: u32
336}
337
338fn main() {
339 let f: Foo;
340 f = Foo { x: 1 };
341}
342"#,
343 expect![[r#"
344 Foo Union FileId(0) 0..24 6..9
345
346 FileId(0) 62..65
347 "#]],
348 );
349 }
350
351 #[test]
316 fn test_enum_after_space() { 352 fn test_enum_after_space() {
317 check( 353 check(
318 r#" 354 r#"
319enum Foo $0{ 355enum Foo $0{
320 A, 356 A,
321 B, 357 B(),
358 C{},
322} 359}
323fn main() { 360fn main() {
324 let f: Foo; 361 let f: Foo;
325 f = Foo::A; 362 f = Foo::A;
363 f = Foo::B();
364 f = Foo::C{};
326} 365}
327"#, 366"#,
328 expect![[r#" 367 expect![[r#"
329 Foo Enum FileId(0) 0..26 5..8 368 Foo Enum FileId(0) 0..37 5..8
330 369
331 FileId(0) 63..66 370 FileId(0) 74..77
371 FileId(0) 90..93
372 FileId(0) 108..111
373 "#]],
374 );
375 }
376
377 #[test]
378 fn test_variant_record_after_space() {
379 check(
380 r#"
381enum Foo {
382 A $0{ n: i32 },
383 B,
384}
385fn main() {
386 let f: Foo;
387 f = Foo::B;
388 f = Foo::A { n: 92 };
389}
390"#,
391 expect![[r#"
392 A Variant FileId(0) 15..27 15..16
393
394 FileId(0) 95..96
395 "#]],
396 );
397 }
398 #[test]
399 fn test_variant_tuple_before_paren() {
400 check(
401 r#"
402enum Foo {
403 A$0(i32),
404 B,
405}
406fn main() {
407 let f: Foo;
408 f = Foo::B;
409 f = Foo::A(92);
410}
411"#,
412 expect![[r#"
413 A Variant FileId(0) 15..21 15..16
414
415 FileId(0) 89..90
332 "#]], 416 "#]],
333 ); 417 );
334 } 418 }