Skip to content

feat: Allow “Extract variable” to be invoked on field names in record expressions. - #23213

Merged
A4-Tacks merged 2 commits into
rust-lang:masterfrom
kpreid:extract
Aug 26, 2026
Merged

feat: Allow “Extract variable” to be invoked on field names in record expressions.#23213
A4-Tacks merged 2 commits into
rust-lang:masterfrom
kpreid:extract

Conversation

@kpreid

@kpreid kpreid commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

This change allows a “mistake” I make very often to succeed: putting the cursor on “foo” in Struct { foo: bar() } when I want to extract let foo = bar();.

It is also preparation for being able to extract multiple field expressions at once (#21863), because it generalizes the analysis of the selection to be able to work on nodes that are not themselves expressions.

Notes

The added take_while() and the tests dont_extract_in_pattern_* are an attempt to preserve the behavior added in #18982, but that PR did not add any tests or clearly specify what effect it was intended to have, so I may have gotten it wrong.

This is only my second contribution to assists, so all feedback is welcome.

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 21, 2026
// expression.
let kind = node.kind();
ast::Expr::can_cast(kind) || kind == SyntaxKind::RECORD_EXPR_FIELD
})

@A4-Tacks A4-Tacks Aug 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why add this? I noticed without this, dont_extract_in_pattern would still pass through
Try not to break

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

On revisiting this, there were several problems.

  • The dont_extract_in_pattern test did not actually test this code path, because the test had a selection and this is the empty-selection code path. I’ve fixed that by adding another test, and also split out those tests to a separate commit to clarify that they are testing existing behavior.
  • I don’t actually need to rewrite this as much as I did. I’ve now kept the existing structure, and only changed ast::Expr::cast into the broader condition needed to operate on field names.
  • The skip(1) was entirely my mistake in understanding the existing code, and has been removed.

Comment thread crates/ide-assists/src/handlers/extract_variable.rs Outdated
@rustbot

rustbot commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@kpreid
kpreid force-pushed the extract branch 3 times, most recently from 63fa313 to 68abcda Compare August 25, 2026 22:33
.is_some_and(|p| p.kind() == SyntaxKind::RECORD_EXPR_FIELD))
})?;

node.ancestors().find_map(valid_target_expr(ctx))?.syntax().clone()

@A4-Tacks A4-Tacks Aug 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don't be so complicated, just:

             let expr = ancestors_at_offset(ctx.source_file().syntax(), ctx.offset())
-                .next()
-                .and_then(ast::Expr::cast)?;
+                .find(|it| !ast::NameRef::can_cast(it.kind()))
+                .and_then(either::Either::<ast::Expr, ast::RecordExprField>::cast)?;
             expr.syntax().ancestors().find_map(valid_target_expr(ctx))?.syntax().clone()

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

… expressions.

This change allows a “mistake” I make very often to succeed: putting
the cursor on “foo” in `Struct { foo: bar() }` when I want to
extract `let foo = bar();`.

It is also groundwork for being able to extract multiple field
expressions at once.
// field, or a record field’s name. This prevents the assist from appearing when
// it is unlikely to be relevant, such as when the cursor is in a pattern.
// (If we did not want to restrict it this way, we could just apply
// `valid_target_expr()` to all ancestors.)

@A4-Tacks A4-Tacks Aug 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Redundant comments, we have testing

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This comment is not for specifying the desired functionality, but for someone reading the code (like me) to understand what the goal of this piece of the code is. Not having this part explained was a significant obstacle; I had to dig into git-blame to understand what the code was supposed to achieve.

@A4-Tacks
A4-Tacks added this pull request to the merge queue Aug 26, 2026
Merged via the queue into rust-lang:master with commit 8f07bcc Aug 26, 2026
18 checks passed
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 26, 2026
@kpreid
kpreid deleted the extract branch August 26, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants