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