Skip to content

Commit 34999b7

Browse files
committed
Handle bindings to refs
1 parent 3f0ea50 commit 34999b7

File tree

4 files changed

+213
-143
lines changed

4 files changed

+213
-143
lines changed

crates/ide-assists/src/handlers/destructure_struct_binding.rs

+72-26
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ use itertools::Itertools;
1010
use syntax::{ast, ted, AstNode, SmolStr};
1111
use text_edit::TextRange;
1212

13-
use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder};
13+
use crate::{
14+
assist_context::{AssistContext, Assists, SourceChangeBuilder},
15+
utils::ref_field_expr::determine_ref_and_parens,
16+
};
1417

1518
// Assist: destructure_struct_binding
1619
//
@@ -58,11 +61,12 @@ fn destructure_struct_binding_impl(
5861
builder: &mut SourceChangeBuilder,
5962
data: &StructEditData,
6063
) {
61-
let assignment_edit = build_assignment_edit(ctx, builder, data);
62-
let usage_edits = build_usage_edits(ctx, builder, data, &assignment_edit.field_name_map);
64+
let field_names = generate_field_names(ctx, data);
65+
let assignment_edit = build_assignment_edit(ctx, builder, data, &field_names);
66+
let usage_edits = build_usage_edits(ctx, builder, data, &field_names.into_iter().collect());
6367

6468
assignment_edit.apply();
65-
for edit in usage_edits.unwrap_or_default() {
69+
for edit in usage_edits.into_iter().flatten() {
6670
edit.apply(builder);
6771
}
6872
}
@@ -74,14 +78,16 @@ struct StructEditData {
7478
visible_fields: Vec<hir::Field>,
7579
usages: Option<UsageSearchResult>,
7680
names_in_scope: FxHashSet<SmolStr>, // TODO currently always empty
77-
add_rest: bool,
81+
has_private_members: bool,
7882
is_nested: bool,
83+
is_ref: bool,
7984
}
8085

8186
fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<StructEditData> {
82-
let ty = ctx.sema.type_of_binding_in_pat(&ident_pat)?.strip_references().as_adt()?;
87+
let ty = ctx.sema.type_of_binding_in_pat(&ident_pat)?;
88+
let is_ref = ty.is_reference();
8389

84-
let hir::Adt::Struct(struct_type) = ty else { return None };
90+
let hir::Adt::Struct(struct_type) = ty.strip_references().as_adt()? else { return None };
8591

8692
let module = ctx.sema.scope(ident_pat.syntax())?.module();
8793
let struct_def = hir::ModuleDef::from(struct_type);
@@ -97,8 +103,9 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
97103
let visible_fields =
98104
fields.into_iter().filter(|field| field.is_visible_from(ctx.db(), module)).collect_vec();
99105

100-
let add_rest = (is_non_exhaustive && is_foreign_crate) || visible_fields.len() < n_fields;
101-
if !matches!(kind, hir::StructKind::Record) && add_rest {
106+
let has_private_members =
107+
(is_non_exhaustive && is_foreign_crate) || visible_fields.len() < n_fields;
108+
if !matches!(kind, hir::StructKind::Record) && has_private_members {
102109
return None;
103110
}
104111

@@ -123,26 +130,26 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
123130
kind,
124131
struct_def_path,
125132
usages,
126-
add_rest,
133+
has_private_members,
127134
visible_fields,
128135
names_in_scope: FxHashSet::default(), // TODO
129136
is_nested,
137+
is_ref,
130138
})
131139
}
132140

