]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
Merge #9759
[rust.git] / crates / ide / src / highlight_related.rs
1 use hir::Semantics;
2 use ide_db::{
3     base_db::FilePosition,
4     defs::Definition,
5     helpers::{for_each_break_expr, for_each_tail_expr, pick_best_token},
6     search::{FileReference, ReferenceAccess, SearchScope},
7     RootDatabase,
8 };
9 use syntax::{
10     ast::{self, LoopBodyOwner},
11     match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, T,
12 };
13
14 use crate::{display::TryToNav, references, NavigationTarget};
15
16 pub struct HighlightedRange {
17     pub range: TextRange,
18     pub access: Option<ReferenceAccess>,
19 }
20
21 #[derive(Default, Clone)]
22 pub struct HighlightRelatedConfig {
23     pub references: bool,
24     pub exit_points: bool,
25     pub break_points: bool,
26     pub yield_points: bool,
27 }
28
29 // Feature: Highlight Related
30 //
31 // Highlights constructs related to the thing under the cursor:
32 // - if on an identifier, highlights all references to that identifier in the current file
33 // - if on an `async` or `await token, highlights all yield points for that async context
34 // - if on a `return` token, `?` character or `->` return type arrow, highlights all exit points for that context
35 // - if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
36 pub(crate) fn highlight_related(
37     sema: &Semantics<RootDatabase>,
38     config: HighlightRelatedConfig,
39     position: FilePosition,
40 ) -> Option<Vec<HighlightedRange>> {
41     let _p = profile::span("highlight_related");
42     let syntax = sema.parse(position.file_id).syntax().clone();
43
44     let token = pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind {
45         T![?] => 2, // prefer `?` when the cursor is sandwiched like `await$0?`
46         T![await]
47         | T![async]
48         | T![return]
49         | T![break]
50         | T![loop]
51         | T![for]
52         | T![while]
53         | T![->] => 1,
54         _ => 0,
55     })?;
56
57     match token.kind() {
58         T![return] | T![?] | T![->] if config.exit_points => highlight_exit_points(sema, token),
59         T![await] | T![async] if config.yield_points => highlight_yield_points(token),
60         T![break] | T![loop] | T![for] | T![while] if config.break_points => {
61             highlight_break_points(token)
62         }
63         _ if config.references => highlight_references(sema, &syntax, position),
64         _ => None,
65     }
66 }
67
68 fn highlight_references(
69     sema: &Semantics<RootDatabase>,
70     syntax: &SyntaxNode,
71     FilePosition { offset, file_id }: FilePosition,
72 ) -> Option<Vec<HighlightedRange>> {
73     let def = references::find_def(sema, syntax, offset)?;
74     let usages = def
75         .usages(sema)
76         .set_scope(Some(SearchScope::single_file(file_id)))
77         .include_self_refs()
78         .all();
79
80     let declaration = match def {
81         Definition::ModuleDef(hir::ModuleDef::Module(module)) => {
82             Some(NavigationTarget::from_module_to_decl(sema.db, module))
83         }
84         def => def.try_to_nav(sema.db),
85     }
86     .filter(|decl| decl.file_id == file_id)
87     .and_then(|decl| {
88         let range = decl.focus_range?;
89         let access = references::decl_access(&def, syntax, range);
90         Some(HighlightedRange { range, access })
91     });
92
93     let file_refs = usages.references.get(&file_id).map_or(&[][..], Vec::as_slice);
94     let mut res = Vec::with_capacity(file_refs.len() + 1);
95     res.extend(declaration);
96     res.extend(
97         file_refs
98             .iter()
99             .map(|&FileReference { access, range, .. }| HighlightedRange { range, access }),
100     );
101     Some(res)
102 }
103
104 fn highlight_exit_points(
105     sema: &Semantics<RootDatabase>,
106     token: SyntaxToken,
107 ) -> Option<Vec<HighlightedRange>> {
108     fn hl(
109         sema: &Semantics<RootDatabase>,
110         body: Option<ast::Expr>,
111     ) -> Option<Vec<HighlightedRange>> {
112         let mut highlights = Vec::new();
113         let body = body?;
114         body.walk(&mut |expr| match expr {
115             ast::Expr::ReturnExpr(expr) => {
116                 if let Some(token) = expr.return_token() {
117                     highlights.push(HighlightedRange { access: None, range: token.text_range() });
118                 }
119             }
120             ast::Expr::TryExpr(try_) => {
121                 if let Some(token) = try_.question_mark_token() {
122                     highlights.push(HighlightedRange { access: None, range: token.text_range() });
123                 }
124             }
125             ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => {
126                 if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) {
127                     highlights
128                         .push(HighlightedRange { access: None, range: expr.syntax().text_range() });
129                 }
130             }
131             _ => (),
132         });
133         let tail = match body {
134             ast::Expr::BlockExpr(b) => b.tail_expr(),
135             e => Some(e),
136         };
137
138         if let Some(tail) = tail {
139             for_each_tail_expr(&tail, &mut |tail| {
140                 let range = match tail {
141                     ast::Expr::BreakExpr(b) => b
142                         .break_token()
143                         .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
144                     _ => tail.syntax().text_range(),
145                 };
146                 highlights.push(HighlightedRange { access: None, range })
147             });
148         }
149         Some(highlights)
150     }
151     for anc in token.ancestors() {
152         return match_ast! {
153             match anc {
154                 ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)),
155                 ast::ClosureExpr(closure) => hl(sema, closure.body()),
156                 ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) {
157                     hl(sema, effect.block_expr().map(ast::Expr::BlockExpr))
158                 } else {
159                     continue;
160                 },
161                 _ => continue,
162             }
163         };
164     }
165     None
166 }
167
168 fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
169     fn hl(
170         token: Option<SyntaxToken>,
171         label: Option<ast::Label>,
172         body: Option<ast::BlockExpr>,
173     ) -> Option<Vec<HighlightedRange>> {
174         let mut highlights = Vec::new();
175         let range = cover_range(
176             token.map(|tok| tok.text_range()),
177             label.as_ref().map(|it| it.syntax().text_range()),
178         );
179         highlights.extend(range.map(|range| HighlightedRange { access: None, range }));
180         for_each_break_expr(label, body, &mut |break_| {
181             let range = cover_range(
182                 break_.break_token().map(|it| it.text_range()),
183                 break_.lifetime().map(|it| it.syntax().text_range()),
184             );
185             highlights.extend(range.map(|range| HighlightedRange { access: None, range }));
186         });
187         Some(highlights)
188     }
189     let parent = token.parent()?;
190     let lbl = match_ast! {
191         match parent {
192             ast::BreakExpr(b) => b.lifetime(),
193             ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()),
194             ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()),
195             ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()),
196             ast::EffectExpr(b) => Some(b.label().and_then(|it| it.lifetime())?),
197             _ => return None,
198         }
199     };
200     let lbl = lbl.as_ref();
201     let label_matches = |def_lbl: Option<ast::Label>| match lbl {
202         Some(lbl) => {
203             Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text())
204         }
205         None => true,
206     };
207     for anc in token.ancestors().flat_map(ast::Expr::cast) {
208         return match anc {
209             ast::Expr::LoopExpr(l) if label_matches(l.label()) => {
210                 hl(l.loop_token(), l.label(), l.loop_body())
211             }
212             ast::Expr::ForExpr(f) if label_matches(f.label()) => {
213                 hl(f.for_token(), f.label(), f.loop_body())
214             }
215             ast::Expr::WhileExpr(w) if label_matches(w.label()) => {
216                 hl(w.while_token(), w.label(), w.loop_body())
217             }
218             ast::Expr::EffectExpr(e) if e.label().is_some() && label_matches(e.label()) => {
219                 hl(None, e.label(), e.block_expr())
220             }
221             _ => continue,
222         };
223     }
224     None
225 }
226
227 fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
228     fn hl(
229         async_token: Option<SyntaxToken>,
230         body: Option<ast::Expr>,
231     ) -> Option<Vec<HighlightedRange>> {
232         let mut highlights =
233             vec![HighlightedRange { access: None, range: async_token?.text_range() }];
234         if let Some(body) = body {
235             body.walk(&mut |expr| {
236                 if let ast::Expr::AwaitExpr(expr) = expr {
237                     if let Some(token) = expr.await_token() {
238                         highlights
239                             .push(HighlightedRange { access: None, range: token.text_range() });
240                     }
241                 }
242             });
243         }
244         Some(highlights)
245     }
246     for anc in token.ancestors() {
247         return match_ast! {
248             match anc {
249                 ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
250                 ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr().map(ast::Expr::BlockExpr)),
251                 ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
252                 _ => continue,
253             }
254         };
255     }
256     None
257 }
258
259 fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
260     match (r0, r1) {
261         (Some(r0), Some(r1)) => Some(r0.cover(r1)),
262         (Some(range), None) => Some(range),
263         (None, Some(range)) => Some(range),
264         (None, None) => None,
265     }
266 }
267
268 #[cfg(test)]
269 mod tests {
270     use crate::fixture;
271
272     use super::*;
273
274     fn check(ra_fixture: &str) {
275         let config = HighlightRelatedConfig {
276             break_points: true,
277             exit_points: true,
278             references: true,
279             yield_points: true,
280         };
281
282         check_with_config(ra_fixture, config);
283     }
284
285     fn check_with_config(ra_fixture: &str, config: HighlightRelatedConfig) {
286         let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
287
288         let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or(Vec::default());
289
290         let mut expected = annotations
291             .into_iter()
292             .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
293             .collect::<Vec<_>>();
294
295         let mut actual = hls
296             .into_iter()
297             .map(|hl| {
298                 (
299                     hl.range,
300                     hl.access.map(|it| {
301                         match it {
302                             ReferenceAccess::Read => "read",
303                             ReferenceAccess::Write => "write",
304                         }
305                         .to_string()
306                     }),
307                 )
308             })
309             .collect::<Vec<_>>();
310         actual.sort_by_key(|(range, _)| range.start());
311         expected.sort_by_key(|(range, _)| range.start());
312
313         assert_eq!(expected, actual);
314     }
315
316     #[test]
317     fn test_hl_module() {
318         check(
319             r#"
320 //- /lib.rs
321 mod foo$0;
322  // ^^^
323 //- /foo.rs
324 struct Foo;
325 "#,
326         );
327     }
328
329     #[test]
330     fn test_hl_self_in_crate_root() {
331         check(
332             r#"
333 use self$0;
334 "#,
335         );
336     }
337
338     #[test]
339     fn test_hl_self_in_module() {
340         check(
341             r#"
342 //- /lib.rs
343 mod foo;
344 //- /foo.rs
345 use self$0;
346  // ^^^^
347 "#,
348         );
349     }
350
351     #[test]
352     fn test_hl_local() {
353         check(
354             r#"
355 fn foo() {
356     let mut bar = 3;
357          // ^^^ write
358     bar$0;
359  // ^^^ read
360 }
361 "#,
362         );
363     }
364
365     #[test]
366     fn test_hl_yield_points() {
367         check(
368             r#"
369 pub async fn foo() {
370  // ^^^^^
371     let x = foo()
372         .await$0
373       // ^^^^^
374         .await;
375       // ^^^^^
376     || { 0.await };
377     (async { 0.await }).await
378                      // ^^^^^
379 }
380 "#,
381         );
382     }
383
384     #[test]
385     fn test_hl_yield_points2() {
386         check(
387             r#"
388 pub async$0 fn foo() {
389  // ^^^^^
390     let x = foo()
391         .await
392       // ^^^^^
393         .await;
394       // ^^^^^
395     || { 0.await };
396     (async { 0.await }).await
397                      // ^^^^^
398 }
399 "#,
400         );
401     }
402
403     #[test]
404     fn test_hl_yield_nested_fn() {
405         check(
406             r#"
407 async fn foo() {
408     async fn foo2() {
409  // ^^^^^
410         async fn foo3() {
411             0.await
412         }
413         0.await$0
414        // ^^^^^
415     }
416     0.await
417 }
418 "#,
419         );
420     }
421
422     #[test]
423     fn test_hl_yield_nested_async_blocks() {
424         check(
425             r#"
426 async fn foo() {
427     (async {
428   // ^^^^^
429         (async {
430            0.await
431         }).await$0 }
432         // ^^^^^
433     ).await;
434 }
435 "#,
436         );
437     }
438
439     #[test]
440     fn test_hl_exit_points() {
441         check(
442             r#"
443 fn foo() -> u32 {
444     if true {
445         return$0 0;
446      // ^^^^^^
447     }
448
449     0?;
450   // ^
451     0xDEAD_BEEF
452  // ^^^^^^^^^^^
453 }
454 "#,
455         );
456     }
457
458     #[test]
459     fn test_hl_exit_points2() {
460         check(
461             r#"
462 fn foo() ->$0 u32 {
463     if true {
464         return 0;
465      // ^^^^^^
466     }
467
468     0?;
469   // ^
470     0xDEAD_BEEF
471  // ^^^^^^^^^^^
472 }
473 "#,
474         );
475     }
476
477     #[test]
478     fn test_hl_prefer_ref_over_tail_exit() {
479         check(
480             r#"
481 fn foo() -> u32 {
482 // ^^^
483     if true {
484         return 0;
485     }
486
487     0?;
488
489     foo$0()
490  // ^^^
491 }
492 "#,
493         );
494     }
495
496     #[test]
497     fn test_hl_never_call_is_exit_point() {
498         check(
499             r#"
500 struct Never;
501 impl Never {
502     fn never(self) -> ! { loop {} }
503 }
504 macro_rules! never {
505     () => { never() }
506 }
507 fn never() -> ! { loop {} }
508 fn foo() ->$0 u32 {
509     never();
510  // ^^^^^^^
511     never!();
512  // FIXME sema doesn't give us types for macrocalls
513
514     Never.never();
515  // ^^^^^^^^^^^^^
516
517     0
518  // ^
519 }
520 "#,
521         );
522     }
523
524     #[test]
525     fn test_hl_inner_tail_exit_points() {
526         check(
527             r#"
528 fn foo() ->$0 u32 {
529     if true {
530         unsafe {
531             return 5;
532          // ^^^^^^
533             5
534          // ^
535         }
536     } else if false {
537         0
538      // ^
539     } else {
540         match 5 {
541             6 => 100,
542               // ^^^
543             7 => loop {
544                 break 5;
545              // ^^^^^
546             }
547             8 => 'a: loop {
548                 'b: loop {
549                     break 'a 5;
550                  // ^^^^^
551                     break 'b 5;
552                     break 5;
553                 };
554             }
555             //
556             _ => 500,
557               // ^^^
558         }
559     }
560 }
561 "#,
562         );
563     }
564
565     #[test]
566     fn test_hl_inner_tail_exit_points_labeled_block() {
567         check(
568             r#"
569 fn foo() ->$0 u32 {
570     'foo: {
571         break 'foo 0;
572      // ^^^^^
573         loop {
574             break;
575             break 'foo 0;
576          // ^^^^^
577         }
578         0
579      // ^
580     }
581 }
582 "#,
583         );
584     }
585
586     #[test]
587     fn test_hl_break_loop() {
588         check(
589             r#"
590 fn foo() {
591     'outer: loop {
592  // ^^^^^^^^^^^^
593          break;
594       // ^^^^^
595          'inner: loop {
596             break;
597             'innermost: loop {
598                 break 'outer;
599              // ^^^^^^^^^^^^
600                 break 'inner;
601             }
602             break$0 'outer;
603          // ^^^^^^^^^^^^
604             break;
605         }
606         break;
607      // ^^^^^
608     }
609 }
610 "#,
611         );
612     }
613
614     #[test]
615     fn test_hl_break_loop2() {
616         check(
617             r#"
618 fn foo() {
619     'outer: loop {
620         break;
621         'inner: loop {
622      // ^^^^^^^^^^^^
623             break;
624          // ^^^^^
625             'innermost: loop {
626                 break 'outer;
627                 break 'inner;
628              // ^^^^^^^^^^^^
629             }
630             break 'outer;
631             break$0;
632          // ^^^^^
633         }
634         break;
635     }
636 }
637 "#,
638         );
639     }
640
641     #[test]
642     fn test_hl_break_for() {
643         check(
644             r#"
645 fn foo() {
646     'outer: for _ in () {
647  // ^^^^^^^^^^^
648          break;
649       // ^^^^^
650          'inner: for _ in () {
651             break;
652             'innermost: for _ in () {
653                 break 'outer;
654              // ^^^^^^^^^^^^
655                 break 'inner;
656             }
657             break$0 'outer;
658          // ^^^^^^^^^^^^
659             break;
660         }
661         break;
662      // ^^^^^
663     }
664 }
665 "#,
666         );
667     }
668
669     #[test]
670     fn test_hl_break_while() {
671         check(
672             r#"
673 fn foo() {
674     'outer: while true {
675  // ^^^^^^^^^^^^^
676          break;
677       // ^^^^^
678          'inner: while true {
679             break;
680             'innermost: while true {
681                 break 'outer;
682              // ^^^^^^^^^^^^
683                 break 'inner;
684             }
685             break$0 'outer;
686          // ^^^^^^^^^^^^
687             break;
688         }
689         break;
690      // ^^^^^
691     }
692 }
693 "#,
694         );
695     }
696
697     #[test]
698     fn test_hl_break_labeled_block() {
699         check(
700             r#"
701 fn foo() {
702     'outer: {
703  // ^^^^^^^
704          break;
705       // ^^^^^
706          'inner: {
707             break;
708             'innermost: {
709                 break 'outer;
710              // ^^^^^^^^^^^^
711                 break 'inner;
712             }
713             break$0 'outer;
714          // ^^^^^^^^^^^^
715             break;
716         }
717         break;
718      // ^^^^^
719     }
720 }
721 "#,
722         );
723     }
724
725     #[test]
726     fn test_hl_break_unlabeled_loop() {
727         check(
728             r#"
729 fn foo() {
730     loop {
731  // ^^^^
732         break$0;
733      // ^^^^^
734     }
735 }
736 "#,
737         );
738     }
739
740     #[test]
741     fn test_hl_break_unlabeled_block_in_loop() {
742         check(
743             r#"
744 fn foo() {
745     loop {
746  // ^^^^
747         {
748             break$0;
749          // ^^^^^
750         }
751     }
752 }
753 "#,
754         );
755     }
756
757     #[test]
758     fn test_hl_disabled_ref_local() {
759         let config = HighlightRelatedConfig {
760             references: false,
761             break_points: true,
762             exit_points: true,
763             yield_points: true,
764         };
765
766         let ra_fixture = r#"
767 fn foo() {
768     let x$0 = 5;
769     let y = x * 2;
770 }"#;
771
772         check_with_config(ra_fixture, config);
773     }
774
775     #[test]
776     fn test_hl_disabled_ref_local_preserved_break() {
777         let config = HighlightRelatedConfig {
778             references: false,
779             break_points: true,
780             exit_points: true,
781             yield_points: true,
782         };
783
784         let ra_fixture = r#"
785 fn foo() {
786     let x$0 = 5;
787     let y = x * 2;
788
789     loop {
790         break;
791     }
792 }"#;
793
794         check_with_config(ra_fixture, config.clone());
795
796         let ra_fixture = r#"
797 fn foo() {
798     let x = 5;
799     let y = x * 2;
800
801     loop$0 {
802 //  ^^^^
803         break;
804 //      ^^^^^
805     }
806 }"#;
807
808         check_with_config(ra_fixture, config);
809     }
810
811     #[test]
812     fn test_hl_disabled_ref_local_preserved_yield() {
813         let config = HighlightRelatedConfig {
814             references: false,
815             break_points: true,
816             exit_points: true,
817             yield_points: true,
818         };
819
820         let ra_fixture = r#"
821 async fn foo() {
822     let x$0 = 5;
823     let y = x * 2;
824
825     0.await;
826 }"#;
827
828         check_with_config(ra_fixture, config.clone());
829
830         let ra_fixture = r#"
831     async fn foo() {
832 //  ^^^^^
833         let x = 5;
834         let y = x * 2;
835
836         0.await$0;
837 //        ^^^^^
838 }"#;
839
840         check_with_config(ra_fixture, config);
841     }
842
843     #[test]
844     fn test_hl_disabled_ref_local_preserved_exit() {
845         let config = HighlightRelatedConfig {
846             references: false,
847             break_points: true,
848             exit_points: true,
849             yield_points: true,
850         };
851
852         let ra_fixture = r#"
853 fn foo() -> i32 {
854     let x$0 = 5;
855     let y = x * 2;
856
857     if true {
858         return y;
859     }
860
861     0?
862 }"#;
863
864         check_with_config(ra_fixture, config.clone());
865
866         let ra_fixture = r#"
867 fn foo() ->$0 i32 {
868     let x = 5;
869     let y = x * 2;
870
871     if true {
872         return y;
873 //      ^^^^^^
874     }
875
876     0?
877 //   ^
878 "#;
879
880         check_with_config(ra_fixture, config);
881     }
882
883     #[test]
884     fn test_hl_disabled_break() {
885         let config = HighlightRelatedConfig {
886             references: true,
887             break_points: false,
888             exit_points: true,
889             yield_points: true,
890         };
891
892         let ra_fixture = r#"
893 fn foo() {
894     loop {
895         break$0;
896     }
897 }"#;
898
899         check_with_config(ra_fixture, config);
900     }
901
902     #[test]
903     fn test_hl_disabled_yield() {
904         let config = HighlightRelatedConfig {
905             references: true,
906             break_points: true,
907             exit_points: true,
908             yield_points: false,
909         };
910
911         let ra_fixture = r#"
912 async$0 fn foo() {
913     0.await;
914 }"#;
915
916         check_with_config(ra_fixture, config);
917     }
918
919     #[test]
920     fn test_hl_disabled_exit() {
921         let config = HighlightRelatedConfig {
922             references: true,
923             break_points: true,
924             exit_points: false,
925             yield_points: true,
926         };
927
928         let ra_fixture = r#"
929 fn foo() ->$0 i32 {
930     if true {
931         return -1;
932     }
933
934     42
935 }"#;
936
937         check_with_config(ra_fixture, config);
938     }
939 }