• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use hir::Semantics;
2 use ide_db::{
3     base_db::{FileId, FilePosition, FileRange},
4     defs::{Definition, IdentClass},
5     helpers::pick_best_token,
6     search::{FileReference, ReferenceCategory, SearchScope},
7     syntax_helpers::node_ext::{
8         for_each_break_and_continue_expr, for_each_tail_expr, full_path_of_name_ref, walk_expr,
9     },
10     FxHashSet, RootDatabase,
11 };
12 use syntax::{
13     ast::{self, HasLoopBody},
14     match_ast, AstNode,
15     SyntaxKind::{self, IDENT, INT_NUMBER},
16     SyntaxNode, SyntaxToken, TextRange, T,
17 };
18 
19 use crate::{navigation_target::ToNav, references, NavigationTarget, TryToNav};
20 
21 #[derive(PartialEq, Eq, Hash)]
22 pub struct HighlightedRange {
23     pub range: TextRange,
24     // FIXME: This needs to be more precise. Reference category makes sense only
25     // for references, but we also have defs. And things like exit points are
26     // neither.
27     pub category: Option<ReferenceCategory>,
28 }
29 
30 #[derive(Default, Clone)]
31 pub struct HighlightRelatedConfig {
32     pub references: bool,
33     pub exit_points: bool,
34     pub break_points: bool,
35     pub closure_captures: bool,
36     pub yield_points: bool,
37 }
38 
39 // Feature: Highlight Related
40 //
41 // Highlights constructs related to the thing under the cursor:
42 //
43 // . if on an identifier, highlights all references to that identifier in the current file
44 // .. additionally, if the identifier is a trait in a where clause, type parameter trait bound or use item, highlights all references to that trait's assoc items in the corresponding scope
45 // . if on an `async` or `await token, highlights all yield points for that async context
46 // . if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context
47 // . if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context
48 // . if on a `move` or `|` token that belongs to a closure, highlights all captures of the closure.
49 //
50 // Note: `?`, `|` and `->` do not currently trigger this behavior in the VSCode editor.
51 pub(crate) fn highlight_related(
52     sema: &Semantics<'_, RootDatabase>,
53     config: HighlightRelatedConfig,
54     FilePosition { offset, file_id }: FilePosition,
55 ) -> Option<Vec<HighlightedRange>> {
56     let _p = profile::span("highlight_related");
57     let syntax = sema.parse(file_id).syntax().clone();
58 
59     let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
60         T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
61         T![->] => 4,
62         kind if kind.is_keyword() => 3,
63         IDENT | INT_NUMBER => 2,
64         T![|] => 1,
65         _ => 0,
66     })?;
67     // most if not all of these should be re-implemented with information seeded from hir
68     match token.kind() {
69         T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
70             highlight_exit_points(sema, token)
71         }
72         T![fn] | T![return] | T![->] if config.exit_points => highlight_exit_points(sema, token),
73         T![await] | T![async] if config.yield_points => highlight_yield_points(token),
74         T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => {
75             highlight_break_points(token)
76         }
77         T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
78             highlight_break_points(token)
79         }
80         T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id),
81         T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id),
82         _ if config.references => highlight_references(sema, &syntax, token, file_id),
83         _ => None,
84     }
85 }
86 
highlight_closure_captures( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, file_id: FileId, ) -> Option<Vec<HighlightedRange>>87 fn highlight_closure_captures(
88     sema: &Semantics<'_, RootDatabase>,
89     token: SyntaxToken,
90     file_id: FileId,
91 ) -> Option<Vec<HighlightedRange>> {
92     let closure = token.parent_ancestors().take(2).find_map(ast::ClosureExpr::cast)?;
93     let search_range = closure.body()?.syntax().text_range();
94     let ty = &sema.type_of_expr(&closure.into())?.original;
95     let c = ty.as_closure()?;
96     Some(
97         c.captured_items(sema.db)
98             .into_iter()
99             .map(|capture| capture.local())
100             .flat_map(|local| {
101                 let usages = Definition::Local(local)
102                     .usages(sema)
103                     .set_scope(Some(SearchScope::file_range(FileRange {
104                         file_id,
105                         range: search_range,
106                     })))
107                     .include_self_refs()
108                     .all()
109                     .references
110                     .remove(&file_id)
111                     .into_iter()
112                     .flatten()
113                     .map(|FileReference { category, range, .. }| HighlightedRange {
114                         range,
115                         category,
116                     });
117                 let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
118                 local
119                     .sources(sema.db)
120                     .into_iter()
121                     .map(|x| x.to_nav(sema.db))
122                     .filter(|decl| decl.file_id == file_id)
123                     .filter_map(|decl| decl.focus_range)
124                     .map(move |range| HighlightedRange { range, category })
125                     .chain(usages)
126             })
127             .collect(),
128     )
129 }
130 
highlight_references( sema: &Semantics<'_, RootDatabase>, node: &SyntaxNode, token: SyntaxToken, file_id: FileId, ) -> Option<Vec<HighlightedRange>>131 fn highlight_references(
132     sema: &Semantics<'_, RootDatabase>,
133     node: &SyntaxNode,
134     token: SyntaxToken,
135     file_id: FileId,
136 ) -> Option<Vec<HighlightedRange>> {
137     let defs = find_defs(sema, token.clone());
138     let usages = defs
139         .iter()
140         .filter_map(|&d| {
141             d.usages(sema)
142                 .set_scope(Some(SearchScope::single_file(file_id)))
143                 .include_self_refs()
144                 .all()
145                 .references
146                 .remove(&file_id)
147         })
148         .flatten()
149         .map(|FileReference { category, range, .. }| HighlightedRange { range, category });
150     let mut res = FxHashSet::default();
151     for &def in &defs {
152         // highlight trait usages
153         if let Definition::Trait(t) = def {
154             let trait_item_use_scope = (|| {
155                 let name_ref = token.parent().and_then(ast::NameRef::cast)?;
156                 let path = full_path_of_name_ref(&name_ref)?;
157                 let parent = path.syntax().parent()?;
158                 match_ast! {
159                     match parent {
160                         ast::UseTree(it) => it.syntax().ancestors().find(|it| {
161                             ast::SourceFile::can_cast(it.kind()) || ast::Module::can_cast(it.kind())
162                         }),
163                         ast::PathType(it) => it
164                             .syntax()
165                             .ancestors()
166                             .nth(2)
167                             .and_then(ast::TypeBoundList::cast)?
168                             .syntax()
169                             .parent()
170                             .filter(|it| ast::WhereClause::can_cast(it.kind()) || ast::TypeParam::can_cast(it.kind()))?
171                             .ancestors()
172                             .find(|it| {
173                                 ast::Item::can_cast(it.kind())
174                             }),
175                         _ => None,
176                     }
177                 }
178             })();
179             if let Some(trait_item_use_scope) = trait_item_use_scope {
180                 res.extend(
181                     t.items_with_supertraits(sema.db)
182                         .into_iter()
183                         .filter_map(|item| {
184                             Definition::from(item)
185                                 .usages(sema)
186                                 .set_scope(Some(SearchScope::file_range(FileRange {
187                                     file_id,
188                                     range: trait_item_use_scope.text_range(),
189                                 })))
190                                 .include_self_refs()
191                                 .all()
192                                 .references
193                                 .remove(&file_id)
194                         })
195                         .flatten()
196                         .map(|FileReference { category, range, .. }| HighlightedRange {
197                             range,
198                             category,
199                         }),
200                 );
201             }
202         }
203 
204         // highlight the defs themselves
205         match def {
206             Definition::Local(local) => {
207                 let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
208                 local
209                     .sources(sema.db)
210                     .into_iter()
211                     .map(|x| x.to_nav(sema.db))
212                     .filter(|decl| decl.file_id == file_id)
213                     .filter_map(|decl| decl.focus_range)
214                     .map(|range| HighlightedRange { range, category })
215                     .for_each(|x| {
216                         res.insert(x);
217                     });
218             }
219             def => {
220                 let hl_range = match def {
221                     Definition::Module(module) => {
222                         Some(NavigationTarget::from_module_to_decl(sema.db, module))
223                     }
224                     def => def.try_to_nav(sema.db),
225                 }
226                 .filter(|decl| decl.file_id == file_id)
227                 .and_then(|decl| decl.focus_range)
228                 .map(|range| {
229                     let category = references::decl_mutability(&def, node, range)
230                         .then_some(ReferenceCategory::Write);
231                     HighlightedRange { range, category }
232                 });
233                 if let Some(hl_range) = hl_range {
234                     res.insert(hl_range);
235                 }
236             }
237         }
238     }
239 
240     res.extend(usages);
241     if res.is_empty() {
242         None
243     } else {
244         Some(res.into_iter().collect())
245     }
246 }
247 
highlight_exit_points( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, ) -> Option<Vec<HighlightedRange>>248 fn highlight_exit_points(
249     sema: &Semantics<'_, RootDatabase>,
250     token: SyntaxToken,
251 ) -> Option<Vec<HighlightedRange>> {
252     fn hl(
253         sema: &Semantics<'_, RootDatabase>,
254         def_ranges: [Option<TextRange>; 2],
255         body: Option<ast::Expr>,
256     ) -> Option<Vec<HighlightedRange>> {
257         let mut highlights = Vec::new();
258         highlights.extend(
259             def_ranges
260                 .into_iter()
261                 .flatten()
262                 .map(|range| HighlightedRange { category: None, range }),
263         );
264         let body = body?;
265         walk_expr(&body, &mut |expr| match expr {
266             ast::Expr::ReturnExpr(expr) => {
267                 if let Some(token) = expr.return_token() {
268                     highlights.push(HighlightedRange { category: None, range: token.text_range() });
269                 }
270             }
271             ast::Expr::TryExpr(try_) => {
272                 if let Some(token) = try_.question_mark_token() {
273                     highlights.push(HighlightedRange { category: None, range: token.text_range() });
274                 }
275             }
276             ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) => {
277                 if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) {
278                     highlights.push(HighlightedRange {
279                         category: None,
280                         range: expr.syntax().text_range(),
281                     });
282                 }
283             }
284             _ => (),
285         });
286         let tail = match body {
287             ast::Expr::BlockExpr(b) => b.tail_expr(),
288             e => Some(e),
289         };
290 
291         if let Some(tail) = tail {
292             for_each_tail_expr(&tail, &mut |tail| {
293                 let range = match tail {
294                     ast::Expr::BreakExpr(b) => b
295                         .break_token()
296                         .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()),
297                     _ => tail.syntax().text_range(),
298                 };
299                 highlights.push(HighlightedRange { category: None, range })
300             });
301         }
302         Some(highlights)
303     }
304     for anc in token.parent_ancestors() {
305         return match_ast! {
306             match anc {
307                 ast::Fn(fn_) => hl(sema, [fn_.fn_token().map(|it| it.text_range()), None], fn_.body().map(ast::Expr::BlockExpr)),
308                 ast::ClosureExpr(closure) => hl(
309                     sema,
310                     closure.param_list().map_or([None; 2], |p| [p.l_paren_token().map(|it| it.text_range()), p.r_paren_token().map(|it| it.text_range())]),
311                     closure.body()
312                 ),
313                 ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
314                     hl(
315                         sema,
316                         [block_expr.modifier().and_then(|modifier| match modifier {
317                             ast::BlockModifier::Async(t) | ast::BlockModifier::Try(t) | ast::BlockModifier::Const(t) => Some(t.text_range()),
318                             _ => None,
319                         }), None],
320                         Some(block_expr.into())
321                     )
322                 } else {
323                     continue;
324                 },
325                 _ => continue,
326             }
327         };
328     }
329     None
330 }
331 
highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>>332 fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
333     fn hl(
334         cursor_token_kind: SyntaxKind,
335         token: Option<SyntaxToken>,
336         label: Option<ast::Label>,
337         body: Option<ast::StmtList>,
338     ) -> Option<Vec<HighlightedRange>> {
339         let mut highlights = Vec::new();
340         let range = cover_range(
341             token.map(|tok| tok.text_range()),
342             label.as_ref().map(|it| it.syntax().text_range()),
343         );
344         highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
345         for_each_break_and_continue_expr(label, body, &mut |expr| {
346             let range: Option<TextRange> = match (cursor_token_kind, expr) {
347                 (T![for] | T![while] | T![loop] | T![break], ast::Expr::BreakExpr(break_)) => {
348                     cover_range(
349                         break_.break_token().map(|it| it.text_range()),
350                         break_.lifetime().map(|it| it.syntax().text_range()),
351                     )
352                 }
353                 (
354                     T![for] | T![while] | T![loop] | T![continue],
355                     ast::Expr::ContinueExpr(continue_),
356                 ) => cover_range(
357                     continue_.continue_token().map(|it| it.text_range()),
358                     continue_.lifetime().map(|it| it.syntax().text_range()),
359                 ),
360                 _ => None,
361             };
362             highlights.extend(range.map(|range| HighlightedRange { category: None, range }));
363         });
364         Some(highlights)
365     }
366     let parent = token.parent()?;
367     let lbl = match_ast! {
368         match parent {
369             ast::BreakExpr(b) => b.lifetime(),
370             ast::ContinueExpr(c) => c.lifetime(),
371             ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()),
372             ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()),
373             ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()),
374             ast::BlockExpr(b) => Some(b.label().and_then(|it| it.lifetime())?),
375             _ => return None,
376         }
377     };
378     let lbl = lbl.as_ref();
379     let label_matches = |def_lbl: Option<ast::Label>| match lbl {
380         Some(lbl) => {
381             Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text())
382         }
383         None => true,
384     };
385     let token_kind = token.kind();
386     for anc in token.parent_ancestors().flat_map(ast::Expr::cast) {
387         return match anc {
388             ast::Expr::LoopExpr(l) if label_matches(l.label()) => hl(
389                 token_kind,
390                 l.loop_token(),
391                 l.label(),
392                 l.loop_body().and_then(|it| it.stmt_list()),
393             ),
394             ast::Expr::ForExpr(f) if label_matches(f.label()) => hl(
395                 token_kind,
396                 f.for_token(),
397                 f.label(),
398                 f.loop_body().and_then(|it| it.stmt_list()),
399             ),
400             ast::Expr::WhileExpr(w) if label_matches(w.label()) => hl(
401                 token_kind,
402                 w.while_token(),
403                 w.label(),
404                 w.loop_body().and_then(|it| it.stmt_list()),
405             ),
406             ast::Expr::BlockExpr(e) if e.label().is_some() && label_matches(e.label()) => {
407                 hl(token_kind, None, e.label(), e.stmt_list())
408             }
409             _ => continue,
410         };
411     }
412     None
413 }
414 
highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>>415 fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> {
416     fn hl(
417         async_token: Option<SyntaxToken>,
418         body: Option<ast::Expr>,
419     ) -> Option<Vec<HighlightedRange>> {
420         let mut highlights =
421             vec![HighlightedRange { category: None, range: async_token?.text_range() }];
422         if let Some(body) = body {
423             walk_expr(&body, &mut |expr| {
424                 if let ast::Expr::AwaitExpr(expr) = expr {
425                     if let Some(token) = expr.await_token() {
426                         highlights
427                             .push(HighlightedRange { category: None, range: token.text_range() });
428                     }
429                 }
430             });
431         }
432         Some(highlights)
433     }
434     for anc in token.parent_ancestors() {
435         return match_ast! {
436             match anc {
437                 ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)),
438                 ast::BlockExpr(block_expr) => {
439                     if block_expr.async_token().is_none() {
440                         continue;
441                     }
442                     hl(block_expr.async_token(), Some(block_expr.into()))
443                 },
444                 ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()),
445                 _ => continue,
446             }
447         };
448     }
449     None
450 }
451 
cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange>452 fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> {
453     match (r0, r1) {
454         (Some(r0), Some(r1)) => Some(r0.cover(r1)),
455         (Some(range), None) => Some(range),
456         (None, Some(range)) => Some(range),
457         (None, None) => None,
458     }
459 }
460 
find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition>461 fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> {
462     sema.descend_into_macros(token)
463         .into_iter()
464         .filter_map(|token| IdentClass::classify_token(sema, &token))
465         .map(IdentClass::definitions_no_ops)
466         .flatten()
467         .collect()
468 }
469 
470 #[cfg(test)]
471 mod tests {
472     use crate::fixture;
473 
474     use super::*;
475 
476     const ENABLED_CONFIG: HighlightRelatedConfig = HighlightRelatedConfig {
477         break_points: true,
478         exit_points: true,
479         references: true,
480         closure_captures: true,
481         yield_points: true,
482     };
483 
484     #[track_caller]
check(ra_fixture: &str)485     fn check(ra_fixture: &str) {
486         check_with_config(ra_fixture, ENABLED_CONFIG);
487     }
488 
489     #[track_caller]
check_with_config(ra_fixture: &str, config: HighlightRelatedConfig)490     fn check_with_config(ra_fixture: &str, config: HighlightRelatedConfig) {
491         let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
492 
493         let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default();
494 
495         let mut expected = annotations
496             .into_iter()
497             .map(|(r, access)| (r.range, (!access.is_empty()).then_some(access)))
498             .collect::<Vec<_>>();
499 
500         let mut actual = hls
501             .into_iter()
502             .map(|hl| {
503                 (
504                     hl.range,
505                     hl.category.map(|it| {
506                         match it {
507                             ReferenceCategory::Read => "read",
508                             ReferenceCategory::Write => "write",
509                             ReferenceCategory::Import => "import",
510                         }
511                         .to_string()
512                     }),
513                 )
514             })
515             .collect::<Vec<_>>();
516         actual.sort_by_key(|(range, _)| range.start());
517         expected.sort_by_key(|(range, _)| range.start());
518 
519         assert_eq!(expected, actual);
520     }
521 
522     #[test]
test_hl_tuple_fields()523     fn test_hl_tuple_fields() {
524         check(
525             r#"
526 struct Tuple(u32, u32);
527 
528 fn foo(t: Tuple) {
529     t.0$0;
530    // ^ read
531     t.0;
532    // ^ read
533 }
534 "#,
535         );
536     }
537 
538     #[test]
test_hl_module()539     fn test_hl_module() {
540         check(
541             r#"
542 //- /lib.rs
543 mod foo$0;
544  // ^^^
545 //- /foo.rs
546 struct Foo;
547 "#,
548         );
549     }
550 
551     #[test]
test_hl_self_in_crate_root()552     fn test_hl_self_in_crate_root() {
553         check(
554             r#"
555 use crate$0;
556   //^^^^^ import
557 use self;
558   //^^^^ import
559 mod __ {
560     use super;
561       //^^^^^ import
562 }
563 "#,
564         );
565         check(
566             r#"
567 //- /main.rs crate:main deps:lib
568 use lib$0;
569   //^^^ import
570 //- /lib.rs crate:lib
571 "#,
572         );
573     }
574 
575     #[test]
test_hl_self_in_module()576     fn test_hl_self_in_module() {
577         check(
578             r#"
579 //- /lib.rs
580 mod foo;
581 //- /foo.rs
582 use self$0;
583  // ^^^^ import
584 "#,
585         );
586     }
587 
588     #[test]
test_hl_local()589     fn test_hl_local() {
590         check(
591             r#"
592 fn foo() {
593     let mut bar = 3;
594          // ^^^ write
595     bar$0;
596  // ^^^ read
597 }
598 "#,
599         );
600     }
601 
602     #[test]
test_hl_local_in_attr()603     fn test_hl_local_in_attr() {
604         check(
605             r#"
606 //- proc_macros: identity
607 #[proc_macros::identity]
608 fn foo() {
609     let mut bar = 3;
610          // ^^^ write
611     bar$0;
612  // ^^^ read
613 }
614 "#,
615         );
616     }
617 
618     #[test]
test_multi_macro_usage()619     fn test_multi_macro_usage() {
620         check(
621             r#"
622 macro_rules! foo {
623     ($ident:ident) => {
624         fn $ident() -> $ident { loop {} }
625         struct $ident;
626     }
627 }
628 
629 foo!(bar$0);
630   // ^^^
631 fn foo() {
632     let bar: bar = bar();
633           // ^^^
634                 // ^^^
635 }
636 "#,
637         );
638         check(
639             r#"
640 macro_rules! foo {
641     ($ident:ident) => {
642         fn $ident() -> $ident { loop {} }
643         struct $ident;
644     }
645 }
646 
647 foo!(bar);
648   // ^^^
649 fn foo() {
650     let bar: bar$0 = bar();
651           // ^^^
652 }
653 "#,
654         );
655     }
656 
657     #[test]
test_hl_yield_points()658     fn test_hl_yield_points() {
659         check(
660             r#"
661 pub async fn foo() {
662  // ^^^^^
663     let x = foo()
664         .await$0
665       // ^^^^^
666         .await;
667       // ^^^^^
668     || { 0.await };
669     (async { 0.await }).await
670                      // ^^^^^
671 }
672 "#,
673         );
674     }
675 
676     #[test]
test_hl_yield_points2()677     fn test_hl_yield_points2() {
678         check(
679             r#"
680 pub async$0 fn foo() {
681  // ^^^^^
682     let x = foo()
683         .await
684       // ^^^^^
685         .await;
686       // ^^^^^
687     || { 0.await };
688     (async { 0.await }).await
689                      // ^^^^^
690 }
691 "#,
692         );
693     }
694 
695     #[test]
test_hl_let_else_yield_points()696     fn test_hl_let_else_yield_points() {
697         check(
698             r#"
699 pub async fn foo() {
700  // ^^^^^
701     let x = foo()
702         .await$0
703       // ^^^^^
704         .await;
705       // ^^^^^
706     || { 0.await };
707     let Some(_) = None else {
708         foo().await
709            // ^^^^^
710     };
711     (async { 0.await }).await
712                      // ^^^^^
713 }
714 "#,
715         );
716     }
717 
718     #[test]
test_hl_yield_nested_fn()719     fn test_hl_yield_nested_fn() {
720         check(
721             r#"
722 async fn foo() {
723     async fn foo2() {
724  // ^^^^^
725         async fn foo3() {
726             0.await
727         }
728         0.await$0
729        // ^^^^^
730     }
731     0.await
732 }
733 "#,
734         );
735     }
736 
737     #[test]
test_hl_yield_nested_async_blocks()738     fn test_hl_yield_nested_async_blocks() {
739         check(
740             r#"
741 async fn foo() {
742     (async {
743   // ^^^^^
744         (async {
745            0.await
746         }).await$0 }
747         // ^^^^^
748     ).await;
749 }
750 "#,
751         );
752     }
753 
754     #[test]
test_hl_exit_points()755     fn test_hl_exit_points() {
756         check(
757             r#"
758   fn foo() -> u32 {
759 //^^
760     if true {
761         return$0 0;
762      // ^^^^^^
763     }
764 
765     0?;
766   // ^
767     0xDEAD_BEEF
768  // ^^^^^^^^^^^
769 }
770 "#,
771         );
772     }
773 
774     #[test]
test_hl_exit_points2()775     fn test_hl_exit_points2() {
776         check(
777             r#"
778   fn foo() ->$0 u32 {
779 //^^
780     if true {
781         return 0;
782      // ^^^^^^
783     }
784 
785     0?;
786   // ^
787     0xDEAD_BEEF
788  // ^^^^^^^^^^^
789 }
790 "#,
791         );
792     }
793 
794     #[test]
test_hl_exit_points3()795     fn test_hl_exit_points3() {
796         check(
797             r#"
798   fn$0 foo() -> u32 {
799 //^^
800     if true {
801         return 0;
802      // ^^^^^^
803     }
804 
805     0?;
806   // ^
807     0xDEAD_BEEF
808  // ^^^^^^^^^^^
809 }
810 "#,
811         );
812     }
813 
814     #[test]
test_hl_let_else_exit_points()815     fn test_hl_let_else_exit_points() {
816         check(
817             r#"
818   fn$0 foo() -> u32 {
819 //^^
820     let Some(bar) = None else {
821         return 0;
822      // ^^^^^^
823     };
824 
825     0?;
826   // ^
827     0xDEAD_BEEF
828  // ^^^^^^^^^^^
829 }
830 "#,
831         );
832     }
833 
834     #[test]
test_hl_prefer_ref_over_tail_exit()835     fn test_hl_prefer_ref_over_tail_exit() {
836         check(
837             r#"
838 fn foo() -> u32 {
839 // ^^^
840     if true {
841         return 0;
842     }
843 
844     0?;
845 
846     foo$0()
847  // ^^^
848 }
849 "#,
850         );
851     }
852 
853     #[test]
test_hl_never_call_is_exit_point()854     fn test_hl_never_call_is_exit_point() {
855         check(
856             r#"
857 struct Never;
858 impl Never {
859     fn never(self) -> ! { loop {} }
860 }
861 macro_rules! never {
862     () => { never() }
863 }
864 fn never() -> ! { loop {} }
865   fn foo() ->$0 u32 {
866 //^^
867     never();
868  // ^^^^^^^
869     never!();
870  // ^^^^^^^^
871 
872     Never.never();
873  // ^^^^^^^^^^^^^
874 
875     0
876  // ^
877 }
878 "#,
879         );
880     }
881 
882     #[test]
test_hl_inner_tail_exit_points()883     fn test_hl_inner_tail_exit_points() {
884         check(
885             r#"
886   fn foo() ->$0 u32 {
887 //^^
888     if true {
889         unsafe {
890             return 5;
891          // ^^^^^^
892             5
893          // ^
894         }
895     } else if false {
896         0
897      // ^
898     } else {
899         match 5 {
900             6 => 100,
901               // ^^^
902             7 => loop {
903                 break 5;
904              // ^^^^^
905             }
906             8 => 'a: loop {
907                 'b: loop {
908                     break 'a 5;
909                  // ^^^^^
910                     break 'b 5;
911                     break 5;
912                 };
913             }
914             //
915             _ => 500,
916               // ^^^
917         }
918     }
919 }
920 "#,
921         );
922     }
923 
924     #[test]
test_hl_inner_tail_exit_points_labeled_block()925     fn test_hl_inner_tail_exit_points_labeled_block() {
926         check(
927             r#"
928   fn foo() ->$0 u32 {
929 //^^
930     'foo: {
931         break 'foo 0;
932      // ^^^^^
933         loop {
934             break;
935             break 'foo 0;
936          // ^^^^^
937         }
938         0
939      // ^
940     }
941 }
942 "#,
943         );
944     }
945 
946     #[test]
test_hl_inner_tail_exit_points_loops()947     fn test_hl_inner_tail_exit_points_loops() {
948         check(
949             r#"
950   fn foo() ->$0 u32 {
951 //^^
952     'foo: while { return 0; true } {
953                // ^^^^^^
954         break 'foo 0;
955      // ^^^^^
956         return 0;
957      // ^^^^^^
958     }
959 }
960 "#,
961         );
962     }
963 
964     #[test]
test_hl_break_loop()965     fn test_hl_break_loop() {
966         check(
967             r#"
968 fn foo() {
969     'outer: loop {
970  // ^^^^^^^^^^^^
971          break;
972       // ^^^^^
973          'inner: loop {
974             break;
975             'innermost: loop {
976                 break 'outer;
977              // ^^^^^^^^^^^^
978                 break 'inner;
979             }
980             break$0 'outer;
981          // ^^^^^^^^^^^^
982             break;
983         }
984         break;
985      // ^^^^^
986     }
987 }
988 "#,
989         );
990     }
991 
992     #[test]
test_hl_break_loop2()993     fn test_hl_break_loop2() {
994         check(
995             r#"
996 fn foo() {
997     'outer: loop {
998         break;
999         'inner: loop {
1000      // ^^^^^^^^^^^^
1001             break;
1002          // ^^^^^
1003             'innermost: loop {
1004                 break 'outer;
1005                 break 'inner;
1006              // ^^^^^^^^^^^^
1007             }
1008             break 'outer;
1009             break$0;
1010          // ^^^^^
1011         }
1012         break;
1013     }
1014 }
1015 "#,
1016         );
1017     }
1018 
1019     #[test]
test_hl_break_for()1020     fn test_hl_break_for() {
1021         check(
1022             r#"
1023 fn foo() {
1024     'outer: for _ in () {
1025  // ^^^^^^^^^^^
1026          break;
1027       // ^^^^^
1028          'inner: for _ in () {
1029             break;
1030             'innermost: for _ in () {
1031                 break 'outer;
1032              // ^^^^^^^^^^^^
1033                 break 'inner;
1034             }
1035             break$0 'outer;
1036          // ^^^^^^^^^^^^
1037             break;
1038         }
1039         break;
1040      // ^^^^^
1041     }
1042 }
1043 "#,
1044         );
1045     }
1046 
1047     #[test]
test_hl_break_for_but_not_continue()1048     fn test_hl_break_for_but_not_continue() {
1049         check(
1050             r#"
1051 fn foo() {
1052     'outer: for _ in () {
1053  // ^^^^^^^^^^^
1054         break;
1055      // ^^^^^
1056         continue;
1057         'inner: for _ in () {
1058             break;
1059             continue;
1060             'innermost: for _ in () {
1061                 continue 'outer;
1062                 break 'outer;
1063              // ^^^^^^^^^^^^
1064                 continue 'inner;
1065                 break 'inner;
1066             }
1067             break$0 'outer;
1068          // ^^^^^^^^^^^^
1069             continue 'outer;
1070             break;
1071             continue;
1072         }
1073         break;
1074      // ^^^^^
1075         continue;
1076     }
1077 }
1078 "#,
1079         );
1080     }
1081 
1082     #[test]
test_hl_continue_for_but_not_break()1083     fn test_hl_continue_for_but_not_break() {
1084         check(
1085             r#"
1086 fn foo() {
1087     'outer: for _ in () {
1088  // ^^^^^^^^^^^
1089         break;
1090         continue;
1091      // ^^^^^^^^
1092         'inner: for _ in () {
1093             break;
1094             continue;
1095             'innermost: for _ in () {
1096                 continue 'outer;
1097              // ^^^^^^^^^^^^^^^
1098                 break 'outer;
1099                 continue 'inner;
1100                 break 'inner;
1101             }
1102             break 'outer;
1103             continue$0 'outer;
1104          // ^^^^^^^^^^^^^^^
1105             break;
1106             continue;
1107         }
1108         break;
1109         continue;
1110      // ^^^^^^^^
1111     }
1112 }
1113 "#,
1114         );
1115     }
1116 
1117     #[test]
test_hl_break_and_continue()1118     fn test_hl_break_and_continue() {
1119         check(
1120             r#"
1121 fn foo() {
1122     'outer: fo$0r _ in () {
1123  // ^^^^^^^^^^^
1124         break;
1125      // ^^^^^
1126         continue;
1127      // ^^^^^^^^
1128         'inner: for _ in () {
1129             break;
1130             continue;
1131             'innermost: for _ in () {
1132                 continue 'outer;
1133              // ^^^^^^^^^^^^^^^
1134                 break 'outer;
1135              // ^^^^^^^^^^^^
1136                 continue 'inner;
1137                 break 'inner;
1138             }
1139             break 'outer;
1140          // ^^^^^^^^^^^^
1141             continue 'outer;
1142          // ^^^^^^^^^^^^^^^
1143             break;
1144             continue;
1145         }
1146         break;
1147      // ^^^^^
1148         continue;
1149      // ^^^^^^^^
1150     }
1151 }
1152 "#,
1153         );
1154     }
1155 
1156     #[test]
test_hl_break_while()1157     fn test_hl_break_while() {
1158         check(
1159             r#"
1160 fn foo() {
1161     'outer: while true {
1162  // ^^^^^^^^^^^^^
1163          break;
1164       // ^^^^^
1165          'inner: while true {
1166             break;
1167             'innermost: while true {
1168                 break 'outer;
1169              // ^^^^^^^^^^^^
1170                 break 'inner;
1171             }
1172             break$0 'outer;
1173          // ^^^^^^^^^^^^
1174             break;
1175         }
1176         break;
1177      // ^^^^^
1178     }
1179 }
1180 "#,
1181         );
1182     }
1183 
1184     #[test]
test_hl_break_labeled_block()1185     fn test_hl_break_labeled_block() {
1186         check(
1187             r#"
1188 fn foo() {
1189     'outer: {
1190  // ^^^^^^^
1191          break;
1192       // ^^^^^
1193          'inner: {
1194             break;
1195             'innermost: {
1196                 break 'outer;
1197              // ^^^^^^^^^^^^
1198                 break 'inner;
1199             }
1200             break$0 'outer;
1201          // ^^^^^^^^^^^^
1202             break;
1203         }
1204         break;
1205      // ^^^^^
1206     }
1207 }
1208 "#,
1209         );
1210     }
1211 
1212     #[test]
test_hl_break_unlabeled_loop()1213     fn test_hl_break_unlabeled_loop() {
1214         check(
1215             r#"
1216 fn foo() {
1217     loop {
1218  // ^^^^
1219         break$0;
1220      // ^^^^^
1221     }
1222 }
1223 "#,
1224         );
1225     }
1226 
1227     #[test]
test_hl_break_unlabeled_block_in_loop()1228     fn test_hl_break_unlabeled_block_in_loop() {
1229         check(
1230             r#"
1231 fn foo() {
1232     loop {
1233  // ^^^^
1234         {
1235             break$0;
1236          // ^^^^^
1237         }
1238     }
1239 }
1240 "#,
1241         );
1242     }
1243 
1244     #[test]
test_hl_field_shorthand()1245     fn test_hl_field_shorthand() {
1246         check(
1247             r#"
1248 struct Struct { field: u32 }
1249               //^^^^^
1250 fn function(field: u32) {
1251           //^^^^^
1252     Struct { field$0 }
1253            //^^^^^ read
1254 }
1255 "#,
1256         );
1257     }
1258 
1259     #[test]
test_hl_disabled_ref_local()1260     fn test_hl_disabled_ref_local() {
1261         let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1262 
1263         check_with_config(
1264             r#"
1265 fn foo() {
1266     let x$0 = 5;
1267     let y = x * 2;
1268 }
1269 "#,
1270             config,
1271         );
1272     }
1273 
1274     #[test]
test_hl_disabled_ref_local_preserved_break()1275     fn test_hl_disabled_ref_local_preserved_break() {
1276         let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1277 
1278         check_with_config(
1279             r#"
1280 fn foo() {
1281     let x$0 = 5;
1282     let y = x * 2;
1283 
1284     loop {
1285         break;
1286     }
1287 }
1288 "#,
1289             config.clone(),
1290         );
1291 
1292         check_with_config(
1293             r#"
1294 fn foo() {
1295     let x = 5;
1296     let y = x * 2;
1297 
1298     loop$0 {
1299 //  ^^^^
1300         break;
1301 //      ^^^^^
1302     }
1303 }
1304 "#,
1305             config,
1306         );
1307     }
1308 
1309     #[test]
test_hl_disabled_ref_local_preserved_yield()1310     fn test_hl_disabled_ref_local_preserved_yield() {
1311         let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1312 
1313         check_with_config(
1314             r#"
1315 async fn foo() {
1316     let x$0 = 5;
1317     let y = x * 2;
1318 
1319     0.await;
1320 }
1321 "#,
1322             config.clone(),
1323         );
1324 
1325         check_with_config(
1326             r#"
1327     async fn foo() {
1328 //  ^^^^^
1329         let x = 5;
1330         let y = x * 2;
1331 
1332         0.await$0;
1333 //        ^^^^^
1334 }
1335 "#,
1336             config,
1337         );
1338     }
1339 
1340     #[test]
test_hl_disabled_ref_local_preserved_exit()1341     fn test_hl_disabled_ref_local_preserved_exit() {
1342         let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
1343 
1344         check_with_config(
1345             r#"
1346 fn foo() -> i32 {
1347     let x$0 = 5;
1348     let y = x * 2;
1349 
1350     if true {
1351         return y;
1352     }
1353 
1354     0?
1355 }
1356 "#,
1357             config.clone(),
1358         );
1359 
1360         check_with_config(
1361             r#"
1362   fn foo() ->$0 i32 {
1363 //^^
1364     let x = 5;
1365     let y = x * 2;
1366 
1367     if true {
1368         return y;
1369 //      ^^^^^^
1370     }
1371 
1372     0?
1373 //   ^
1374 "#,
1375             config,
1376         );
1377     }
1378 
1379     #[test]
test_hl_disabled_break()1380     fn test_hl_disabled_break() {
1381         let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };
1382 
1383         check_with_config(
1384             r#"
1385 fn foo() {
1386     loop {
1387         break$0;
1388     }
1389 }
1390 "#,
1391             config,
1392         );
1393     }
1394 
1395     #[test]
test_hl_disabled_yield()1396     fn test_hl_disabled_yield() {
1397         let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };
1398 
1399         check_with_config(
1400             r#"
1401 async$0 fn foo() {
1402     0.await;
1403 }
1404 "#,
1405             config,
1406         );
1407     }
1408 
1409     #[test]
test_hl_disabled_exit()1410     fn test_hl_disabled_exit() {
1411         let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };
1412 
1413         check_with_config(
1414             r#"
1415 fn foo() ->$0 i32 {
1416     if true {
1417         return -1;
1418     }
1419 
1420     42
1421 }"#,
1422             config,
1423         );
1424     }
1425 
1426     #[test]
test_hl_multi_local()1427     fn test_hl_multi_local() {
1428         check(
1429             r#"
1430 fn foo((
1431     foo$0
1432   //^^^
1433     | foo
1434     //^^^
1435     | foo
1436     //^^^
1437 ): ()) {
1438     foo;
1439   //^^^read
1440     let foo;
1441 }
1442 "#,
1443         );
1444         check(
1445             r#"
1446 fn foo((
1447     foo
1448   //^^^
1449     | foo$0
1450     //^^^
1451     | foo
1452     //^^^
1453 ): ()) {
1454     foo;
1455   //^^^read
1456     let foo;
1457 }
1458 "#,
1459         );
1460         check(
1461             r#"
1462 fn foo((
1463     foo
1464   //^^^
1465     | foo
1466     //^^^
1467     | foo
1468     //^^^
1469 ): ()) {
1470     foo$0;
1471   //^^^read
1472     let foo;
1473 }
1474 "#,
1475         );
1476     }
1477 
1478     #[test]
test_hl_trait_impl_methods()1479     fn test_hl_trait_impl_methods() {
1480         check(
1481             r#"
1482 trait Trait {
1483     fn func$0(self) {}
1484      //^^^^
1485 }
1486 
1487 impl Trait for () {
1488     fn func(self) {}
1489      //^^^^
1490 }
1491 
1492 fn main() {
1493     <()>::func(());
1494         //^^^^
1495     ().func();
1496      //^^^^
1497 }
1498 "#,
1499         );
1500         check(
1501             r#"
1502 trait Trait {
1503     fn func(self) {}
1504 }
1505 
1506 impl Trait for () {
1507     fn func$0(self) {}
1508      //^^^^
1509 }
1510 
1511 fn main() {
1512     <()>::func(());
1513         //^^^^
1514     ().func();
1515      //^^^^
1516 }
1517 "#,
1518         );
1519         check(
1520             r#"
1521 trait Trait {
1522     fn func(self) {}
1523 }
1524 
1525 impl Trait for () {
1526     fn func(self) {}
1527      //^^^^
1528 }
1529 
1530 fn main() {
1531     <()>::func(());
1532         //^^^^
1533     ().func$0();
1534      //^^^^
1535 }
1536 "#,
1537         );
1538     }
1539 
1540     #[test]
test_assoc_type_highlighting()1541     fn test_assoc_type_highlighting() {
1542         check(
1543             r#"
1544 trait Trait {
1545     type Output;
1546       // ^^^^^^
1547 }
1548 impl Trait for () {
1549     type Output$0 = ();
1550       // ^^^^^^
1551 }
1552 "#,
1553         );
1554     }
1555 
1556     #[test]
test_closure_capture_pipe()1557     fn test_closure_capture_pipe() {
1558         check(
1559             r#"
1560 fn f() {
1561     let x = 1;
1562     //  ^
1563     let c = $0|y| x + y;
1564     //          ^ read
1565 }
1566 "#,
1567         );
1568     }
1569 
1570     #[test]
test_closure_capture_move()1571     fn test_closure_capture_move() {
1572         check(
1573             r#"
1574 fn f() {
1575     let x = 1;
1576     //  ^
1577     let c = move$0 |y| x + y;
1578     //               ^ read
1579 }
1580 "#,
1581         );
1582     }
1583 
1584     #[test]
test_trait_highlights_assoc_item_uses()1585     fn test_trait_highlights_assoc_item_uses() {
1586         check(
1587             r#"
1588 trait Foo {
1589     //^^^
1590     type T;
1591     const C: usize;
1592     fn f() {}
1593     fn m(&self) {}
1594 }
1595 impl Foo for i32 {
1596    //^^^
1597     type T = i32;
1598     const C: usize = 0;
1599     fn f() {}
1600     fn m(&self) {}
1601 }
1602 fn f<T: Foo$0>(t: T) {
1603       //^^^
1604     let _: T::T;
1605             //^
1606     t.m();
1607     //^
1608     T::C;
1609      //^
1610     T::f();
1611      //^
1612 }
1613 
1614 fn f2<T: Foo>(t: T) {
1615        //^^^
1616     let _: T::T;
1617     t.m();
1618     T::C;
1619     T::f();
1620 }
1621 "#,
1622         );
1623     }
1624 }
1625