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