From f9efbb82bbf24a7e0ac34d04a4b014114454f9a9 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 21 Aug 2026 11:25:29 -0700 Subject: [PATCH 1/2] Add tests for `extract_variable` assist not applying to patterns. --- crates/ide-assists/src/handlers/extract_variable.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index c2c50b16de76..8f6f772844a9 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -952,6 +952,16 @@ fn foo() { check_assist_not_applicable(extract_variable, r#"fn main() { 1 + /* $0comment$0 */ 1; }"#); } + #[test] + fn dont_extract_in_pattern_with_selection() { + check_assist_not_applicable(extract_variable, r#"fn foo() { [].map(|$0bar$0| bar + 1) } "#); + } + + #[test] + fn dont_extract_in_pattern_without_selection() { + check_assist_not_applicable(extract_variable, r#"fn foo() { [].map(|b$0ar| bar + 1) } "#); + } + #[test] fn extract_var_expr_stmt() { cov_mark::check!(test_extract_var_expr_stmt); From ce17ff9b15e8b688f1f4d94bc2588808e7805141 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 21 Aug 2026 11:25:29 -0700 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20Allow=20=E2=80=9CExtract=20variable?= =?UTF-8?q?=E2=80=9D=20to=20be=20invoked=20on=20field=20names=20in=20recor?= =?UTF-8?q?d=20expressions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../src/handlers/extract_variable.rs | 100 +++++++++++++++++- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index 8f6f772844a9..a5239e03fcdd 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -76,10 +76,16 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - if let Some(t) = ctx.token_at_offset().find(|it| it.kind() == T![;]) { t.parent().and_then(ast::ExprStmt::cast)?.syntax().clone() } else { - let expr = ancestors_at_offset(ctx.source_file().syntax(), ctx.offset()) - .next() - .and_then(ast::Expr::cast)?; - expr.syntax().ancestors().find_map(valid_target_expr(ctx))?.syntax().clone() + // Offer the assist only if the nearest syntax node is an expression, or a record + // 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.) + let expr_or_field = ancestors_at_offset(ctx.source_file().syntax(), ctx.offset()) + .find(|it| !ast::NameRef::can_cast(it.kind())) + .and_then(either::Either::::cast)?; + + expr_or_field.syntax().ancestors().find_map(valid_target_expr(ctx))?.syntax().clone() } } else { match ctx.covering_element() { @@ -367,6 +373,11 @@ fn valid_target_expr(ctx: &AssistContext<'_, '_>) -> impl Fn(SyntaxNode) -> Opti let path_resolution = ctx.sema.resolve_path(&path_expr.path()?)?; like_const_value(ctx, path_resolution).then_some(path_expr.into()) } + SyntaxKind::RECORD_EXPR_FIELD => { + // If we are on `k` in `Struct { k: v }`, then extract `v`. + let record_field = ast::RecordExprField::cast(node)?; + record_field.expr() + } _ => ast::Expr::cast(node), } } @@ -1596,6 +1607,87 @@ struct S { foo: i32 } +fn main() { + let $0foo = 1 + 1; + S { foo } +} +"#, + "Extract into variable", + ) + } + + #[test] + fn extract_var_from_record_field() { + check_assist_by_label( + extract_variable, + r#" +struct S { + foo: i32 +} + +fn main() { + S { $0foo: 1 + 1,$0 } +} +"#, + r#" +struct S { + foo: i32 +} + +fn main() { + let $0foo = 1 + 1; + S { foo, } +} +"#, + "Extract into variable", + ) + } + + #[test] + fn extract_var_from_record_field_name() { + check_assist_by_label( + extract_variable, + r#" +struct S { + foo: i32 +} + +fn main() { + S { f$0oo: 1 + 1 } +} +"#, + r#" +struct S { + foo: i32 +} + +fn main() { + let $0foo = 1 + 1; + S { foo } +} +"#, + "Extract into variable", + ) + } + + #[test] + fn extract_var_from_record_field_colon() { + check_assist_by_label( + extract_variable, + r#" +struct S { + foo: i32 +} + +fn main() { + S { foo $0: 1 + 1 } +} +"#, + r#" +struct S { + foo: i32 +} + fn main() { let $0foo = 1 + 1; S { foo }