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