aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r--crates/ra_ide/src/lib.rs4
-rw-r--r--crates/ra_ide/src/references.rs253
2 files changed, 201 insertions, 56 deletions
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 7b187eba3..4d8deb21c 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -75,7 +75,9 @@ pub use crate::{
75 inlay_hints::{InlayHint, InlayKind}, 75 inlay_hints::{InlayHint, InlayKind},
76 line_index::{LineCol, LineIndex}, 76 line_index::{LineCol, LineIndex},
77 line_index_utils::translate_offset_with_edit, 77 line_index_utils::translate_offset_with_edit,
78 references::{Reference, ReferenceKind, ReferenceSearchResult, SearchScope}, 78 references::{
79 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope,
80 },
79 runnables::{Runnable, RunnableKind}, 81 runnables::{Runnable, RunnableKind},
80 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 82 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
81 syntax_highlighting::HighlightedRange, 83 syntax_highlighting::HighlightedRange,
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
index 5a3ec4eb9..4e52e0e7b 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -19,8 +19,9 @@ use once_cell::unsync::Lazy;
19use ra_db::{SourceDatabase, SourceDatabaseExt}; 19use ra_db::{SourceDatabase, SourceDatabaseExt};
20use ra_prof::profile; 20use ra_prof::profile;
21use ra_syntax::{ 21use ra_syntax::{
22 algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextUnit, 22 algo::find_node_at_offset,
23 TokenAtOffset, 23 ast::{self, NameOwner},
24 match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, TextUnit, TokenAtOffset,
24}; 25};
25 26
26use crate::{ 27use crate::{
@@ -37,15 +38,22 @@ pub use self::search_scope::SearchScope;
37 38
38#[derive(Debug, Clone)] 39#[derive(Debug, Clone)]
39pub struct ReferenceSearchResult { 40pub struct ReferenceSearchResult {
40 declaration: NavigationTarget, 41 declaration: Declaration,
41 declaration_kind: ReferenceKind,
42 references: Vec<Reference>, 42 references: Vec<Reference>,
43} 43}
44 44
45#[derive(Debug, Clone)] 45#[derive(Debug, Clone)]
46pub struct Declaration {
47 pub nav: NavigationTarget,
48 pub kind: ReferenceKind,
49 pub access: Option<ReferenceAccess>,
50}
51
52#[derive(Debug, Clone)]
46pub struct Reference { 53pub struct Reference {
47 pub file_range: FileRange, 54 pub file_range: FileRange,
48 pub kind: ReferenceKind, 55 pub kind: ReferenceKind,
56 pub access: Option<ReferenceAccess>,
49} 57}
50 58
51#[derive(Debug, Clone, PartialEq)] 59#[derive(Debug, Clone, PartialEq)]
@@ -54,11 +62,21 @@ pub enum ReferenceKind {
54 Other, 62 Other,
55} 63}
56 64
65#[derive(Debug, Copy, Clone, PartialEq)]
66pub enum ReferenceAccess {
67 Read,
68 Write,
69}
70
57impl ReferenceSearchResult { 71impl ReferenceSearchResult {
58 pub fn declaration(&self) -> &NavigationTarget { 72 pub fn declaration(&self) -> &Declaration {
59 &self.declaration 73 &self.declaration
60 } 74 }
61 75
76 pub fn decl_target(&self) -> &NavigationTarget {
77 &self.declaration.nav
78 }
79
62 pub fn references(&self) -> &[Reference] { 80 pub fn references(&self) -> &[Reference] {
63 &self.references 81 &self.references
64 } 82 }
@@ -72,7 +90,7 @@ impl ReferenceSearchResult {
72} 90}
73 91
74// allow turning ReferenceSearchResult into an iterator 92// allow turning ReferenceSearchResult into an iterator
75// over FileRanges 93// over References
76impl IntoIterator for ReferenceSearchResult { 94impl IntoIterator for ReferenceSearchResult {
77 type Item = Reference; 95 type Item = Reference;
78 type IntoIter = std::vec::IntoIter<Reference>; 96 type IntoIter = std::vec::IntoIter<Reference>;
@@ -81,10 +99,11 @@ impl IntoIterator for ReferenceSearchResult {
81 let mut v = Vec::with_capacity(self.len()); 99 let mut v = Vec::with_capacity(self.len());
82 v.push(Reference { 100 v.push(Reference {
83 file_range: FileRange { 101 file_range: FileRange {
84 file_id: self.declaration.file_id(), 102 file_id: self.declaration.nav.file_id(),
85 range: self.declaration.range(), 103 range: self.declaration.nav.range(),
86 }, 104 },
87 kind: self.declaration_kind, 105 kind: self.declaration.kind,
106 access: self.declaration.access,
88 }); 107 });
89 v.append(&mut self.references); 108 v.append(&mut self.references);
90 v.into_iter() 109 v.into_iter()
@@ -131,15 +150,20 @@ pub(crate) fn find_all_refs(
131 } 150 }
132 }; 151 };
133 152
153 let decl_range = declaration.range();
154
155 let declaration = Declaration {
156 nav: declaration,
157 kind: ReferenceKind::Other,
158 access: decl_access(&def.kind, &name, &syntax, decl_range),
159 };
160
134 let references = process_definition(db, def, name, search_scope) 161 let references = process_definition(db, def, name, search_scope)
135 .into_iter() 162 .into_iter()
136 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) 163 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
137 .collect(); 164 .collect();
138 165
139 Some(RangeInfo::new( 166 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
140 range,
141 ReferenceSearchResult { declaration, references, declaration_kind: ReferenceKind::Other },
142 ))
143} 167}
144 168
145fn find_name<'a>( 169fn find_name<'a>(
@@ -201,7 +225,12 @@ fn process_definition(
201 } else { 225 } else {
202 ReferenceKind::Other 226 ReferenceKind::Other
203 }; 227 };
204 refs.push(Reference { file_range: FileRange { file_id, range }, kind }); 228
229 refs.push(Reference {
230 file_range: FileRange { file_id, range },
231 kind,
232 access: reference_access(&d.kind, &name_ref),
233 });
205 } 234 }
206 } 235 }
207 } 236 }
@@ -210,11 +239,69 @@ fn process_definition(
210 refs 239 refs
211} 240}
212 241
242fn decl_access(
243 kind: &NameKind,
244 name: &str,
245 syntax: &SyntaxNode,
246 range: TextRange,
247) -> Option<ReferenceAccess> {
248 match kind {
249 NameKind::Local(_) | NameKind::Field(_) => {}
250 _ => return None,
251 };
252
253 let stmt = find_node_at_offset::<ast::LetStmt>(syntax, range.start())?;
254 if let Some(_) = stmt.initializer() {
255 let pat = stmt.pat()?;
256 match pat {
257 ast::Pat::BindPat(it) => {
258 if it.name()?.text().as_str() == name {
259 return Some(ReferenceAccess::Write);
260 }
261 }
262 _ => {}
263 }
264 }
265
266 None
267}
268
269fn reference_access(kind: &NameKind, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
270 // Only Locals and Fields have accesses for now.
271 match kind {
272 NameKind::Local(_) | NameKind::Field(_) => {}
273 _ => return None,
274 };
275
276 let mode = name_ref.syntax().ancestors().find_map(|node| {
277 match_ast! {
278 match (node) {
279 ast::BinExpr(expr) => {
280 if expr.op_kind()?.is_assignment() {
281 // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
282 // FIXME: This is not terribly accurate.
283 if let Some(lhs) = expr.lhs() {
284 if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() {
285 return Some(ReferenceAccess::Write);
286 }
287 }
288 }
289 return Some(ReferenceAccess::Read);
290 },
291 _ => {None}
292 }
293 }
294 });
295
296 // Default Locals and Fields to read
297 mode.or(Some(ReferenceAccess::Read))
298}
299
213#[cfg(test)] 300#[cfg(test)]
214mod tests { 301mod tests {
215 use crate::{ 302 use crate::{
216 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, 303 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis},
217 Reference, ReferenceKind, ReferenceSearchResult, SearchScope, 304 Declaration, Reference, ReferenceSearchResult, SearchScope,
218 }; 305 };
219 306
220 #[test] 307 #[test]
@@ -234,8 +321,7 @@ mod tests {
234 let refs = get_all_refs(code); 321 let refs = get_all_refs(code);
235 check_result( 322 check_result(
236 refs, 323 refs,
237 "Foo STRUCT_DEF FileId(1) [5; 39) [12; 15)", 324 "Foo STRUCT_DEF FileId(1) [5; 39) [12; 15) Other",
238 ReferenceKind::Other,
239 &["FileId(1) [142; 145) StructLiteral"], 325 &["FileId(1) [142; 145) StructLiteral"],
240 ); 326 );
241 } 327 }
@@ -258,13 +344,12 @@ mod tests {
258 let refs = get_all_refs(code); 344 let refs = get_all_refs(code);
259 check_result( 345 check_result(
260 refs, 346 refs,
261 "i BIND_PAT FileId(1) [33; 34)", 347 "i BIND_PAT FileId(1) [33; 34) Other Write",
262 ReferenceKind::Other,
263 &[ 348 &[
264 "FileId(1) [67; 68) Other", 349 "FileId(1) [67; 68) Other Write",
265 "FileId(1) [71; 72) Other", 350 "FileId(1) [71; 72) Other Read",
266 "FileId(1) [101; 102) Other", 351 "FileId(1) [101; 102) Other Write",
267 "FileId(1) [127; 128) Other", 352 "FileId(1) [127; 128) Other Write",
268 ], 353 ],
269 ); 354 );
270 } 355 }
@@ -279,9 +364,8 @@ mod tests {
279 let refs = get_all_refs(code); 364 let refs = get_all_refs(code);
280 check_result( 365 check_result(
281 refs, 366 refs,
282 "i BIND_PAT FileId(1) [12; 13)", 367 "i BIND_PAT FileId(1) [12; 13) Other",
283 ReferenceKind::Other, 368 &["FileId(1) [38; 39) Other Read"],
284 &["FileId(1) [38; 39) Other"],
285 ); 369 );
286 } 370 }
287 371
@@ -295,9 +379,8 @@ mod tests {
295 let refs = get_all_refs(code); 379 let refs = get_all_refs(code);
296 check_result( 380 check_result(
297 refs, 381 refs,
298 "i BIND_PAT FileId(1) [12; 13)", 382 "i BIND_PAT FileId(1) [12; 13) Other",
299 ReferenceKind::Other, 383 &["FileId(1) [38; 39) Other Read"],
300 &["FileId(1) [38; 39) Other"],
301 ); 384 );
302 } 385 }
303 386
@@ -317,9 +400,8 @@ mod tests {
317 let refs = get_all_refs(code); 400 let refs = get_all_refs(code);
318 check_result( 401 check_result(
319 refs, 402 refs,
320 "spam RECORD_FIELD_DEF FileId(1) [66; 79) [70; 74)", 403 "spam RECORD_FIELD_DEF FileId(1) [66; 79) [70; 74) Other",
321 ReferenceKind::Other, 404 &["FileId(1) [152; 156) Other Read"],
322 &["FileId(1) [152; 156) Other"],
323 ); 405 );
324 } 406 }
325 407
@@ -334,7 +416,7 @@ mod tests {
334 "#; 416 "#;
335 417
336 let refs = get_all_refs(code); 418 let refs = get_all_refs(code);
337 check_result(refs, "f FN_DEF FileId(1) [88; 104) [91; 92)", ReferenceKind::Other, &[]); 419 check_result(refs, "f FN_DEF FileId(1) [88; 104) [91; 92) Other", &[]);
338 } 420 }
339 421
340 #[test] 422 #[test]
@@ -349,7 +431,7 @@ mod tests {
349 "#; 431 "#;
350 432
351 let refs = get_all_refs(code); 433 let refs = get_all_refs(code);
352 check_result(refs, "B ENUM_VARIANT FileId(1) [83; 84) [83; 84)", ReferenceKind::Other, &[]); 434 check_result(refs, "B ENUM_VARIANT FileId(1) [83; 84) [83; 84) Other", &[]);
353 } 435 }
354 436
355 #[test] 437 #[test]
@@ -390,8 +472,7 @@ mod tests {
390 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 472 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
391 check_result( 473 check_result(
392 refs, 474 refs,
393 "Foo STRUCT_DEF FileId(2) [16; 50) [27; 30)", 475 "Foo STRUCT_DEF FileId(2) [16; 50) [27; 30) Other",
394 ReferenceKind::Other,
395 &["FileId(1) [52; 55) StructLiteral", "FileId(3) [77; 80) StructLiteral"], 476 &["FileId(1) [52; 55) StructLiteral", "FileId(3) [77; 80) StructLiteral"],
396 ); 477 );
397 } 478 }
@@ -421,8 +502,7 @@ mod tests {
421 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 502 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
422 check_result( 503 check_result(
423 refs, 504 refs,
424 "foo SOURCE_FILE FileId(2) [0; 35)", 505 "foo SOURCE_FILE FileId(2) [0; 35) Other",
425 ReferenceKind::Other,
426 &["FileId(1) [13; 16) Other"], 506 &["FileId(1) [13; 16) Other"],
427 ); 507 );
428 } 508 }
@@ -451,8 +531,7 @@ mod tests {
451 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 531 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
452 check_result( 532 check_result(
453 refs, 533 refs,
454 "Foo STRUCT_DEF FileId(3) [0; 41) [18; 21)", 534 "Foo STRUCT_DEF FileId(3) [0; 41) [18; 21) Other",
455 ReferenceKind::Other,
456 &["FileId(2) [20; 23) Other", "FileId(2) [46; 49) StructLiteral"], 535 &["FileId(2) [20; 23) Other", "FileId(2) [46; 49) StructLiteral"],
457 ); 536 );
458 } 537 }
@@ -480,8 +559,7 @@ mod tests {
480 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 559 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
481 check_result( 560 check_result(
482 refs, 561 refs,
483 "quux FN_DEF FileId(1) [18; 34) [25; 29)", 562 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other",
484 ReferenceKind::Other,
485 &["FileId(2) [16; 20) Other", "FileId(3) [16; 20) Other"], 563 &["FileId(2) [16; 20) Other", "FileId(3) [16; 20) Other"],
486 ); 564 );
487 565
@@ -489,8 +567,7 @@ mod tests {
489 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap(); 567 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap();
490 check_result( 568 check_result(
491 refs, 569 refs,
492 "quux FN_DEF FileId(1) [18; 34) [25; 29)", 570 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other",
493 ReferenceKind::Other,
494 &["FileId(3) [16; 20) Other"], 571 &["FileId(3) [16; 20) Other"],
495 ); 572 );
496 } 573 }
@@ -509,33 +586,99 @@ mod tests {
509 let refs = get_all_refs(code); 586 let refs = get_all_refs(code);
510 check_result( 587 check_result(
511 refs, 588 refs,
512 "m1 MACRO_CALL FileId(1) [9; 63) [46; 48)", 589 "m1 MACRO_CALL FileId(1) [9; 63) [46; 48) Other",
513 ReferenceKind::Other,
514 &["FileId(1) [96; 98) Other", "FileId(1) [114; 116) Other"], 590 &["FileId(1) [96; 98) Other", "FileId(1) [114; 116) Other"],
515 ); 591 );
516 } 592 }
517 593
594 #[test]
595 fn test_basic_highlight_read_write() {
596 let code = r#"
597 fn foo() {
598 let i<|> = 0;
599 i = i + 1;
600 }"#;
601
602 let refs = get_all_refs(code);
603 check_result(
604 refs,
605 "i BIND_PAT FileId(1) [36; 37) Other Write",
606 &["FileId(1) [55; 56) Other Write", "FileId(1) [59; 60) Other Read"],
607 );
608 }
609
610 #[test]
611 fn test_basic_highlight_field_read_write() {
612 let code = r#"
613 struct S {
614 f: u32,
615 }
616
617 fn foo() {
618 let mut s = S{f: 0};
619 s.f<|> = 0;
620 }"#;
621
622 let refs = get_all_refs(code);
623 check_result(
624 refs,
625 "f RECORD_FIELD_DEF FileId(1) [32; 38) [32; 33) Other",
626 &["FileId(1) [96; 97) Other Read", "FileId(1) [117; 118) Other Write"],
627 );
628 }
629
630 #[test]
631 fn test_basic_highlight_decl_no_write() {
632 let code = r#"
633 fn foo() {
634 let i<|>;
635 i = 1;
636 }"#;
637
638 let refs = get_all_refs(code);
639 check_result(
640 refs,
641 "i BIND_PAT FileId(1) [36; 37) Other",
642 &["FileId(1) [51; 52) Other Write"],
643 );
644 }
645
518 fn get_all_refs(text: &str) -> ReferenceSearchResult { 646 fn get_all_refs(text: &str) -> ReferenceSearchResult {
519 let (analysis, position) = single_file_with_position(text); 647 let (analysis, position) = single_file_with_position(text);
520 analysis.find_all_refs(position, None).unwrap().unwrap() 648 analysis.find_all_refs(position, None).unwrap().unwrap()
521 } 649 }
522 650
523 fn check_result( 651 fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) {
524 res: ReferenceSearchResult,
525 expected_decl: &str,
526 decl_kind: ReferenceKind,
527 expected_refs: &[&str],
528 ) {
529 res.declaration().assert_match(expected_decl); 652 res.declaration().assert_match(expected_decl);
530 assert_eq!(res.declaration_kind, decl_kind);
531
532 assert_eq!(res.references.len(), expected_refs.len()); 653 assert_eq!(res.references.len(), expected_refs.len());
533 res.references().iter().enumerate().for_each(|(i, r)| r.assert_match(expected_refs[i])); 654 res.references().iter().enumerate().for_each(|(i, r)| r.assert_match(expected_refs[i]));
534 } 655 }
535 656
657 impl Declaration {
658 fn debug_render(&self) -> String {
659 let mut s = format!("{} {:?}", self.nav.debug_render(), self.kind);
660 if let Some(access) = self.access {
661 s.push_str(&format!(" {:?}", access));
662 }
663 s
664 }
665
666 fn assert_match(&self, expected: &str) {
667 let actual = self.debug_render();
668 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
669 }
670 }
671
536 impl Reference { 672 impl Reference {
537 fn debug_render(&self) -> String { 673 fn debug_render(&self) -> String {
538 format!("{:?} {:?} {:?}", self.file_range.file_id, self.file_range.range, self.kind) 674 let mut s = format!(
675 "{:?} {:?} {:?}",
676 self.file_range.file_id, self.file_range.range, self.kind
677 );
678 if let Some(access) = self.access {
679 s.push_str(&format!(" {:?}", access));
680 }
681 s
539 } 682 }
540 683
541 fn assert_match(&self, expected: &str) { 684 fn assert_match(&self, expected: &str) {