]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/highlight_related.rs
Merge #9269
[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 // Feature: Highlight Related
22 //
23 // Highlights constructs related to the thing under the cursor:
24 // - if on an identifier, highlights all references to that identifier in the current file
25 // - if on an `async` or `await token, highlights all yield points for that async context
26 // - if on a `return` token, `?` character or `->` return type arrow, highlights all exit points for that context
27 // - if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
28 pub(crate) fn highlight_related(
29     sema: &Semantics<RootDatabase>,
30     position: FilePosition,
31 ) -> Option<Vec<HighlightedRange>> {
32     let _p = profile::span("highlight_related");
33     let syntax = sema.parse(position.file_id).syntax().clone();
34
35     let token = pick_best_token(syntax.token_at_offset(position.offset), |kind| match kind {
36         T![?] => 2, // prefer `?` when the cursor is sandwiched like `await$0?`
37         T![await]
38         | T![async]
39         | T![return]
40         | T![break]
41         | T![loop]
42         | T![for]
43         | T![while]
44         | T![->] => 1,
45         _ => 0,
46     })?;
47
48     match token.kind() {
49         T![return] | T![?] | T![->] => highlight_exit_points(sema, token),
50         T![await] | T![async] => highlight_yield_points(token),
51         T![break] | T![loop] | T![for] | T![while] => highlight_break_points(token),
52         _ => highlight_references(sema, &syntax, position),
53     }
54 }
55
56 fn highlight_references(
57     sema: &Semantics<RootDatabase>,
58     syntax: &SyntaxNode,
59     FilePosition { offset, file_id }: FilePosition,
60 ) -> Option<Vec<HighlightedRange>> {
61     let def = references::find_def(sema, syntax, offset)?;
62     let usages = def
63         .usages(sema)
64         .set_scope(Some(SearchScope::single_file(file_id)))
65         .include_self_refs()
66         .all();
67
68     let declaration = match def {
69         Definition::ModuleDef(hir::ModuleDef::Module(module)) => {
70             Some(NavigationTarget::from_module_to_decl(sema.db, module))
71         }
72         def => def.try_to_nav(sema.db),
73     }
74     .filter(|decl| decl.file_id == file_id)
75     .and_then(|decl| {
76         let range = decl.focus_range?;
77         let access = references::decl_access(&def, syntax, range);
78         Some(HighlightedRange { range, access })
79     });
80
81     let file_refs = usages.references.get(&file_id).map_or(&[][..], Vec::as_slice);
82     let mut res = Vec::with_capacity(file_refs.len() + 1);
83     res.extend(declaration);
84     res.extend(
85         file_refs
86             .iter()
87             .map(|&FileReference { access, range, .. }| HighlightedRange { range, access }),
88     );
89     Some(res)
90 }
91
92 fn highlight_exit_points(
93     sema: &Semantics<RootDatabase>,
94     token: SyntaxToken,
95 ) -> Option<Vec<HighlightedRange>> {
96     fn hl(
97         sema: &Semantics<RootDatabase>,
98         body: Option<ast::Expr>,
99     ) -> Option<Vec<HighlightedRange>> {
100         let mut highlights = Vec::new();
101         let body = body?;
102         body.walk(&mut |expr| match expr {
103             ast::Expr::ReturnExpr(expr) => {
104                 if let Some(token) = expr.return_token() {
105                     highlights.push(HighlightedRange { access: None, range: token.text_range() });
106                 }
107             }
108             ast::Expr::TryExpr(try_) => {
109                 if let Some(token) = try_.question_mark_token() {
110                     highlights.push(HighlightedRange { access: None, range: token.text_range() });
111                 }
112             }
113             ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroCall(_) => {
114                 if sema.type_of_expr(&expr).map_or(false, |ty| ty.is_never()) {
115                     highlights
116                         .push(HighlightedRange { access: None, range: expr.syntax().text_range() });
117                 }
118             }
119             _ => (),
120         });
121         let tail = match body {
122             ast::Expr::BlockExpr(b) => b.tail_expr(),
123             e => Some(e),
124         };
125
126         if let Some(tail) = tail {
127             for_each_tail_expr(&tail, &mut |tail| {
128                 let range = match tail {
129                     ast::Expr::BreakExpr(b) => b
130                         .break_token()
131                         .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
132                     _ => tail.syntax().text_range(),
133                 };
134                 highlights.push(HighlightedRange { access: None, range })
135             });
136         }
137         Some(highlights)
138     }
139     for anc in token.ancestors() {
140         return match_ast! {
141             match anc {
142                 ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)),
143                 ast::ClosureExpr(closure) => hl(sema, closure.body()),
144                 ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) {
145                     hl(sema, effect.block_expr().map(ast::Expr::BlockExpr))
146                 } else {
147                     continue;
148                 },
149                 _ => continue,
150             }
151         };
152     }
153     None
154 }
155
156 fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
157     fn hl(
158         token: Option<SyntaxToken>,
159         label: Option<ast::Label>,
160         body: Option<ast::BlockExpr>,
161     ) -> Option<Vec<HighlightedRange>> {
162         let mut highlights = Vec::new();
163         let range = cover_range(
164             token.map(|tok| tok.text_range()),
165             label.as_ref().map(|it| it.syntax().text_range()),
166         );
167         highlights.extend(range.map(|range| HighlightedRange { access: None, range }));
168         for_each_break_expr(label, body, &mut |break_| {
169             let range = cover_range(
170                 break_.break_token().map(|it| it.text_range()),
171                 break_.lifetime().map(|it| it.syntax().text_range()),
172             );
173             highlights.extend(range.map(|range| HighlightedRange { access: None, range }));
174         });
175         Some(highlights)
176     }
177     let parent = token.parent()?;
178     let lbl = match_ast! {
179         match parent {
180             ast::BreakExpr(b) => b.lifetime(),
181             ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()),
182             ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()),
183             ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()),
184             ast::EffectExpr(b) => Some(b.label().and_then(|it| it.lifetime())?),
185             _ => return None,
186         }
187     };
188     let lbl = lbl.as_ref();
189     let label_matches = |def_lbl: Option<ast::Label>| match lbl {
190         Some(lbl) => {
191             Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text())
192         }
193         None => true,
194     };
195     for anc in token.ancestors().flat_map(ast::Expr::cast) {
196         return match anc {
197             ast::Expr::LoopExpr(l) if label_matches(l.label()) => {
198                 hl(l.loop_token(), l.label(), l.loop_body())
199             }
200             ast::Expr::ForExpr(f) if label_matches(f.label()) => {
201                 hl(f.for_token(), f.label(), f.loop_body())
202             }
203             ast::Expr::WhileExpr(w) if label_matches(w.label()) => {
204                 hl(w.while_token(), w.label(), w.loop_body())
205             }
206             ast::Expr::EffectExpr(e) if e.label().is_some() && label_matches(e.label()) => {
207                 hl(None, e.label(), e.block_expr())
208             }
209             _ => continue,
210         };
211     }
212     None
213 }
214
215 fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
216     fn hl(
217         async_token: Option<SyntaxToken>,
218         body: Option<ast::Expr>,
219     ) -> Option<Vec<HighlightedRange>> {
220         let mut highlights = Vec::new();
221         highlights.push(HighlightedRange { access: None, range: async_token?.text_range() });
222         if let Some(body) = body {
223             body.walk(&mut |expr| {
224                 if let ast::Expr::AwaitExpr(expr) = expr {
225                     if let Some(token) = expr.await_token() {
226                         highlights
227                             .push(HighlightedRange { access: None, range: token.text_range() });
228                     }
229                 }
230             });
231         }
232         Some(highlights)
233     }
234     for anc in token.ancestors() {
235         return match_ast! {
236             match anc {
237                 ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
238                 ast::EffectExpr(effect) => hl(effect.async_token(), effect.block_expr().map(ast::Expr::BlockExpr)),
239                 ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
240                 _ => continue,
241             }
242         };
243     }
244     None
245 }
246
247 fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
248     match (r0, r1) {
249         (Some(r0), Some(r1)) => Some(r0.cover(r1)),
250         (Some(range), None) => Some(range),
251         (None, Some(range)) => Some(range),
252         (None, None) => None,
253     }
254 }
255
256 #[cfg(test)]
257 mod tests {
258     use crate::fixture;
259
260     use super::*;
261
262     fn check(ra_fixture: &str) {
263         let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
264         let hls = analysis.highlight_related(pos).unwrap().unwrap();
265
266         let mut expected = annotations
267             .into_iter()
268             .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
269             .collect::<Vec<_>>();
270
271         let mut actual = hls
272             .into_iter()
273             .map(|hl| {
274                 (
275                     hl.range,
276                     hl.access.map(|it| {
277                         match it {
278                             ReferenceAccess::Read => "read",
279                             ReferenceAccess::Write => "write",
280                         }
281                         .to_string()
282                     }),
283                 )
284             })
285             .collect::<Vec<_>>();
286         actual.sort_by_key(|(range, _)| range.start());
287         expected.sort_by_key(|(range, _)| range.start());
288
289         assert_eq!(expected, actual);
290     }
291
292     #[test]
293     fn test_hl_module() {
294         check(
295             r#"
296 //- /lib.rs
297 mod foo$0;
298  // ^^^
299 //- /foo.rs
300 struct Foo;
301 "#,
302         );
303     }
304
305     #[test]
306     fn test_hl_self_in_crate_root() {
307         check(
308             r#"
309 use self$0;
310 "#,
311         );
312     }
313
314     #[test]
315     fn test_hl_self_in_module() {
316         check(
317             r#"
318 //- /lib.rs
319 mod foo;
320 //- /foo.rs
321 use self$0;
322  // ^^^^
323 "#,
324         );
325     }
326
327     #[test]
328     fn test_hl_local() {
329         check(
330             r#"
331 fn foo() {
332     let mut bar = 3;
333          // ^^^ write
334     bar$0;
335  // ^^^ read
336 }
337 "#,
338         );
339     }
340
341     #[test]
342     fn test_hl_yield_points() {
343         check(
344             r#"
345 pub async fn foo() {
346  // ^^^^^
347     let x = foo()
348         .await$0
349       // ^^^^^
350         .await;
351       // ^^^^^
352     || { 0.await };
353     (async { 0.await }).await
354                      // ^^^^^
355 }
356 "#,
357         );
358     }
359
360     #[test]
361     fn test_hl_yield_points2() {
362         check(
363             r#"
364 pub async$0 fn foo() {
365  // ^^^^^
366     let x = foo()
367         .await
368       // ^^^^^
369         .await;
370       // ^^^^^
371     || { 0.await };
372     (async { 0.await }).await
373                      // ^^^^^
374 }
375 "#,
376         );
377     }
378
379     #[test]
380     fn test_hl_yield_nested_fn() {
381         check(
382             r#"
383 async fn foo() {
384     async fn foo2() {
385  // ^^^^^
386         async fn foo3() {
387             0.await
388         }
389         0.await$0
390        // ^^^^^
391     }
392     0.await
393 }
394 "#,
395         );
396     }
397
398     #[test]
399     fn test_hl_yield_nested_async_blocks() {
400         check(
401             r#"
402 async fn foo() {
403     (async {
404   // ^^^^^
405         (async {
406            0.await
407         }).await$0 }
408         // ^^^^^
409     ).await;
410 }
411 "#,
412         );
413     }
414
415     #[test]
416     fn test_hl_exit_points() {
417         check(
418             r#"
419 fn foo() -> u32 {
420     if true {
421         return$0 0;
422      // ^^^^^^
423     }
424
425     0?;
426   // ^
427     0xDEAD_BEEF
428  // ^^^^^^^^^^^
429 }
430 "#,
431         );
432     }
433
434     #[test]
435     fn test_hl_exit_points2() {
436         check(
437             r#"
438 fn foo() ->$0 u32 {
439     if true {
440         return 0;
441      // ^^^^^^
442     }
443
444     0?;
445   // ^
446     0xDEAD_BEEF
447  // ^^^^^^^^^^^
448 }
449 "#,
450         );
451     }
452
453     #[test]
454     fn test_hl_prefer_ref_over_tail_exit() {
455         check(
456             r#"
457 fn foo() -> u32 {
458 // ^^^
459     if true {
460         return 0;
461     }
462
463     0?;
464
465     foo$0()
466  // ^^^
467 }
468 "#,
469         );
470     }
471
472     #[test]
473     fn test_hl_never_call_is_exit_point() {
474         check(
475             r#"
476 struct Never;
477 impl Never {
478     fn never(self) -> ! { loop {} }
479 }
480 macro_rules! never {
481     () => { never() }
482 }
483 fn never() -> ! { loop {} }
484 fn foo() ->$0 u32 {
485     never();
486  // ^^^^^^^
487     never!();
488  // FIXME sema doesn't give us types for macrocalls
489
490     Never.never();
491  // ^^^^^^^^^^^^^
492
493     0
494  // ^
495 }
496 "#,
497         );
498     }
499
500     #[test]
501     fn test_hl_inner_tail_exit_points() {
502         check(
503             r#"
504 fn foo() ->$0 u32 {
505     if true {
506         unsafe {
507             return 5;
508          // ^^^^^^
509             5
510          // ^
511         }
512     } else {
513         match 5 {
514             6 => 100,
515               // ^^^
516             7 => loop {
517                 break 5;
518              // ^^^^^
519             }
520             8 => 'a: loop {
521                 'b: loop {
522                     break 'a 5;
523                  // ^^^^^
524                     break 'b 5;
525                     break 5;
526                 };
527             }
528             //
529             _ => 500,
530               // ^^^
531         }
532     }
533 }
534 "#,
535         );
536     }
537
538     #[test]
539     fn test_hl_inner_tail_exit_points_labeled_block() {
540         check(
541             r#"
542 fn foo() ->$0 u32 {
543     'foo: {
544         break 'foo 0;
545      // ^^^^^
546         loop {
547             break;
548             break 'foo 0;
549          // ^^^^^
550         }
551         0
552      // ^
553     }
554 }
555 "#,
556         );
557     }
558
559     #[test]
560     fn test_hl_break_loop() {
561         check(
562             r#"
563 fn foo() {
564     'outer: loop {
565  // ^^^^^^^^^^^^
566          break;
567       // ^^^^^
568          'inner: loop {
569             break;
570             'innermost: loop {
571                 break 'outer;
572              // ^^^^^^^^^^^^
573                 break 'inner;
574             }
575             break$0 'outer;
576          // ^^^^^^^^^^^^
577             break;
578         }
579         break;
580      // ^^^^^
581     }
582 }
583 "#,
584         );
585     }
586
587     #[test]
588     fn test_hl_break_loop2() {
589         check(
590             r#"
591 fn foo() {
592     'outer: loop {
593         break;
594         'inner: loop {
595      // ^^^^^^^^^^^^
596             break;
597          // ^^^^^
598             'innermost: loop {
599                 break 'outer;
600                 break 'inner;
601              // ^^^^^^^^^^^^
602             }
603             break 'outer;
604             break$0;
605          // ^^^^^
606         }
607         break;
608     }
609 }
610 "#,
611         );
612     }
613
614     #[test]
615     fn test_hl_break_for() {
616         check(
617             r#"
618 fn foo() {
619     'outer: for _ in () {
620  // ^^^^^^^^^^^
621          break;
622       // ^^^^^
623          'inner: for _ in () {
624             break;
625             'innermost: for _ in () {
626                 break 'outer;
627              // ^^^^^^^^^^^^
628                 break 'inner;
629             }
630             break$0 'outer;
631          // ^^^^^^^^^^^^
632             break;
633         }
634         break;
635      // ^^^^^
636     }
637 }
638 "#,
639         );
640     }
641
642     #[test]
643     fn test_hl_break_while() {
644         check(
645             r#"
646 fn foo() {
647     'outer: while true {
648  // ^^^^^^^^^^^^^
649          break;
650       // ^^^^^
651          'inner: while true {
652             break;
653             'innermost: while true {
654                 break 'outer;
655              // ^^^^^^^^^^^^
656                 break 'inner;
657             }
658             break$0 'outer;
659          // ^^^^^^^^^^^^
660             break;
661         }
662         break;
663      // ^^^^^
664     }
665 }
666 "#,
667         );
668     }
669
670     #[test]
671     fn test_hl_break_labeled_block() {
672         check(
673             r#"
674 fn foo() {
675     'outer: {
676  // ^^^^^^^
677          break;
678       // ^^^^^
679          'inner: {
680             break;
681             'innermost: {
682                 break 'outer;
683              // ^^^^^^^^^^^^
684                 break 'inner;
685             }
686             break$0 'outer;
687          // ^^^^^^^^^^^^
688             break;
689         }
690         break;
691      // ^^^^^
692     }
693 }
694 "#,
695         );
696     }
697
698     #[test]
699     fn test_hl_break_unlabeled_loop() {
700         check(
701             r#"
702 fn foo() {
703     loop {
704  // ^^^^
705         break$0;
706      // ^^^^^
707     }
708 }
709 "#,
710         );
711     }
712
713     #[test]
714     fn test_hl_break_unlabeled_block_in_loop() {
715         check(
716             r#"
717 fn foo() {
718     loop {
719  // ^^^^
720         {
721             break$0;
722          // ^^^^^
723         }
724     }
725 }
726 "#,
727         );
728     }
729 }