133141
fn build_assignment_edit(
134-
ctx: &AssistContext<'_>,
142+
_ctx: &AssistContext<'_>,
135143
builder: &mut SourceChangeBuilder,
136144
data: &StructEditData,
145+
field_names: &[(SmolStr, SmolStr)],
137146
) -> AssignmentEdit {
138147
let ident_pat = builder.make_mut(data.ident_pat.clone());
139148

140149
let struct_path = mod_path_to_ast(&data.struct_def_path);
141150
let is_ref = ident_pat.ref_token().is_some();
142151
let is_mut = ident_pat.mut_token().is_some();
143152

144-
let field_names = generate_field_names(ctx, data);
145-
146153
let new_pat = match data.kind {
147154
hir::StructKind::Tuple => {
148155
let ident_pats = field_names.iter().map(|(_, new_name)| {
@@ -169,7 +176,7 @@ fn build_assignment_edit(
169176

170177
let field_list = ast::make::record_pat_field_list(
171178
fields,
172-
data.add_rest.then_some(ast::make::rest_pat()),
179+
data.has_private_members.then_some(ast::make::rest_pat()),
173180
);
174181
ast::Pat::RecordPat(ast::make::record_pat_with_fields(struct_path, field_list))
175182
}
@@ -185,7 +192,7 @@ fn build_assignment_edit(
185192
NewPat::Pat(new_pat.clone_for_update())
186193
};
187194

188-
AssignmentEdit { ident_pat, new_pat, field_name_map: field_names.into_iter().collect() }
195+
AssignmentEdit { ident_pat, new_pat }
189196
}
190197

191198
fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(SmolStr, SmolStr)> {
@@ -195,17 +202,17 @@ fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(
195202
.iter()
196203
.enumerate()
197204
.map(|(index, _)| {
198-
let new_name = format!("_{}", index);
199-
(index.to_string().into(), new_name.into())
205+
let new_name = new_field_name((format!("_{}", index)).into(), &data.names_in_scope);
206+
(index.to_string().into(), new_name)
200207
})
201208
.collect(),
202209
hir::StructKind::Record => data
203210
.visible_fields
204211
.iter()
205212
.map(|field| {
206213
let field_name = field.name(ctx.db()).to_smol_str();
207-
let new_field_name = new_field_name(field_name.clone(), &data.names_in_scope);
208-
(field_name, new_field_name)
214+
let new_name = new_field_name(field_name.clone(), &data.names_in_scope);
215+
(field_name, new_name)
209216
})
210217
.collect(),
211218
hir::StructKind::Unit => Vec::new(),
@@ -225,7 +232,6 @@ fn new_field_name(base_name: SmolStr, names_in_scope: &FxHashSet<SmolStr>) -> Sm
225232
struct AssignmentEdit {
226233
ident_pat: ast::IdentPat,
227234
new_pat: NewPat,
228-
field_name_map: FxHashMap<SmolStr, SmolStr>,
229235
}
230236

231237
enum NewPat {
@@ -260,26 +266,37 @@ fn build_usage_edits(
260266
.iter()
261267
.find_map(|(file_id, refs)| (*file_id == ctx.file_id()).then_some(refs))?
262268
.iter()
263-
.filter_map(|r| build_usage_edit(builder, r, field_names))
269+
.filter_map(|r| build_usage_edit(ctx, builder, data, r, field_names))
264270
.collect_vec();
265271

266272
Some(edits)
267273
}
268274

269275
fn build_usage_edit(
276+
ctx: &AssistContext<'_>,
270277
builder: &mut SourceChangeBuilder,
278+
data: &StructEditData,
271279
usage: &FileReference,
272280
field_names: &FxHashMap<SmolStr, SmolStr>,
273281
) -> Option<StructUsageEdit> {
274282
match usage.name.syntax().ancestors().find_map(ast::FieldExpr::cast) {
275283
Some(field_expr) => Some({
276284
let field_name: SmolStr = field_expr.name_ref()?.to_string().into();
277285
let new_field_name = field_names.get(&field_name)?;
278-
279-
let expr = builder.make_mut(field_expr).into();
280-
let new_expr =
281-
ast::make::expr_path(ast::make::ext::ident_path(new_field_name)).clone_for_update();
282-
StructUsageEdit::IndexField(expr, new_expr)
286+
let new_expr = ast::make::expr_path(ast::make::ext::ident_path(new_field_name));
287+
288+
if data.is_ref {
289+
let (replace_expr, ref_data) = determine_ref_and_parens(ctx, &field_expr);
290+
StructUsageEdit::IndexField(
291+
builder.make_mut(replace_expr),
292+
ref_data.wrap_expr(new_expr).clone_for_update(),
293+
)
294+
} else {
295+
StructUsageEdit::IndexField(
296+
builder.make_mut(field_expr).into(),
297+
new_expr.clone_for_update(),
298+
)
299+
}
283300
}),
284301
None => Some(StructUsageEdit::Path(usage.range)),
285302
}
@@ -602,4 +619,33 @@ mod tests {
602619
"#,
603620
)
604621
}
622+
623+
#[test]
624+
fn mut_ref() {
625+
check_assist(
626+
destructure_struct_binding,
627+
r#"
628+
struct Foo {
629+
bar: i32,
630+
baz: i32
631+
}
632+
633+
fn main() {
634+
let $0foo = &mut Foo { bar: 1, baz: 2 };
635+
foo.bar = 5;
636+
}
637+
"#,
638+
r#"
639+
struct Foo {
640+
bar: i32,
641+
baz: i32
642+
}
643+
644+
fn main() {
645+
let Foo { bar, baz } = &mut Foo { bar: 1, baz: 2 };
646+
*bar = 5;
647+
}
648+
"#,
649+
)
650+
}
605651
}

crates/ide-assists/src/handlers/destructure_tuple_binding.rs

+7-117
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ use ide_db::{
55
};
66
use itertools::Itertools;
77
use syntax::{
8-
ast::{self, make, AstNode, FieldExpr, HasName, IdentPat, MethodCallExpr},
9-
ted, T,
8+
ast::{self, make, AstNode, FieldExpr, HasName, IdentPat},
9+
ted,
1010
};
1111
use text_edit::TextRange;
1212

13-
use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder};
13+
use crate::{
14+
assist_context::{AssistContext, Assists, SourceChangeBuilder},
15+
utils::ref_field_expr::determine_ref_and_parens,
16+
};
1417

1518
// Assist: destructure_tuple_binding
1619
//
@@ -274,7 +277,7 @@ fn edit_tuple_field_usage(
274277
let field_name = make::expr_path(make::ext::ident_path(field_name));
275278

276279
if data.ref_type.is_some() {
277-
let (replace_expr, ref_data) = handle_ref_field_usage(ctx, &index.field_expr);
280+
let (replace_expr, ref_data) = determine_ref_and_parens(ctx, &index.field_expr);
278281
let replace_expr = builder.make_mut(replace_expr);
279282
EditTupleUsage::ReplaceExpr(replace_expr, ref_data.wrap_expr(field_name))
280283
} else {
@@ -361,119 +364,6 @@ fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIn
361364
}
362365
}
363366

364-
struct RefData {
365-
needs_deref: bool,
366-
needs_parentheses: bool,
367-
}
368-
impl RefData {
369-
fn wrap_expr(&self, mut expr: ast::Expr) -> ast::Expr {
370-
if self.needs_deref {
371-
expr = make::expr_prefix(T![*], expr);
372-
}
373-
374-
if self.needs_parentheses {
375-
expr = make::expr_paren(expr);
376-
}
377-
378-
expr
379-
}
380-
}
381-
fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> (ast::Expr, RefData) {
382-
let s = field_expr.syntax();
383-
let mut ref_data = RefData { needs_deref: true, needs_parentheses: true };
384-
let mut target_node = field_expr.clone().into();
385-
386-
let parent = match s.parent().map(ast::Expr::cast) {
387-
Some(Some(parent)) => parent,
388-
Some(None) => {
389-
ref_data.needs_parentheses = false;
390-
return (target_node, ref_data);
391-
}
392-
None => return (target_node, ref_data),
393-
};
394-
395-
match parent {
396-
ast::Expr::ParenExpr(it) => {
397-
// already parens in place -> don't replace
398-
ref_data.needs_parentheses = false;
399-
// there might be a ref outside: `&(t.0)` -> can be removed
400-
if let Some(it) = it.syntax().parent().and_then(ast::RefExpr::cast) {
401-
ref_data.needs_deref = false;
402-
target_node = it.into();
403-
}
404-
}
405-
ast::Expr::RefExpr(it) => {
406-
// `&*` -> cancel each other out
407-
ref_data.needs_deref = false;
408-
ref_data.needs_parentheses = false;
409-
// might be surrounded by parens -> can be removed too
410-
match it.syntax().parent().and_then(ast::ParenExpr::cast) {
411-
Some(parent) => target_node = parent.into(),
412-
None => target_node = it.into(),
413-
};
414-
}
415-
// higher precedence than deref `*`
416-
// https://doc.rust-lang.org/reference/expressions.html#expression-precedence
417-
// -> requires parentheses
418-
ast::Expr::PathExpr(_it) => {}
419-
ast::Expr::MethodCallExpr(it) => {
420-
// `field_expr` is `self_param` (otherwise it would be in `ArgList`)
421-
422-
// test if there's already auto-ref in place (`value` -> `&value`)
423-
// -> no method accepting `self`, but `&self` -> no need for deref
424-
//
425-
// other combinations (`&value` -> `value`, `&&value` -> `&value`, `&value` -> `&&value`) might or might not be able to auto-ref/deref,
426-
// but there might be trait implementations an added `&` might resolve to
427-
// -> ONLY handle auto-ref from `value` to `&value`
428-
fn is_auto_ref(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> bool {
429-
fn impl_(ctx: &AssistContext<'_>, call_expr: &MethodCallExpr) -> Option<bool> {
430-
let rec = call_expr.receiver()?;
431-
let rec_ty = ctx.sema.type_of_expr(&rec)?.original();
432-
// input must be actual value
433-
if rec_ty.is_reference() {
434-
return Some(false);
435-
}
436-
437-
// doesn't resolve trait impl
438-
let f = ctx.sema.resolve_method_call(call_expr)?;
439-
let self_param = f.self_param(ctx.db())?;
440-
// self must be ref
441-
match self_param.access(ctx.db()) {
442-
hir::Access::Shared | hir::Access::Exclusive => Some(true),
443-
hir::Access::Owned => Some(false),
444-
}
445-
}
446-
impl_(ctx, call_expr).unwrap_or(false)
447-
}
448-
449-
if is_auto_ref(ctx, &it) {
450-
ref_data.needs_deref = false;
451-
ref_data.needs_parentheses = false;
452-
}
453-
}
454-
ast::Expr::FieldExpr(_it) => {
455-
// `t.0.my_field`
456-
ref_data.needs_deref = false;
457-
ref_data.needs_parentheses = false;
458-
}
459-
ast::Expr::IndexExpr(_it) => {
460-
// `t.0[1]`
461-
ref_data.needs_deref = false;
462-
ref_data.needs_parentheses = false;
463-
}
464-
ast::Expr::TryExpr(_it) => {
465-
// `t.0?`
466-
// requires deref and parens: `(*_0)`
467-
}
468-
// lower precedence than deref `*` -> no parens
469-
_ => {
470-
ref_data.needs_parentheses = false;
471-
}
472-
};
473-
474-
(target_node, ref_data)
475-
}
476-
477367
#[cfg(test)]
478368
mod tests {
479369
use super::*;

crates/ide-assists/src/utils.rs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use syntax::{
2222
use crate::assist_context::{AssistContext, SourceChangeBuilder};
2323

2424
mod gen_trait_fn_body;
25+
pub(crate) mod ref_field_expr;
2526
pub(crate) mod suggest_name;
2627

2728
pub(crate) fn unwrap_trivial_block(block_expr: ast::BlockExpr) -> ast::Expr {

0 commit comments

Comments
 (0)