Skip to content

feat: highlighting of return values while the cursor is on match / if / => #19546

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

roife
Copy link
Member

@roife roife commented Apr 9, 2025

Inspired by #18924, a follow-up to #17542.

This PR adds a new feature that highlights the related return values when the cursor is on match, if, or a match arm arrow (=>). This helps users quickly identify the different possible outcomes of control flow expressions.

For match keyword:

  • The match keyword itself
  • All return expressions in each arm of the match

For => (match arm arrow):

  • The arrow itself
  • The return expression for that specific arm

For if keyword:

  • All if keywords in the if-else chain
  • All return expressions in the if-else branches

This feature works with nested expressions and macros.

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 9, 2025
@roife
Copy link
Member Author

roife commented Apr 9, 2025

Here's a demo.

2025-04-09.10.32.29.mov

@roife roife force-pushed the branch-value-highlights branch from 8fb7504 to 6d3a9e1 Compare April 9, 2025 08:29
Comment on lines 408 to 511
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)
.take_while(|node| !ast::MacroCall::can_cast(node.kind()))
.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)
.take_while(|node| !ast::MacroCall::can_cast(node.kind()))
.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)
.take_while(|node| !ast::MacroCall::can_cast(node.kind()))
.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)
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

judging by the tests, this doesn't really do anything signifcant. Can we drop this?

Copy link
Member Author

@roife roife Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose here is "when a user performs a goto-def on the match kw, server should navigate to the match kw itself."

This approach is more from a user experience perspective. In VS Code, if a user perform a goto-def on an identifier and the response is the identifier itself, VS Code would understand that this location is a definition. Therefore, VS Code will automatically trigger find-references.

This is very intuitive for users: when I perform ctrl + click on match kw, it will automatically trigger find-references to locate all branch exit points.

The current logic here is indeed quite complicated. I will try to simplify it to make it look more elegant.

@@ -285,11 +290,129 @@ fn highlight_references(
if res.is_empty() { None } else { Some(res.into_iter().collect()) }
}

pub(crate) fn highlight_branches(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is hte implementation of this so different from the test. I'd expect this to look fairly similar to hl_exit_points

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For match and match_arm, it's much simpler than exit_points (they only need to handle tail_expr).

For if, there is an additional step to find the root node of if-else chains.

I have pushed a new commit to simplify this function.

@roife roife force-pushed the branch-value-highlights branch from 3789184 to 335d67b Compare April 15, 2025 14:47
@roife roife force-pushed the branch-value-highlights branch from 335d67b to e23e442 Compare April 17, 2025 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants