]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
Merge #9593
[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 {
537         match 5 {
538             6 => 100,
539               // ^^^
540             7 => loop {
541                 break 5;
542              // ^^^^^
543             }
544             8 => 'a: loop {
545                 'b: loop {
546                     break 'a 5;
547                  // ^^^^^
548                     break 'b 5;
549                     break 5;
550                 };
551             }
552             //
553             _ => 500,
554               // ^^^
555         }
556     }
557 }
558 "#,
559         );
560     }
561
562     #[test]
563     fn test_hl_inner_tail_exit_points_labeled_block() {
564         check(
565             r#"
566 fn foo() ->$0 u32 {
567     'foo: {
568         break 'foo 0;
569      // ^^^^^
570         loop {
571             break;
572             break 'foo 0;
573          // ^^^^^
574         }
575         0
576      // ^
577     }
578 }
579 "#,
580         );
581     }
582
583     #[test]
584     fn test_hl_break_loop() {
585         check(
586             r#"
587 fn foo() {
588     'outer: loop {
589  // ^^^^^^^^^^^^
590          break;
591       // ^^^^^
592          'inner: loop {
593             break;
594             'innermost: loop {
595                 break 'outer;
596              // ^^^^^^^^^^^^
597                 break 'inner;
598             }
599             break$0 'outer;
600          // ^^^^^^^^^^^^
601             break;
602         }
603         break;
604      // ^^^^^
605     }
606 }
607 "#,
608         );
609     }
610
611     #[test]
612     fn test_hl_break_loop2() {
613         check(
614             r#"
615 fn foo() {
616     'outer: loop {
617         break;
618         'inner: loop {
619      // ^^^^^^^^^^^^
620             break;
621          // ^^^^^
622             'innermost: loop {
623                 break 'outer;
624                 break 'inner;
625              // ^^^^^^^^^^^^
626             }
627             break 'outer;
628             break$0;
629          // ^^^^^
630         }
631         break;
632     }
633 }
634 "#,
635         );
636     }
637
638     #[test]
639     fn test_hl_break_for() {
640         check(
641             r#"
642 fn foo() {
643     'outer: for _ in () {
644  // ^^^^^^^^^^^
645          break;
646       // ^^^^^
647          'inner: for _ in () {
648             break;
649             'innermost: for _ in () {
650                 break 'outer;
651              // ^^^^^^^^^^^^
652                 break 'inner;
653             }
654             break$0 'outer;
655          // ^^^^^^^^^^^^
656             break;
657         }
658         break;
659      // ^^^^^
660     }
661 }
662 "#,
663         );
664     }
665
666     #[test]
667     fn test_hl_break_while() {
668         check(
669             r#"
670 fn foo() {
671     'outer: while true {
672  // ^^^^^^^^^^^^^
673          break;
674       // ^^^^^
675          'inner: while true {
676             break;
677             'innermost: while true {
678                 break 'outer;
679              // ^^^^^^^^^^^^
680                 break 'inner;
681             }
682             break$0 'outer;
683          // ^^^^^^^^^^^^
684             break;
685         }
686         break;
687      // ^^^^^
688     }
689 }
690 "#,
691         );
692     }
693
694     #[test]
695     fn test_hl_break_labeled_block() {
696         check(
697             r#"
698 fn foo() {
699     'outer: {
700  // ^^^^^^^
701          break;
702       // ^^^^^
703          'inner: {
704             break;
705             'innermost: {
706                 break 'outer;
707              // ^^^^^^^^^^^^
708                 break 'inner;
709             }
710             break$0 'outer;
711          // ^^^^^^^^^^^^
712             break;
713         }
714         break;
715      // ^^^^^
716     }
717 }
718 "#,
719         );
720     }
721
722     #[test]
723     fn test_hl_break_unlabeled_loop() {
724         check(
725             r#"
726 fn foo() {
727     loop {
728  // ^^^^
729         break$0;
730      // ^^^^^
731     }
732 }
733 "#,
734         );
735     }
736
737     #[test]
738     fn test_hl_break_unlabeled_block_in_loop() {
739         check(
740             r#"
741 fn foo() {
742     loop {
743  // ^^^^
744         {
745             break$0;
746          // ^^^^^
747         }
748     }
749 }
750 "#,
751         );
752     }
753
754     #[test]
755     fn test_hl_disabled_ref_local() {
756         let config = HighlightRelatedConfig {
757             references: false,
758             break_points: true,
759             exit_points: true,
760             yield_points: true,
761         };
762
763         let ra_fixture = r#"
764 fn foo() {
765     let x$0 = 5;
766     let y = x * 2;
767 }"#;
768
769         check_with_config(ra_fixture, config);
770     }
771
772     #[test]
773     fn test_hl_disabled_ref_local_preserved_break() {
774         let config = HighlightRelatedConfig {
775             references: false,
776             break_points: true,
777             exit_points: true,
778             yield_points: true,
779         };
780
781         let ra_fixture = r#"
782 fn foo() {
783     let x$0 = 5;
784     let y = x * 2;
785
786     loop {
787         break;
788     }
789 }"#;
790
791         check_with_config(ra_fixture, config.clone());
792
793         let ra_fixture = r#"
794 fn foo() {
795     let x = 5;
796     let y = x * 2;
797
798     loop$0 {
799 //  ^^^^
800         break;
801 //      ^^^^^
802     }
803 }"#;
804
805         check_with_config(ra_fixture, config);
806     }
807
808     #[test]
809     fn test_hl_disabled_ref_local_preserved_yield() {
810         let config = HighlightRelatedConfig {
811             references: false,
812             break_points: true,
813             exit_points: true,
814             yield_points: true,
815         };
816
817         let ra_fixture = r#"
818 async fn foo() {
819     let x$0 = 5;
820     let y = x * 2;
821
822     0.await;
823 }"#;
824
825         check_with_config(ra_fixture, config.clone());
826
827         let ra_fixture = r#"
828     async fn foo() {
829 //  ^^^^^
830         let x = 5;
831         let y = x * 2;
832
833         0.await$0;
834 //        ^^^^^
835 }"#;
836
837         check_with_config(ra_fixture, config);
838     }
839
840     #[test]
841     fn test_hl_disabled_ref_local_preserved_exit() {
842         let config = HighlightRelatedConfig {
843             references: false,
844             break_points: true,
845             exit_points: true,
846             yield_points: true,
847         };
848
849         let ra_fixture = r#"
850 fn foo() -> i32 {
851     let x$0 = 5;
852     let y = x * 2;
853
854     if true {
855         return y;
856     }
857
858     0?
859 }"#;
860
861         check_with_config(ra_fixture, config.clone());
862
863         let ra_fixture = r#"
864 fn foo() ->$0 i32 {
865     let x = 5;
866     let y = x * 2;
867
868     if true {
869         return y;
870 //      ^^^^^^
871     }
872
873     0?
874 //   ^
875 "#;
876
877         check_with_config(ra_fixture, config);
878     }
879
880     #[test]
881     fn test_hl_disabled_break() {
882         let config = HighlightRelatedConfig {
883             references: true,
884             break_points: false,
885             exit_points: true,
886             yield_points: true,
887         };
888
889         let ra_fixture = r#"
890 fn foo() {
891     loop {
892         break$0;
893     }
894 }"#;
895
896         check_with_config(ra_fixture, config);
897     }
898
899     #[test]
900     fn test_hl_disabled_yield() {
901         let config = HighlightRelatedConfig {
902             references: true,
903             break_points: true,
904             exit_points: true,
905             yield_points: false,
906         };
907
908         let ra_fixture = r#"
909 async$0 fn foo() {
910     0.await;
911 }"#;
912
913         check_with_config(ra_fixture, config);
914     }
915
916     #[test]
917     fn test_hl_disabled_exit() {
918         let config = HighlightRelatedConfig {
919             references: true,
920             break_points: true,
921             exit_points: false,
922             yield_points: true,
923         };
924
925         let ra_fixture = r#"
926 fn foo() ->$0 i32 {
927     if true {
928         return -1;
929     }
930
931     42
932 }"#;
933
934         check_with_config(ra_fixture, config);
935     }
936 }