aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/references.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/references.rs')
-rw-r--r--crates/ide/src/references.rs694
1 files changed, 694 insertions, 0 deletions
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
new file mode 100644
index 000000000..0a76ec6b4
--- /dev/null
+++ b/crates/ide/src/references.rs
@@ -0,0 +1,694 @@
1//! This module implements a reference search.
2//! First, the element at the cursor position must be either an `ast::Name`
3//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we
4//! try to resolve the direct tree parent of this element, otherwise we
5//! already have a definition and just need to get its HIR together with
6//! some information that is needed for futher steps of searching.
7//! After that, we collect files that might contain references and look
8//! for text occurrences of the identifier. If there's an `ast::NameRef`
9//! at the index that the match starts at and its tree parent is
10//! resolved to the search element definition, we get a reference.
11
12mod rename;
13
14use hir::Semantics;
15use ide_db::{
16 defs::{classify_name, classify_name_ref, Definition},
17 search::SearchScope,
18 RootDatabase,
19};
20use syntax::{
21 algo::find_node_at_offset,
22 ast::{self, NameOwner},
23 AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
24};
25
26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
27
28pub(crate) use self::rename::rename;
29
30pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind};
31
32#[derive(Debug, Clone)]
33pub struct ReferenceSearchResult {
34 declaration: Declaration,
35 references: Vec<Reference>,
36}
37
38#[derive(Debug, Clone)]
39pub struct Declaration {
40 pub nav: NavigationTarget,
41 pub kind: ReferenceKind,
42 pub access: Option<ReferenceAccess>,
43}
44
45impl ReferenceSearchResult {
46 pub fn declaration(&self) -> &Declaration {
47 &self.declaration
48 }
49
50 pub fn decl_target(&self) -> &NavigationTarget {
51 &self.declaration.nav
52 }
53
54 pub fn references(&self) -> &[Reference] {
55 &self.references
56 }
57
58 /// Total number of references
59 /// At least 1 since all valid references should
60 /// Have a declaration
61 pub fn len(&self) -> usize {
62 self.references.len() + 1
63 }
64}
65
66// allow turning ReferenceSearchResult into an iterator
67// over References
68impl IntoIterator for ReferenceSearchResult {
69 type Item = Reference;
70 type IntoIter = std::vec::IntoIter<Reference>;
71
72 fn into_iter(mut self) -> Self::IntoIter {
73 let mut v = Vec::with_capacity(self.len());
74 v.push(Reference {
75 file_range: FileRange {
76 file_id: self.declaration.nav.file_id,
77 range: self.declaration.nav.focus_or_full_range(),
78 },
79 kind: self.declaration.kind,
80 access: self.declaration.access,
81 });
82 v.append(&mut self.references);
83 v.into_iter()
84 }
85}
86
87pub(crate) fn find_all_refs(
88 sema: &Semantics<RootDatabase>,
89 position: FilePosition,
90 search_scope: Option<SearchScope>,
91) -> Option<RangeInfo<ReferenceSearchResult>> {
92 let _p = profile::span("find_all_refs");
93 let syntax = sema.parse(position.file_id).syntax().clone();
94
95 let (opt_name, search_kind) = if let Some(name) =
96 get_struct_def_name_for_struct_literal_search(&sema, &syntax, position)
97 {
98 (Some(name), ReferenceKind::StructLiteral)
99 } else {
100 (
101 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset),
102 ReferenceKind::Other,
103 )
104 };
105
106 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?;
107
108 let references = def
109 .find_usages(sema, search_scope)
110 .into_iter()
111 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
112 .collect();
113
114 let decl_range = def.try_to_nav(sema.db)?.focus_or_full_range();
115
116 let declaration = Declaration {
117 nav: def.try_to_nav(sema.db)?,
118 kind: ReferenceKind::Other,
119 access: decl_access(&def, &syntax, decl_range),
120 };
121
122 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
123}
124
125fn find_name(
126 sema: &Semantics<RootDatabase>,
127 syntax: &SyntaxNode,
128 position: FilePosition,
129 opt_name: Option<ast::Name>,
130) -> Option<RangeInfo<Definition>> {
131 if let Some(name) = opt_name {
132 let def = classify_name(sema, &name)?.definition(sema.db);
133 let range = name.syntax().text_range();
134 return Some(RangeInfo::new(range, def));
135 }
136 let name_ref =
137 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?;
138 let def = classify_name_ref(sema, &name_ref)?.definition(sema.db);
139 let range = name_ref.syntax().text_range();
140 Some(RangeInfo::new(range, def))
141}
142
143fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
144 match def {
145 Definition::Local(_) | Definition::Field(_) => {}
146 _ => return None,
147 };
148
149 let stmt = find_node_at_offset::<ast::LetStmt>(syntax, range.start())?;
150 if stmt.initializer().is_some() {
151 let pat = stmt.pat()?;
152 if let ast::Pat::IdentPat(it) = pat {
153 if it.mut_token().is_some() {
154 return Some(ReferenceAccess::Write);
155 }
156 }
157 }
158
159 None
160}
161
162fn get_struct_def_name_for_struct_literal_search(
163 sema: &Semantics<RootDatabase>,
164 syntax: &SyntaxNode,
165 position: FilePosition,
166) -> Option<ast::Name> {
167 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
168 if right.kind() != SyntaxKind::L_CURLY && right.kind() != SyntaxKind::L_PAREN {
169 return None;
170 }
171 if let Some(name) =
172 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start())
173 {
174 return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name());
175 }
176 if sema
177 .find_node_at_offset_with_descend::<ast::GenericParamList>(
178 &syntax,
179 left.text_range().start(),
180 )
181 .is_some()
182 {
183 return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name());
184 }
185 }
186 None
187}
188
189#[cfg(test)]
190mod tests {
191 use crate::{
192 mock_analysis::{analysis_and_position, MockAnalysis},
193 Declaration, Reference, ReferenceSearchResult, SearchScope,
194 };
195
196 #[test]
197 fn test_struct_literal_after_space() {
198 let refs = get_all_refs(
199 r#"
200struct Foo <|>{
201 a: i32,
202}
203impl Foo {
204 fn f() -> i32 { 42 }
205}
206fn main() {
207 let f: Foo;
208 f = Foo {a: Foo::f()};
209}
210"#,
211 );
212 check_result(
213 refs,
214 "Foo STRUCT FileId(1) 0..26 7..10 Other",
215 &["FileId(1) 101..104 StructLiteral"],
216 );
217 }
218
219 #[test]
220 fn test_struct_literal_before_space() {
221 let refs = get_all_refs(
222 r#"
223struct Foo<|> {}
224 fn main() {
225 let f: Foo;
226 f = Foo {};
227}
228"#,
229 );
230 check_result(
231 refs,
232 "Foo STRUCT FileId(1) 0..13 7..10 Other",
233 &["FileId(1) 41..44 Other", "FileId(1) 54..57 StructLiteral"],
234 );
235 }
236
237 #[test]
238 fn test_struct_literal_with_generic_type() {
239 let refs = get_all_refs(
240 r#"
241struct Foo<T> <|>{}
242 fn main() {
243 let f: Foo::<i32>;
244 f = Foo {};
245}
246"#,
247 );
248 check_result(
249 refs,
250 "Foo STRUCT FileId(1) 0..16 7..10 Other",
251 &["FileId(1) 64..67 StructLiteral"],
252 );
253 }
254
255 #[test]
256 fn test_struct_literal_for_tuple() {
257 let refs = get_all_refs(
258 r#"
259struct Foo<|>(i32);
260
261fn main() {
262 let f: Foo;
263 f = Foo(1);
264}
265"#,
266 );
267 check_result(
268 refs,
269 "Foo STRUCT FileId(1) 0..16 7..10 Other",
270 &["FileId(1) 54..57 StructLiteral"],
271 );
272 }
273
274 #[test]
275 fn test_find_all_refs_for_local() {
276 let refs = get_all_refs(
277 r#"
278fn main() {
279 let mut i = 1;
280 let j = 1;
281 i = i<|> + j;
282
283 {
284 i = 0;
285 }
286
287 i = 5;
288}"#,
289 );
290 check_result(
291 refs,
292 "i IDENT_PAT FileId(1) 24..25 Other Write",
293 &[
294 "FileId(1) 50..51 Other Write",
295 "FileId(1) 54..55 Other Read",
296 "FileId(1) 76..77 Other Write",
297 "FileId(1) 94..95 Other Write",
298 ],
299 );
300 }
301
302 #[test]
303 fn search_filters_by_range() {
304 let refs = get_all_refs(
305 r#"
306fn foo() {
307 let spam<|> = 92;
308 spam + spam
309}
310fn bar() {
311 let spam = 92;
312 spam + spam
313}
314"#,
315 );
316 check_result(
317 refs,
318 "spam IDENT_PAT FileId(1) 19..23 Other",
319 &["FileId(1) 34..38 Other Read", "FileId(1) 41..45 Other Read"],
320 );
321 }
322
323 #[test]
324 fn test_find_all_refs_for_param_inside() {
325 let refs = get_all_refs(
326 r#"
327fn foo(i : u32) -> u32 {
328 i<|>
329}
330"#,
331 );
332 check_result(refs, "i IDENT_PAT FileId(1) 7..8 Other", &["FileId(1) 29..30 Other Read"]);
333 }
334
335 #[test]
336 fn test_find_all_refs_for_fn_param() {
337 let refs = get_all_refs(
338 r#"
339fn foo(i<|> : u32) -> u32 {
340 i
341}
342"#,
343 );
344 check_result(refs, "i IDENT_PAT FileId(1) 7..8 Other", &["FileId(1) 29..30 Other Read"]);
345 }
346
347 #[test]
348 fn test_find_all_refs_field_name() {
349 let refs = get_all_refs(
350 r#"
351//- /lib.rs
352struct Foo {
353 pub spam<|>: u32,
354}
355
356fn main(s: Foo) {
357 let f = s.spam;
358}
359"#,
360 );
361 check_result(
362 refs,
363 "spam RECORD_FIELD FileId(1) 17..30 21..25 Other",
364 &["FileId(1) 67..71 Other Read"],
365 );
366 }
367
368 #[test]
369 fn test_find_all_refs_impl_item_name() {
370 let refs = get_all_refs(
371 r#"
372struct Foo;
373impl Foo {
374 fn f<|>(&self) { }
375}
376"#,
377 );
378 check_result(refs, "f FN FileId(1) 27..43 30..31 Other", &[]);
379 }
380
381 #[test]
382 fn test_find_all_refs_enum_var_name() {
383 let refs = get_all_refs(
384 r#"
385enum Foo {
386 A,
387 B<|>,
388 C,
389}
390"#,
391 );
392 check_result(refs, "B VARIANT FileId(1) 22..23 22..23 Other", &[]);
393 }
394
395 #[test]
396 fn test_find_all_refs_two_modules() {
397 let (analysis, pos) = analysis_and_position(
398 r#"
399//- /lib.rs
400pub mod foo;
401pub mod bar;
402
403fn f() {
404 let i = foo::Foo { n: 5 };
405}
406
407//- /foo.rs
408use crate::bar;
409
410pub struct Foo {
411 pub n: u32,
412}
413
414fn f() {
415 let i = bar::Bar { n: 5 };
416}
417
418//- /bar.rs
419use crate::foo;
420
421pub struct Bar {
422 pub n: u32,
423}
424
425fn f() {
426 let i = foo::Foo<|> { n: 5 };
427}
428"#,
429 );
430 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
431 check_result(
432 refs,
433 "Foo STRUCT FileId(2) 17..51 28..31 Other",
434 &["FileId(1) 53..56 StructLiteral", "FileId(3) 79..82 StructLiteral"],
435 );
436 }
437
438 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
439 // So, there are two references: the first one is a definition of the `foo` module,
440 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`.
441 #[test]
442 fn test_find_all_refs_decl_module() {
443 let (analysis, pos) = analysis_and_position(
444 r#"
445//- /lib.rs
446mod foo<|>;
447
448use foo::Foo;
449
450fn f() {
451 let i = Foo { n: 5 };
452}
453
454//- /foo.rs
455pub struct Foo {
456 pub n: u32,
457}
458"#,
459 );
460 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
461 check_result(refs, "foo SOURCE_FILE FileId(2) 0..35 Other", &["FileId(1) 14..17 Other"]);
462 }
463
464 #[test]
465 fn test_find_all_refs_super_mod_vis() {
466 let (analysis, pos) = analysis_and_position(
467 r#"
468//- /lib.rs
469mod foo;
470
471//- /foo.rs
472mod some;
473use some::Foo;
474
475fn f() {
476 let i = Foo { n: 5 };
477}
478
479//- /foo/some.rs
480pub(super) struct Foo<|> {
481 pub n: u32,
482}
483"#,
484 );
485 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
486 check_result(
487 refs,
488 "Foo STRUCT FileId(3) 0..41 18..21 Other",
489 &["FileId(2) 20..23 Other", "FileId(2) 47..50 StructLiteral"],
490 );
491 }
492
493 #[test]
494 fn test_find_all_refs_with_scope() {
495 let code = r#"
496 //- /lib.rs
497 mod foo;
498 mod bar;
499
500 pub fn quux<|>() {}
501
502 //- /foo.rs
503 fn f() { super::quux(); }
504
505 //- /bar.rs
506 fn f() { super::quux(); }
507 "#;
508
509 let (mock, pos) = MockAnalysis::with_files_and_position(code);
510 let bar = mock.id_of("/bar.rs");
511 let analysis = mock.analysis();
512
513 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
514 check_result(
515 refs,
516 "quux FN FileId(1) 19..35 26..30 Other",
517 &["FileId(2) 16..20 StructLiteral", "FileId(3) 16..20 StructLiteral"],
518 );
519
520 let refs =
521 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap();
522 check_result(
523 refs,
524 "quux FN FileId(1) 19..35 26..30 Other",
525 &["FileId(3) 16..20 StructLiteral"],
526 );
527 }
528
529 #[test]
530 fn test_find_all_refs_macro_def() {
531 let refs = get_all_refs(
532 r#"
533#[macro_export]
534macro_rules! m1<|> { () => (()) }
535
536fn foo() {
537 m1();
538 m1();
539}
540"#,
541 );
542 check_result(
543 refs,
544 "m1 MACRO_CALL FileId(1) 0..46 29..31 Other",
545 &["FileId(1) 63..65 StructLiteral", "FileId(1) 73..75 StructLiteral"],
546 );
547 }
548
549 #[test]
550 fn test_basic_highlight_read_write() {
551 let refs = get_all_refs(
552 r#"
553fn foo() {
554 let mut i<|> = 0;
555 i = i + 1;
556}
557"#,
558 );
559 check_result(
560 refs,
561 "i IDENT_PAT FileId(1) 23..24 Other Write",
562 &["FileId(1) 34..35 Other Write", "FileId(1) 38..39 Other Read"],
563 );
564 }
565
566 #[test]
567 fn test_basic_highlight_field_read_write() {
568 let refs = get_all_refs(
569 r#"
570struct S {
571 f: u32,
572}
573
574fn foo() {
575 let mut s = S{f: 0};
576 s.f<|> = 0;
577}
578"#,
579 );
580 check_result(
581 refs,
582 "f RECORD_FIELD FileId(1) 15..21 15..16 Other",
583 &["FileId(1) 55..56 Other Read", "FileId(1) 68..69 Other Write"],
584 );
585 }
586
587 #[test]
588 fn test_basic_highlight_decl_no_write() {
589 let refs = get_all_refs(
590 r#"
591fn foo() {
592 let i<|>;
593 i = 1;
594}
595"#,
596 );
597 check_result(refs, "i IDENT_PAT FileId(1) 19..20 Other", &["FileId(1) 26..27 Other Write"]);
598 }
599
600 #[test]
601 fn test_find_struct_function_refs_outside_module() {
602 let refs = get_all_refs(
603 r#"
604mod foo {
605 pub struct Foo;
606
607 impl Foo {
608 pub fn new<|>() -> Foo {
609 Foo
610 }
611 }
612}
613
614fn main() {
615 let _f = foo::Foo::new();
616}
617"#,
618 );
619 check_result(
620 refs,
621 "new FN FileId(1) 54..101 61..64 Other",
622 &["FileId(1) 146..149 StructLiteral"],
623 );
624 }
625
626 #[test]
627 fn test_find_all_refs_nested_module() {
628 let code = r#"
629 //- /lib.rs
630 mod foo {
631 mod bar;
632 }
633
634 fn f<|>() {}
635
636 //- /foo/bar.rs
637 use crate::f;
638
639 fn g() {
640 f();
641 }
642 "#;
643
644 let (analysis, pos) = analysis_and_position(code);
645 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
646 check_result(
647 refs,
648 "f FN FileId(1) 26..35 29..30 Other",
649 &["FileId(2) 11..12 Other", "FileId(2) 28..29 StructLiteral"],
650 );
651 }
652
653 fn get_all_refs(ra_fixture: &str) -> ReferenceSearchResult {
654 let (analysis, position) = analysis_and_position(ra_fixture);
655 analysis.find_all_refs(position, None).unwrap().unwrap()
656 }
657
658 fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) {
659 res.declaration().assert_match(expected_decl);
660 assert_eq!(res.references.len(), expected_refs.len());
661 res.references()
662 .iter()
663 .enumerate()
664 .for_each(|(i, r)| ref_assert_match(r, expected_refs[i]));
665 }
666
667 impl Declaration {
668 fn debug_render(&self) -> String {
669 let mut s = format!("{} {:?}", self.nav.debug_render(), self.kind);
670 if let Some(access) = self.access {
671 s.push_str(&format!(" {:?}", access));
672 }
673 s
674 }
675
676 fn assert_match(&self, expected: &str) {
677 let actual = self.debug_render();
678 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
679 }
680 }
681
682 fn ref_debug_render(r: &Reference) -> String {
683 let mut s = format!("{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind);
684 if let Some(access) = r.access {
685 s.push_str(&format!(" {:?}", access));
686 }
687 s
688 }
689
690 fn ref_assert_match(r: &Reference, expected: &str) {
691 let actual = ref_debug_render(r);
692 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
693 }
694}