about summary refs log tree commit diff
diff options
context:
space:
mode:
authorroifewu <roifewu@gmail.com>2025-04-09 11:29:05 +0800
committerroifewu <roifewu@gmail.com>2025-06-26 13:41:39 +0800
commit62e112a780a25dbaba783324d8ecd7929076674a (patch)
tree2efcb1f59f867b767f0a434ec9a7a618c5c74051
parent3e5c46fe6c123b237f2dc9fb653c65f5b6de9382 (diff)
downloadrust-62e112a780a25dbaba783324d8ecd7929076674a.tar.gz
rust-62e112a780a25dbaba783324d8ecd7929076674a.zip
feat: highlighting of related return values while the cursor is on any `match`, `if`, or match arm arrow (`=>`)
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_definition.rs210
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/highlight_related.rs333
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/references.rs221
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs3
-rw-r--r--src/tools/rust-analyzer/docs/book/src/configuration_generated.md7
-rw-r--r--src/tools/rust-analyzer/editors/code/package.json10
6 files changed, 775 insertions, 9 deletions
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
index 574803fb9e8..0efc6cfe9b7 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
@@ -298,6 +298,7 @@ fn handle_control_flow_keywords(
         T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => {
             nav_for_break_points(sema, token)
         }
+        T![match] | T![=>] | T![if] => nav_for_branches(sema, token),
         _ => None,
     }
 }
@@ -407,6 +408,64 @@ fn nav_for_exit_points(
     Some(navs)
 }
 
+fn nav_for_branches(
+    sema: &Semantics<'_, RootDatabase>,
+    token: &SyntaxToken,
+) -> Option<Vec<NavigationTarget>> {
+    let db = sema.db;
+
+    let navs = match token.kind() {
+        T![match] => sema
+            .descend_into_macros(token.clone())
+            .into_iter()
+            .filter_map(|token| {
+                let match_expr =
+                    sema.token_ancestors_with_macros(token).find_map(ast::MatchExpr::cast)?;
+                let file_id = sema.hir_file_for(match_expr.syntax());
+                let focus_range = match_expr.match_token()?.text_range();
+                let match_expr_in_file = InFile::new(file_id, match_expr.into());
+                Some(expr_to_nav(db, match_expr_in_file, Some(focus_range)))
+            })
+            .flatten()
+            .collect_vec(),
+
+        T![=>] => sema
+            .descend_into_macros(token.clone())
+            .into_iter()
+            .filter_map(|token| {
+                let match_arm =
+                    sema.token_ancestors_with_macros(token).find_map(ast::MatchArm::cast)?;
+                let match_expr = sema
+                    .ancestors_with_macros(match_arm.syntax().clone())
+                    .find_map(ast::MatchExpr::cast)?;
+                let file_id = sema.hir_file_for(match_expr.syntax());
+                let focus_range = match_arm.fat_arrow_token()?.text_range();
+                let match_expr_in_file = InFile::new(file_id, match_expr.into());
+                Some(expr_to_nav(db, match_expr_in_file, Some(focus_range)))
+            })
+            .flatten()
+            .collect_vec(),
+
+        T![if] => sema
+            .descend_into_macros(token.clone())
+            .into_iter()
+            .filter_map(|token| {
+                let if_expr =
+                    sema.token_ancestors_with_macros(token).find_map(ast::IfExpr::cast)?;
+                let file_id = sema.hir_file_for(if_expr.syntax());
+                let focus_range = if_expr.if_token()?.text_range();
+                let if_expr_in_file = InFile::new(file_id, if_expr.into());
+                Some(expr_to_nav(db, if_expr_in_file, Some(focus_range)))
+            })
+            .flatten()
+            .collect_vec(),
+
+        _ => return Some(Vec::new()),
+    };
+
+    Some(navs)
+}
+
 pub(crate) fn find_loops(
     sema: &Semantics<'_, RootDatabase>,
     token: &SyntaxToken,
@@ -3614,4 +3673,155 @@ fn foo() {
         "#,
         );
     }
+
+    #[test]
+    fn goto_def_for_match_keyword() {
+        check(
+            r#"
+fn main() {
+    match$0 0 {
+ // ^^^^^
+        0 => {},
+        _ => {},
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_def_for_match_arm_fat_arrow() {
+        check(
+            r#"
+fn main() {
+    match 0 {
+        0 =>$0 {},
+       // ^^
+        _ => {},
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_def_for_if_keyword() {
+        check(
+            r#"
+fn main() {
+    if$0 true {
+ // ^^
+        ()
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_def_for_match_nested_in_if() {
+        check(
+            r#"
+fn main() {
+    if true {
+        match$0 0 {
+     // ^^^^^
+            0 => {},
+            _ => {},
+        }
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_def_for_multiple_match_expressions() {
+        check(
+            r#"
+fn main() {
+    match 0 {
+        0 => {},
+        _ => {},
+    };
+
+    match$0 1 {
+ // ^^^^^
+        1 => {},
+        _ => {},
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_def_for_nested_match_expressions() {
+        check(
+            r#"
+fn main() {
+    match 0 {
+        0 => match$0 1 {
+          // ^^^^^
+            1 => {},
+            _ => {},
+        },
+        _ => {},
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_def_for_if_else_chains() {
+        check(
+            r#"
+fn main() {
+    if true {
+        ()
+    } else if$0 false {
+        // ^^
+        ()
+    } else {
+        ()
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_def_for_match_with_guards() {
+        check(
+            r#"
+fn main() {
+    match 42 {
+        x if x > 0 =>$0 {},
+                // ^^
+        _ => {},
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_def_for_match_with_macro_arm() {
+        check(
+            r#"
+macro_rules! arm {
+    () => { 0 => {} };
+}
+
+fn main() {
+    match$0 0 {
+ // ^^^^^
+        arm!(),
+        _ => {},
+    }
+}
+"#,
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
index aa947921a9b..8d93cd63283 100644
--- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
@@ -37,8 +37,11 @@ pub struct HighlightRelatedConfig {
     pub break_points: bool,
     pub closure_captures: bool,
     pub yield_points: bool,
+    pub branches: bool,
 }
 
+type HighlightMap = FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>;
+
 // Feature: Highlight Related
 //
 // Highlights constructs related to the thing under the cursor:
@@ -64,7 +67,7 @@ pub(crate) fn highlight_related(
 
     let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
         T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
-        T![->] => 4,
+        T![->] | T![=>] => 4,
         kind if kind.is_keyword(file_id.edition(sema.db)) => 3,
         IDENT | INT_NUMBER => 2,
         T![|] => 1,
@@ -78,6 +81,9 @@ pub(crate) fn highlight_related(
         T![fn] | T![return] | T![->] if config.exit_points => {
             highlight_exit_points(sema, token).remove(&file_id)
         }
+        T![match] | T![=>] | T![if] if config.branches => {
+            highlight_branches(sema, token).remove(&file_id)
+        }
         T![await] | T![async] if config.yield_points => {
             highlight_yield_points(sema, token).remove(&file_id)
         }
@@ -300,11 +306,122 @@ fn highlight_references(
     if res.is_empty() { None } else { Some(res.into_iter().collect()) }
 }
 
+pub(crate) fn highlight_branches(
+    sema: &Semantics<'_, RootDatabase>,
+    token: SyntaxToken,
+) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
+    let mut highlights: HighlightMap = FxHashMap::default();
+
+    let push_to_highlights = |file_id, range, highlights: &mut HighlightMap| {
+        if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
+            let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
+            highlights.entry(file_id).or_default().insert(hrange);
+        }
+    };
+
+    let push_tail_expr = |tail: Option<ast::Expr>, highlights: &mut HighlightMap| {
+        let Some(tail) = tail else {
+            return;
+        };
+
+        for_each_tail_expr(&tail, &mut |tail| {
+            let file_id = sema.hir_file_for(tail.syntax());
+            let range = tail.syntax().text_range();
+            push_to_highlights(file_id, Some(range), highlights);
+        });
+    };
+
+    match token.kind() {
+        T![match] => {
+            for token in sema.descend_into_macros(token.clone()) {
+                let Some(match_expr) =
+                    sema.token_ancestors_with_macros(token).find_map(ast::MatchExpr::cast)
+                else {
+                    continue;
+                };
+
+                let file_id = sema.hir_file_for(match_expr.syntax());
+                let range = match_expr.match_token().map(|token| token.text_range());
+                push_to_highlights(file_id, range, &mut highlights);
+
+                let Some(arm_list) = match_expr.match_arm_list() else {
+                    continue;
+                };
+
+                for arm in arm_list.arms() {
+                    push_tail_expr(arm.expr(), &mut highlights);
+                }
+            }
+        }
+        T![=>] => {
+            for token in sema.descend_into_macros(token.clone()) {
+                let Some(arm) =
+                    sema.token_ancestors_with_macros(token).find_map(ast::MatchArm::cast)
+                else {
+                    continue;
+                };
+                let file_id = sema.hir_file_for(arm.syntax());
+                let range = arm.fat_arrow_token().map(|token| token.text_range());
+                push_to_highlights(file_id, range, &mut highlights);
+
+                push_tail_expr(arm.expr(), &mut highlights);
+            }
+        }
+        T![if] => {
+            for tok in sema.descend_into_macros(token.clone()) {
+                let Some(if_expr) =
+                    sema.token_ancestors_with_macros(tok).find_map(ast::IfExpr::cast)
+                else {
+                    continue;
+                };
+
+                // Find the root of the if expression
+                let mut if_to_process = iter::successors(Some(if_expr.clone()), |if_expr| {
+                    let parent_if = if_expr.syntax().parent().and_then(ast::IfExpr::cast)?;
+                    if let ast::ElseBranch::IfExpr(nested_if) = parent_if.else_branch()? {
+                        (nested_if.syntax() == if_expr.syntax()).then_some(parent_if)
+                    } else {
+                        None
+                    }
+                })
+                .last()
+                .or(Some(if_expr));
+
+                while let Some(cur_if) = if_to_process.take() {
+                    let file_id = sema.hir_file_for(cur_if.syntax());
+
+                    let if_kw_range = cur_if.if_token().map(|token| token.text_range());
+                    push_to_highlights(file_id, if_kw_range, &mut highlights);
+
+                    if let Some(then_block) = cur_if.then_branch() {
+                        push_tail_expr(Some(then_block.into()), &mut highlights);
+                    }
+
+                    match cur_if.else_branch() {
+                        Some(ast::ElseBranch::Block(else_block)) => {
+                            push_tail_expr(Some(else_block.into()), &mut highlights);
+                            if_to_process = None;
+                        }
+                        Some(ast::ElseBranch::IfExpr(nested_if)) => if_to_process = Some(nested_if),
+                        None => if_to_process = None,
+                    }
+                }
+            }
+        }
+        _ => unreachable!(),
+    }
+
+    highlights
+        .into_iter()
+        .map(|(file_id, ranges)| (file_id, ranges.into_iter().collect()))
+        .collect()
+}
+
 fn hl_exit_points(
     sema: &Semantics<'_, RootDatabase>,
     def_token: Option<SyntaxToken>,
     body: ast::Expr,
-) -> Option<FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>> {
+) -> Option<HighlightMap> {
     let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
 
     let mut push_to_highlights = |file_id, range| {
@@ -411,7 +528,7 @@ pub(crate) fn highlight_break_points(
         loop_token: Option<SyntaxToken>,
         label: Option<ast::Label>,
         expr: ast::Expr,
-    ) -> Option<FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>> {
+    ) -> Option<HighlightMap> {
         let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
 
         let mut push_to_highlights = |file_id, range| {
@@ -504,7 +621,7 @@ pub(crate) fn highlight_yield_points(
         sema: &Semantics<'_, RootDatabase>,
         async_token: Option<SyntaxToken>,
         body: Option<ast::Expr>,
-    ) -> Option<FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>> {
+    ) -> Option<HighlightMap> {
         let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
 
         let mut push_to_highlights = |file_id, range| {
@@ -597,10 +714,7 @@ fn original_frange(
     InFile::new(file_id, text_range?).original_node_file_range_opt(db).map(|(frange, _)| frange)
 }
 
-fn merge_map(
-    res: &mut FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>,
-    new: Option<FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>>,
-) {
+fn merge_map(res: &mut HighlightMap, new: Option<HighlightMap>) {
     let Some(new) = new else {
         return;
     };
@@ -750,6 +864,7 @@ mod tests {
         references: true,
         closure_captures: true,
         yield_points: true,
+        branches: true,
     };
 
     #[track_caller]
@@ -2135,6 +2250,62 @@ fn main() {
     }
 
     #[test]
+    fn nested_match() {
+        check(
+            r#"
+fn main() {
+    match$0 0 {
+ // ^^^^^
+        0 => match 1 {
+            1 => 2,
+              // ^
+            _ => 3,
+              // ^
+        },
+        _ => 4,
+          // ^
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn single_arm_highlight() {
+        check(
+            r#"
+fn main() {
+    match 0 {
+        0 =>$0 {
+       // ^^
+            let x = 1;
+            x
+         // ^
+        }
+        _ => 2,
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn no_branches_when_disabled() {
+        let config = HighlightRelatedConfig { branches: false, ..ENABLED_CONFIG };
+        check_with_config(
+            r#"
+fn main() {
+    match$0 0 {
+        0 => 1,
+        _ => 2,
+    }
+}
+"#,
+            config,
+        );
+    }
+
+    #[test]
     fn asm() {
         check(
             r#"
@@ -2165,6 +2336,152 @@ pub unsafe fn bootstrap() -> ! {
     }
 
     #[test]
+    fn complex_arms_highlight() {
+        check(
+            r#"
+fn calculate(n: i32) -> i32 { n * 2 }
+
+fn main() {
+    match$0 Some(1) {
+ // ^^^^^
+        Some(x) => match x {
+            0 => { let y = x; y },
+                           // ^
+            1 => calculate(x),
+               //^^^^^^^^^^^^
+            _ => (|| 6)(),
+              // ^^^^^^^^
+        },
+        None => loop {
+            break 5;
+         // ^^^^^^^
+        },
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn match_in_macro_highlight() {
+        check(
+            r#"
+macro_rules! M {
+    ($e:expr) => { $e };
+}
+
+fn main() {
+    M!{
+        match$0 Some(1) {
+     // ^^^^^
+            Some(x) => x,
+                    // ^
+            None => 0,
+                 // ^
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn nested_if_else() {
+        check(
+            r#"
+fn main() {
+    if$0 true {
+ // ^^
+        if false {
+            1
+         // ^
+        } else {
+            2
+         // ^
+        }
+    } else {
+        3
+     // ^
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn if_else_if_highlight() {
+        check(
+            r#"
+fn main() {
+    if$0 true {
+ // ^^
+        1
+     // ^
+    } else if false {
+        // ^^
+        2
+     // ^
+    } else {
+        3
+     // ^
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn complex_if_branches() {
+        check(
+            r#"
+fn calculate(n: i32) -> i32 { n * 2 }
+
+fn main() {
+    if$0 true {
+ // ^^
+        let x = 5;
+        calculate(x)
+     // ^^^^^^^^^^^^
+    } else if false {
+        // ^^
+        (|| 10)()
+     // ^^^^^^^^^
+    } else {
+        loop {
+            break 15;
+         // ^^^^^^^^
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn if_in_macro_highlight() {
+        check(
+            r#"
+macro_rules! M {
+    ($e:expr) => { $e };
+}
+
+fn main() {
+    M!{
+        if$0 true {
+     // ^^
+            5
+         // ^
+        } else {
+            10
+         // ^^
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
     fn labeled_block_tail_expr() {
         check(
             r#"
diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs
index c6a323d4081..900f49910d3 100644
--- a/src/tools/rust-analyzer/crates/ide/src/references.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/references.rs
@@ -397,7 +397,10 @@ fn handle_control_flow_keywords(
         .attach_first_edition(file_id)
         .map(|it| it.edition(sema.db))
         .unwrap_or(Edition::CURRENT);
-    let token = file.syntax().token_at_offset(offset).find(|t| t.kind().is_keyword(edition))?;
+    let token = file
+        .syntax()
+        .token_at_offset(offset)
+        .find(|t| t.kind().is_keyword(edition) || t.kind() == T![=>])?;
 
     let references = match token.kind() {
         T![fn] | T![return] | T![try] => highlight_related::highlight_exit_points(sema, token),
@@ -408,6 +411,7 @@ fn handle_control_flow_keywords(
         T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => {
             highlight_related::highlight_break_points(sema, token)
         }
+        T![if] | T![=>] | T![match] => highlight_related::highlight_branches(sema, token),
         _ => return None,
     }
     .into_iter()
@@ -1344,6 +1348,159 @@ impl Foo {
         );
     }
 
+    #[test]
+    fn test_highlight_if_branches() {
+        check(
+            r#"
+fn main() {
+    let x = if$0 true {
+        1
+    } else if false {
+        2
+    } else {
+        3
+    };
+
+    println!("x: {}", x);
+}
+"#,
+            expect![[r#"
+                FileId(0) 24..26
+                FileId(0) 42..43
+                FileId(0) 55..57
+                FileId(0) 74..75
+                FileId(0) 97..98
+            "#]],
+        );
+    }
+
+    #[test]
+    fn test_highlight_match_branches() {
+        check(
+            r#"
+fn main() {
+    $0match Some(42) {
+        Some(x) if x > 0 => println!("positive"),
+        Some(0) => println!("zero"),
+        Some(_) => println!("negative"),
+        None => println!("none"),
+    };
+}
+"#,
+            expect![[r#"
+                FileId(0) 16..21
+                FileId(0) 61..81
+                FileId(0) 102..118
+                FileId(0) 139..159
+                FileId(0) 177..193
+            "#]],
+        );
+    }
+
+    #[test]
+    fn test_highlight_match_arm_arrow() {
+        check(
+            r#"
+fn main() {
+    match Some(42) {
+        Some(x) if x > 0 $0=> println!("positive"),
+        Some(0) => println!("zero"),
+        Some(_) => println!("negative"),
+        None => println!("none"),
+    }
+}
+"#,
+            expect![[r#"
+                FileId(0) 58..60
+                FileId(0) 61..81
+            "#]],
+        );
+    }
+
+    #[test]
+    fn test_highlight_nested_branches() {
+        check(
+            r#"
+fn main() {
+    let x = $0if true {
+        if false {
+            1
+        } else {
+            match Some(42) {
+                Some(_) => 2,
+                None => 3,
+            }
+        }
+    } else {
+        4
+    };
+
+    println!("x: {}", x);
+}
+"#,
+            expect![[r#"
+                FileId(0) 24..26
+                FileId(0) 65..66
+                FileId(0) 140..141
+                FileId(0) 167..168
+                FileId(0) 215..216
+            "#]],
+        );
+    }
+
+    #[test]
+    fn test_highlight_match_with_complex_guards() {
+        check(
+            r#"
+fn main() {
+    let x = $0match (x, y) {
+        (a, b) if a > b && a % 2 == 0 => 1,
+        (a, b) if a < b || b % 2 == 1 => 2,
+        (a, _) if a > 40 => 3,
+        _ => 4,
+    };
+
+    println!("x: {}", x);
+}
+"#,
+            expect![[r#"
+                FileId(0) 24..29
+                FileId(0) 80..81
+                FileId(0) 124..125
+                FileId(0) 155..156
+                FileId(0) 171..172
+            "#]],
+        );
+    }
+
+    #[test]
+    fn test_highlight_mixed_if_match_expressions() {
+        check(
+            r#"
+fn main() {
+    let x = $0if let Some(x) = Some(42) {
+        1
+    } else if let None = None {
+        2
+    } else {
+        match 42 {
+            0 => 3,
+            _ => 4,
+        }
+    };
+}
+"#,
+            expect![[r#"
+                FileId(0) 24..26
+                FileId(0) 60..61
+                FileId(0) 73..75
+                FileId(0) 102..103
+                FileId(0) 153..154
+                FileId(0) 173..174
+            "#]],
+        );
+    }
+
     fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
         check_with_scope(ra_fixture, None, expect)
     }
@@ -2867,4 +3024,66 @@ const FOO$0: i32 = 0;
             "#]],
         );
     }
+
+    #[test]
+    fn test_highlight_if_let_match_combined() {
+        check(
+            r#"
+enum MyEnum { A(i32), B(String), C }
+
+fn main() {
+    let val = MyEnum::A(42);
+
+    let x = $0if let MyEnum::A(x) = val {
+        1
+    } else if let MyEnum::B(s) = val {
+        2
+    } else {
+        match val {
+            MyEnum::C => 3,
+            _ => 4,
+        }
+    };
+}
+"#,
+            expect![[r#"
+                FileId(0) 92..94
+                FileId(0) 128..129
+                FileId(0) 141..143
+                FileId(0) 177..178
+                FileId(0) 237..238
+                FileId(0) 257..258
+            "#]],
+        );
+    }
+
+    #[test]
+    fn test_highlight_nested_match_expressions() {
+        check(
+            r#"
+enum Outer { A(Inner), B }
+enum Inner { X, Y(i32) }
+
+fn main() {
+    let val = Outer::A(Inner::Y(42));
+
+    $0match val {
+        Outer::A(inner) => match inner {
+            Inner::X => println!("Inner::X"),
+            Inner::Y(n) if n > 0 => println!("Inner::Y positive: {}", n),
+            Inner::Y(_) => println!("Inner::Y non-positive"),
+        },
+        Outer::B => println!("Outer::B"),
+    }
+}
+"#,
+            expect![[r#"
+                FileId(0) 108..113
+                FileId(0) 185..205
+                FileId(0) 243..279
+                FileId(0) 308..341
+                FileId(0) 374..394
+            "#]],
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
index 05e1b832cd1..cc8711f2c08 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs
@@ -94,6 +94,8 @@ config_data! {
 
 
 
+        /// Enables highlighting of related return values while the cursor is on any `match`, `if`, or match arm arrow (`=>`).
+        highlightRelated_branches_enable: bool = true,
         /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
         highlightRelated_breakPoints_enable: bool = true,
         /// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.
@@ -1629,6 +1631,7 @@ impl Config {
             exit_points: self.highlightRelated_exitPoints_enable().to_owned(),
             yield_points: self.highlightRelated_yieldPoints_enable().to_owned(),
             closure_captures: self.highlightRelated_closureCaptures_enable().to_owned(),
+            branches: self.highlightRelated_branches_enable().to_owned(),
         }
     }
 
diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md
index 9404b1454a0..587c09234c1 100644
--- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md
+++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md
@@ -612,6 +612,13 @@ Default: `"client"`
 Controls file watching implementation.
 
 
+## rust-analyzer.highlightRelated.branches.enable {#highlightRelated.branches.enable}
+
+Default: `true`
+
+Enables highlighting of related return values while the cursor is on any `match`, `if`, or match arm arrow (`=>`).
+
+
 ## rust-analyzer.highlightRelated.breakPoints.enable {#highlightRelated.breakPoints.enable}
 
 Default: `true`
diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json
index 26a21c1468d..c792ef0ffdc 100644
--- a/src/tools/rust-analyzer/editors/code/package.json
+++ b/src/tools/rust-analyzer/editors/code/package.json
@@ -1532,6 +1532,16 @@
             {
                 "title": "highlightRelated",
                 "properties": {
+                    "rust-analyzer.highlightRelated.branches.enable": {
+                        "markdownDescription": "Enables highlighting of related return values while the cursor is on any `match`, `if`, or match arm arrow (`=>`).",
+                        "default": true,
+                        "type": "boolean"
+                    }
+                }
+            },
+            {
+                "title": "highlightRelated",
+                "properties": {
                     "rust-analyzer.highlightRelated.breakPoints.enable": {
                         "markdownDescription": "Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.",
                         "default": true,