1use hir::{HasVisibility, Semantics};
2use ide_db::{
3 FxHashMap, FxHashSet, RootDatabase,
4 assists::AssistId,
5 defs::Definition,
6 helpers::mod_path_to_ast_with_factory,
7 search::{FileReference, SearchScope},
8};
9use itertools::Itertools;
10use syntax::syntax_editor::SyntaxEditor;
11use syntax::{AstNode, Edition, SmolStr, SyntaxNode, ToSmolStr, ast};
12use syntax::{
13 SyntaxToken,
14 ast::{HasName, edit::IndentLevel, syntax_factory::SyntaxFactory},
15 syntax_editor::Position,
16};
17
18use crate::{
19 assist_context::{AssistContext, Assists, SourceChangeBuilder},
20 utils::{cover_edit_range, ref_field_expr::determine_ref_and_parens},
21};
22
23pub(crate) fn destructure_struct_binding(
51 acc: &mut Assists,
52 ctx: &AssistContext<'_, '_>,
53) -> Option<()> {
54 let target = ctx.find_node_at_offset::<Target>()?;
55 let data = collect_data(target, ctx)?;
56
57 acc.add(
58 AssistId::refactor_rewrite("destructure_struct_binding"),
59 "Destructure struct binding",
60 data.target.syntax().text_range(),
61 |edit| destructure_struct_binding_impl(ctx, edit, &data),
62 );
63
64 Some(())
65}
66
67enum Target {
68 IdentPat(ast::IdentPat),
69 SelfParam { param: ast::SelfParam, insert_after: SyntaxToken },
70}
71
72impl Target {
73 fn ty<'db>(&self, sema: &Semantics<'db, RootDatabase>) -> Option<hir::Type<'db>> {
74 match self {
75 Target::IdentPat(pat) => sema.type_of_binding_in_pat(pat),
76 Target::SelfParam { param, .. } => sema.type_of_self(param),
77 }
78 }
79
80 fn is_ref(&self) -> bool {
81 match self {
82 Target::IdentPat(ident_pat) => ident_pat.ref_token().is_some(),
83 Target::SelfParam { .. } => false,
84 }
85 }
86
87 fn is_mut(&self) -> bool {
88 match self {
89 Target::IdentPat(ident_pat) => ident_pat.mut_token().is_some(),
90 Target::SelfParam { param, .. } => {
91 param.mut_token().is_some() && param.amp_token().is_none()
92 }
93 }
94 }
95}
96
97impl HasName for Target {}
98
99impl AstNode for Target {
100 fn cast(node: SyntaxNode) -> Option<Self> {
101 if ast::IdentPat::can_cast(node.kind()) {
102 ast::IdentPat::cast(node).map(Self::IdentPat)
103 } else {
104 let param = ast::SelfParam::cast(node)?;
105 let param_list = param.syntax().parent().and_then(ast::ParamList::cast)?;
106 let block = param_list.syntax().parent()?.children().find_map(ast::BlockExpr::cast)?;
107 let insert_after = block.stmt_list()?.l_curly_token()?;
108 Some(Self::SelfParam { param, insert_after })
109 }
110 }
111
112 fn can_cast(kind: syntax::SyntaxKind) -> bool {
113 ast::IdentPat::can_cast(kind) || ast::SelfParam::can_cast(kind)
114 }
115
116 fn syntax(&self) -> &SyntaxNode {
117 match self {
118 Target::IdentPat(ident_pat) => ident_pat.syntax(),
119 Target::SelfParam { param, .. } => param.syntax(),
120 }
121 }
122}
123
124fn destructure_struct_binding_impl(
125 ctx: &AssistContext<'_, '_>,
126 builder: &mut SourceChangeBuilder,
127 data: &StructEditData,
128) {
129 let field_names = generate_field_names(ctx, data);
130 let editor = builder.make_editor(data.target.syntax());
131 destructure_pat(ctx, &editor, data, &field_names);
132 update_usages(ctx, &editor, data, &field_names.into_iter().collect());
133 builder.add_file_edits(ctx.vfs_file_id(), editor);
134}
135
136struct StructEditData {
137 target: Target,
138 name: ast::Name,
139 kind: hir::StructKind,
140 struct_def_path: hir::ModPath,
141 visible_fields: Vec<hir::Field>,
142 usages: Vec<FileReference>,
143 names_in_scope: FxHashSet<SmolStr>,
144 has_private_members: bool,
145 need_record_field_name: bool,
146 is_ref: bool,
147 edition: Edition,
148}
149
150impl StructEditData {
151 fn apply_to_destruct(&self, new_pat: ast::Pat, editor: &SyntaxEditor) {
152 let make = editor.make();
153 match &self.target {
154 Target::IdentPat(pat) => {
155 if self.need_record_field_name {
158 let new_pat =
159 make.record_pat_field(make.name_ref(&self.name.to_string()), new_pat);
160 editor.replace(pat.syntax(), new_pat.syntax())
161 } else {
162 editor.replace(pat.syntax(), new_pat.syntax())
163 }
164 }
165 Target::SelfParam { insert_after, .. } => {
166 let indent = IndentLevel::from_token(insert_after) + 1;
167 let newline = make.whitespace(&format!("\n{indent}"));
168 let initializer = make.expr_path(make.ident_path("self"));
169 let let_stmt = make.let_stmt(new_pat, None, Some(initializer));
170 editor.insert_all(
171 Position::after(insert_after),
172 vec![newline.into(), let_stmt.syntax().clone().into()],
173 );
174 }
175 }
176 }
177}
178
179fn collect_data(target: Target, ctx: &AssistContext<'_, '_>) -> Option<StructEditData> {
180 let ty = target.ty(&ctx.sema)?;
181 let hir::Adt::Struct(struct_type) = ty.strip_references().as_adt()? else { return None };
182
183 let module = ctx.sema.scope(target.syntax())?.module();
184 let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.db())));
185 let struct_def = hir::ModuleDef::from(struct_type);
186 let kind = struct_type.kind(ctx.db());
187 let struct_def_path = module.find_path(ctx.db(), struct_def, cfg)?;
188
189 let is_non_exhaustive = struct_def.attrs(ctx.db())?.is_non_exhaustive();
190 let is_foreign_crate =
191 struct_def.module(ctx.db()).is_some_and(|m| m.krate(ctx.db()) != module.krate(ctx.db()));
192
193 let fields = struct_type.fields(ctx.db());
194 let n_fields = fields.len();
195
196 let visible_fields =
197 fields.into_iter().filter(|field| field.is_visible_from(ctx.db(), module)).collect_vec();
198
199 if visible_fields.is_empty() {
200 return None;
201 }
202
203 let has_private_members =
204 (is_non_exhaustive && is_foreign_crate) || visible_fields.len() < n_fields;
205
206 if !matches!(kind, hir::StructKind::Record) && has_private_members {
208 return None;
209 }
210
211 let is_ref = ty.is_reference();
212 let need_record_field_name = target
213 .syntax()
214 .parent()
215 .and_then(ast::RecordPatField::cast)
216 .is_some_and(|field| field.colon_token().is_none());
217
218 let def = match &target {
219 Target::IdentPat(pat) => ctx.sema.to_def(pat),
220 Target::SelfParam { param, .. } => ctx.sema.to_def(param),
221 };
222 let usages = def
223 .and_then(|def| {
224 Definition::Local(def)
225 .usages(&ctx.sema)
226 .in_scope(&SearchScope::single_file(ctx.file_id()))
227 .all()
228 .iter()
229 .next()
230 .map(|(_, refs)| refs.to_vec())
231 })
232 .unwrap_or_default();
233
234 let names_in_scope = get_names_in_scope(ctx, &target, &usages).unwrap_or_default();
235
236 Some(StructEditData {
237 name: target.name()?,
238 target,
239 kind,
240 struct_def_path,
241 usages,
242 has_private_members,
243 visible_fields,
244 names_in_scope,
245 need_record_field_name,
246 is_ref,
247 edition: module.krate(ctx.db()).edition(ctx.db()),
248 })
249}
250
251fn get_names_in_scope(
252 ctx: &AssistContext<'_, '_>,
253 target: &Target,
254 usages: &[FileReference],
255) -> Option<FxHashSet<SmolStr>> {
256 fn last_usage(usages: &[FileReference]) -> Option<SyntaxNode> {
257 usages.last()?.name.syntax().into_node()
258 }
259
260 let last_usage = last_usage(usages);
263 let node = last_usage.as_ref().unwrap_or(target.syntax());
264 let scope = ctx.sema.scope(node)?;
265
266 let mut names = FxHashSet::default();
267 scope.process_all_names(&mut |name, scope| {
268 if let hir::ScopeDef::Local(_) = scope {
269 names.insert(name.as_str().into());
270 }
271 });
272 Some(names)
273}
274
275fn destructure_pat(
276 _ctx: &AssistContext<'_, '_>,
277 editor: &SyntaxEditor,
278 data: &StructEditData,
279 field_names: &[(SmolStr, SmolStr)],
280) {
281 let make = editor.make();
282 let struct_path = mod_path_to_ast_with_factory(make, &data.struct_def_path, data.edition);
283 let is_ref = data.target.is_ref();
284 let is_mut = data.target.is_mut();
285
286 let new_pat = match data.kind {
287 hir::StructKind::Tuple => {
288 let ident_pats = field_names.iter().map(|(_, new_name)| {
289 let name = make.name(new_name);
290 ast::Pat::from(make.ident_pat(is_ref, is_mut, name))
291 });
292 ast::Pat::TupleStructPat(make.tuple_struct_pat(struct_path, ident_pats))
293 }
294 hir::StructKind::Record => {
295 let fields = field_names.iter().map(|(old_name, new_name)| {
296 if old_name == new_name {
298 make.record_pat_field_shorthand(
299 make.ident_pat(is_ref, is_mut, make.name(old_name)).into(),
300 )
301 } else {
302 make.record_pat_field(
303 make.name_ref(old_name),
304 ast::Pat::IdentPat(make.ident_pat(is_ref, is_mut, make.name(new_name))),
305 )
306 }
307 });
308 let field_list = make
309 .record_pat_field_list(fields, data.has_private_members.then_some(make.rest_pat()));
310
311 ast::Pat::RecordPat(make.record_pat_with_fields(struct_path, field_list))
312 }
313 hir::StructKind::Unit => make.path_pat(struct_path),
314 };
315
316 data.apply_to_destruct(new_pat, editor);
317}
318
319fn generate_field_names(
320 ctx: &AssistContext<'_, '_>,
321 data: &StructEditData,
322) -> Vec<(SmolStr, SmolStr)> {
323 match data.kind {
324 hir::StructKind::Tuple => data
325 .visible_fields
326 .iter()
327 .enumerate()
328 .map(|(index, _)| {
329 let new_name = new_field_name((format!("_{index}")).into(), &data.names_in_scope);
330 (index.to_string().into(), new_name)
331 })
332 .collect(),
333 hir::StructKind::Record => data
334 .visible_fields
335 .iter()
336 .map(|field| {
337 let field_name = field.name(ctx.db()).display_no_db(data.edition).to_smolstr();
338 let new_name = new_field_name(field_name.clone(), &data.names_in_scope);
339 (field_name, new_name)
340 })
341 .collect(),
342 hir::StructKind::Unit => Vec::new(),
343 }
344}
345
346fn new_field_name(base_name: SmolStr, names_in_scope: &FxHashSet<SmolStr>) -> SmolStr {
347 let mut name = base_name.clone();
348 let mut i = 1;
349 while names_in_scope.contains(&name) {
350 name = format!("{base_name}_{i}").into();
351 i += 1;
352 }
353 name
354}
355
356fn update_usages(
357 ctx: &AssistContext<'_, '_>,
358 editor: &SyntaxEditor,
359 data: &StructEditData,
360 field_names: &FxHashMap<SmolStr, SmolStr>,
361) {
362 let source = ctx.source_file().syntax();
363 let edits = data
364 .usages
365 .iter()
366 .filter_map(|r| build_usage_edit(ctx, editor.make(), data, r, field_names))
367 .collect_vec();
368 for (old, new) in edits {
369 if let Some(range) = ctx.sema.original_range_opt(&old) {
370 editor.replace_all(cover_edit_range(source, range.range), vec![new.into()]);
371 }
372 }
373}
374
375fn build_usage_edit(
376 ctx: &AssistContext<'_, '_>,
377 make: &SyntaxFactory,
378 data: &StructEditData,
379 usage: &FileReference,
380 field_names: &FxHashMap<SmolStr, SmolStr>,
381) -> Option<(SyntaxNode, SyntaxNode)> {
382 match usage.name.syntax().ancestors().find_map(ast::FieldExpr::cast) {
383 Some(field_expr) => Some({
384 let field_name: SmolStr = field_expr.name_ref()?.to_string().into();
385 let new_field_name = field_names.get(&field_name)?;
386 let new_expr = make.expr_path(make.ident_path(new_field_name));
387
388 if data.is_ref {
390 let (replace_expr, ref_data) = determine_ref_and_parens(ctx, &field_expr);
391 (replace_expr.syntax().clone(), ref_data.wrap_expr(new_expr, make).syntax().clone())
392 } else {
393 (field_expr.syntax().clone(), new_expr.syntax().clone())
394 }
395 }),
396 None => Some((
397 usage.name.syntax().as_node().unwrap().clone(),
398 make.expr_macro(
399 make.ident_path("todo"),
400 make.token_tree(syntax::SyntaxKind::L_PAREN, []),
401 )
402 .syntax()
403 .clone(),
404 )),
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 use crate::tests::{check_assist, check_assist_not_applicable};
413
414 #[test]
415 fn record_struct() {
416 check_assist(
417 destructure_struct_binding,
418 r#"
419 struct Foo { bar: i32, baz: i32 }
420
421 fn main() {
422 let $0foo = Foo { bar: 1, baz: 2 };
423 let bar2 = foo.bar;
424 let baz2 = &foo.baz;
425
426 let foo2 = foo;
427 }
428 "#,
429 r#"
430 struct Foo { bar: i32, baz: i32 }
431
432 fn main() {
433 let Foo { bar, baz } = Foo { bar: 1, baz: 2 };
434 let bar2 = bar;
435 let baz2 = &baz;
436
437 let foo2 = todo!();
438 }
439 "#,
440 )
441 }
442
443 #[test]
444 fn tuple_struct() {
445 check_assist(
446 destructure_struct_binding,
447 r#"
448 struct Foo(i32, i32);
449
450 fn main() {
451 let $0foo = Foo(1, 2);
452 let bar2 = foo.0;
453 let baz2 = foo.1;
454
455 let foo2 = foo;
456 }
457 "#,
458 r#"
459 struct Foo(i32, i32);
460
461 fn main() {
462 let Foo(_0, _1) = Foo(1, 2);
463 let bar2 = _0;
464 let baz2 = _1;
465
466 let foo2 = todo!();
467 }
468 "#,
469 )
470 }
471
472 #[test]
473 fn unit_struct() {
474 check_assist_not_applicable(
475 destructure_struct_binding,
476 r#"
477 struct Foo;
478
479 fn main() {
480 let $0foo = Foo;
481 }
482 "#,
483 )
484 }
485
486 #[test]
487 fn in_foreign_crate() {
488 check_assist(
489 destructure_struct_binding,
490 r#"
491 //- /lib.rs crate:dep
492 pub struct Foo { pub bar: i32 };
493
494 //- /main.rs crate:main deps:dep
495 fn main() {
496 let $0foo = dep::Foo { bar: 1 };
497 let bar2 = foo.bar;
498 }
499 "#,
500 r#"
501 fn main() {
502 let dep::Foo { bar } = dep::Foo { bar: 1 };
503 let bar2 = bar;
504 }
505 "#,
506 )
507 }
508
509 #[test]
510 fn non_exhaustive_record_appends_rest() {
511 check_assist(
512 destructure_struct_binding,
513 r#"
514 //- /lib.rs crate:dep
515 #[non_exhaustive]
516 pub struct Foo { pub bar: i32 };
517
518 //- /main.rs crate:main deps:dep
519 fn main($0foo: dep::Foo) {
520 let bar2 = foo.bar;
521 }
522 "#,
523 r#"
524 fn main(dep::Foo { bar, .. }: dep::Foo) {
525 let bar2 = bar;
526 }
527 "#,
528 )
529 }
530
531 #[test]
532 fn non_exhaustive_tuple_not_applicable() {
533 check_assist_not_applicable(
534 destructure_struct_binding,
535 r#"
536 //- /lib.rs crate:dep
537 #[non_exhaustive]
538 pub struct Foo(pub i32, pub i32);
539
540 //- /main.rs crate:main deps:dep
541 fn main(foo: dep::Foo) {
542 let $0foo2 = foo;
543 let bar = foo2.0;
544 let baz = foo2.1;
545 }
546 "#,
547 )
548 }
549
550 #[test]
551 fn non_exhaustive_unit_not_applicable() {
552 check_assist_not_applicable(
553 destructure_struct_binding,
554 r#"
555 //- /lib.rs crate:dep
556 #[non_exhaustive]
557 pub struct Foo;
558
559 //- /main.rs crate:main deps:dep
560 fn main(foo: dep::Foo) {
561 let $0foo2 = foo;
562 }
563 "#,
564 )
565 }
566
567 #[test]
568 fn record_private_fields_appends_rest() {
569 check_assist(
570 destructure_struct_binding,
571 r#"
572 //- /lib.rs crate:dep
573 pub struct Foo { pub bar: i32, baz: i32 };
574
575 //- /main.rs crate:main deps:dep
576 fn main(foo: dep::Foo) {
577 let $0foo2 = foo;
578 let bar2 = foo2.bar;
579 }
580 "#,
581 r#"
582 fn main(foo: dep::Foo) {
583 let dep::Foo { bar, .. } = foo;
584 let bar2 = bar;
585 }
586 "#,
587 )
588 }
589
590 #[test]
591 fn tuple_private_fields_not_applicable() {
592 check_assist_not_applicable(
593 destructure_struct_binding,
594 r#"
595 //- /lib.rs crate:dep
596 pub struct Foo(pub i32, i32);
597
598 //- /main.rs crate:main deps:dep
599 fn main(foo: dep::Foo) {
600 let $0foo2 = foo;
601 let bar2 = foo2.0;
602 }
603 "#,
604 )
605 }
606
607 #[test]
608 fn nested_inside_record() {
609 check_assist(
610 destructure_struct_binding,
611 r#"
612 struct Foo { fizz: Fizz }
613 struct Fizz { buzz: i32 }
614
615 fn main() {
616 let Foo { $0fizz } = Foo { fizz: Fizz { buzz: 1 } };
617 let buzz2 = fizz.buzz;
618 }
619 "#,
620 r#"
621 struct Foo { fizz: Fizz }
622 struct Fizz { buzz: i32 }
623
624 fn main() {
625 let Foo { fizz: Fizz { buzz } } = Foo { fizz: Fizz { buzz: 1 } };
626 let buzz2 = buzz;
627 }
628 "#,
629 )
630 }
631
632 #[test]
633 fn nested_inside_tuple() {
634 check_assist(
635 destructure_struct_binding,
636 r#"
637 struct Foo(Fizz);
638 struct Fizz { buzz: i32 }
639
640 fn main() {
641 let Foo($0fizz) = Foo(Fizz { buzz: 1 });
642 let buzz2 = fizz.buzz;
643 }
644 "#,
645 r#"
646 struct Foo(Fizz);
647 struct Fizz { buzz: i32 }
648
649 fn main() {
650 let Foo(Fizz { buzz }) = Foo(Fizz { buzz: 1 });
651 let buzz2 = buzz;
652 }
653 "#,
654 )
655 }
656
657 #[test]
658 fn mut_record() {
659 check_assist(
660 destructure_struct_binding,
661 r#"
662 struct Foo { bar: i32, baz: i32 }
663
664 fn main() {
665 let mut $0foo = Foo { bar: 1, baz: 2 };
666 let bar2 = foo.bar;
667 let baz2 = &foo.baz;
668 }
669 "#,
670 r#"
671 struct Foo { bar: i32, baz: i32 }
672
673 fn main() {
674 let Foo { mut bar, mut baz } = Foo { bar: 1, baz: 2 };
675 let bar2 = bar;
676 let baz2 = &baz;
677 }
678 "#,
679 )
680 }
681
682 #[test]
683 fn mut_record_field() {
684 check_assist(
685 destructure_struct_binding,
686 r#"
687 struct Foo { x: () }
688 struct Bar { foo: Foo }
689 fn f(Bar { mut $0foo }: Bar) {}
690 "#,
691 r#"
692 struct Foo { x: () }
693 struct Bar { foo: Foo }
694 fn f(Bar { foo: Foo { mut x } }: Bar) {}
695 "#,
696 )
697 }
698
699 #[test]
700 fn ref_record_field() {
701 check_assist(
702 destructure_struct_binding,
703 r#"
704 struct Foo { x: () }
705 struct Bar { foo: Foo }
706 fn f(Bar { ref $0foo }: Bar) {
707 let _ = foo.x;
708 }
709 "#,
710 r#"
711 struct Foo { x: () }
712 struct Bar { foo: Foo }
713 fn f(Bar { foo: Foo { ref x } }: Bar) {
714 let _ = *x;
715 }
716 "#,
717 )
718 }
719
720 #[test]
721 fn ref_mut_record_field() {
722 check_assist(
723 destructure_struct_binding,
724 r#"
725 struct Foo { x: () }
726 struct Bar { foo: Foo }
727 fn f(Bar { ref mut $0foo }: Bar) {
728 let _ = foo.x;
729 }
730 "#,
731 r#"
732 struct Foo { x: () }
733 struct Bar { foo: Foo }
734 fn f(Bar { foo: Foo { ref mut x } }: Bar) {
735 let _ = *x;
736 }
737 "#,
738 )
739 }
740
741 #[test]
742 fn ref_mut_record_renamed_field() {
743 check_assist(
744 destructure_struct_binding,
745 r#"
746 struct Foo { x: () }
747 struct Bar { foo: Foo }
748 fn f(Bar { foo: ref mut $0foo1 }: Bar) {
749 let _ = foo1.x;
750 }
751 "#,
752 r#"
753 struct Foo { x: () }
754 struct Bar { foo: Foo }
755 fn f(Bar { foo: Foo { ref mut x } }: Bar) {
756 let _ = *x;
757 }
758 "#,
759 )
760 }
761
762 #[test]
763 fn mut_ref() {
764 check_assist(
765 destructure_struct_binding,
766 r#"
767 struct Foo { bar: i32, baz: i32 }
768
769 fn main() {
770 let $0foo = &mut Foo { bar: 1, baz: 2 };
771 foo.bar = 5;
772 }
773 "#,
774 r#"
775 struct Foo { bar: i32, baz: i32 }
776
777 fn main() {
778 let Foo { bar, baz } = &mut Foo { bar: 1, baz: 2 };
779 *bar = 5;
780 }
781 "#,
782 )
783 }
784
785 #[test]
786 fn mut_self_param() {
787 check_assist(
788 destructure_struct_binding,
789 r#"
790 struct Foo { bar: i32, baz: i32 }
791
792 impl Foo {
793 fn foo(mut $0self) {
794 self.bar = 5;
795 }
796 }
797 "#,
798 r#"
799 struct Foo { bar: i32, baz: i32 }
800
801 impl Foo {
802 fn foo(mut self) {
803 let Foo { mut bar, mut baz } = self;
804 bar = 5;
805 }
806 }
807 "#,
808 )
809 }
810
811 #[test]
812 fn ref_mut_self_param() {
813 check_assist(
814 destructure_struct_binding,
815 r#"
816 struct Foo { bar: i32, baz: i32 }
817
818 impl Foo {
819 fn foo(&mut $0self) {
820 self.bar = 5;
821 }
822 }
823 "#,
824 r#"
825 struct Foo { bar: i32, baz: i32 }
826
827 impl Foo {
828 fn foo(&mut self) {
829 let Foo { bar, baz } = self;
830 *bar = 5;
831 }
832 }
833 "#,
834 )
835 }
836
837 #[test]
838 fn ref_self_param() {
839 check_assist(
840 destructure_struct_binding,
841 r#"
842 struct Foo { bar: i32, baz: i32 }
843
844 impl Foo {
845 fn foo(&$0self) -> &i32 {
846 &self.bar
847 }
848 }
849 "#,
850 r#"
851 struct Foo { bar: i32, baz: i32 }
852
853 impl Foo {
854 fn foo(&self) -> &i32 {
855 let Foo { bar, baz } = self;
856 bar
857 }
858 }
859 "#,
860 )
861 }
862
863 #[test]
864 fn ref_not_add_parenthesis_and_deref_record() {
865 check_assist(
866 destructure_struct_binding,
867 r#"
868 struct Foo { bar: i32, baz: i32 }
869
870 fn main() {
871 let $0foo = &Foo { bar: 1, baz: 2 };
872 let _ = &foo.bar;
873 }
874 "#,
875 r#"
876 struct Foo { bar: i32, baz: i32 }
877
878 fn main() {
879 let Foo { bar, baz } = &Foo { bar: 1, baz: 2 };
880 let _ = bar;
881 }
882 "#,
883 )
884 }
885
886 #[test]
887 fn ref_not_add_parenthesis_and_deref_tuple() {
888 check_assist(
889 destructure_struct_binding,
890 r#"
891 struct Foo(i32, i32);
892
893 fn main() {
894 let $0foo = &Foo(1, 2);
895 let _ = &foo.0;
896 }
897 "#,
898 r#"
899 struct Foo(i32, i32);
900
901 fn main() {
902 let Foo(_0, _1) = &Foo(1, 2);
903 let _ = _0;
904 }
905 "#,
906 )
907 }
908
909 #[test]
910 fn record_struct_name_collision() {
911 check_assist(
912 destructure_struct_binding,
913 r#"
914 struct Foo { bar: i32, baz: i32 }
915
916 fn main(baz: i32) {
917 let bar = true;
918 let $0foo = Foo { bar: 1, baz: 2 };
919 let baz_1 = 7;
920 let bar_usage = foo.bar;
921 let baz_usage = foo.baz;
922 }
923 "#,
924 r#"
925 struct Foo { bar: i32, baz: i32 }
926
927 fn main(baz: i32) {
928 let bar = true;
929 let Foo { bar: bar_1, baz: baz_2 } = Foo { bar: 1, baz: 2 };
930 let baz_1 = 7;
931 let bar_usage = bar_1;
932 let baz_usage = baz_2;
933 }
934 "#,
935 )
936 }
937
938 #[test]
939 fn tuple_struct_name_collision() {
940 check_assist(
941 destructure_struct_binding,
942 r#"
943 struct Foo(i32, i32);
944
945 fn main() {
946 let _0 = true;
947 let $0foo = Foo(1, 2);
948 let bar = foo.0;
949 let baz = foo.1;
950 }
951 "#,
952 r#"
953 struct Foo(i32, i32);
954
955 fn main() {
956 let _0 = true;
957 let Foo(_0_1, _1) = Foo(1, 2);
958 let bar = _0_1;
959 let baz = _1;
960 }
961 "#,
962 )
963 }
964
965 #[test]
966 fn record_struct_name_collision_nested_scope() {
967 check_assist(
968 destructure_struct_binding,
969 r#"
970 struct Foo { bar: i32 }
971
972 fn main(foo: Foo) {
973 let bar = 5;
974
975 let new_bar = {
976 let $0foo2 = foo;
977 let bar_1 = 5;
978 foo2.bar
979 };
980 }
981 "#,
982 r#"
983 struct Foo { bar: i32 }
984
985 fn main(foo: Foo) {
986 let bar = 5;
987
988 let new_bar = {
989 let Foo { bar: bar_2 } = foo;
990 let bar_1 = 5;
991 bar_2
992 };
993 }
994 "#,
995 )
996 }
997
998 #[test]
999 fn record_struct_no_public_members() {
1000 check_assist_not_applicable(
1001 destructure_struct_binding,
1002 r#"
1003 //- /lib.rs crate:dep
1004 pub struct Foo { bar: i32, baz: i32 };
1005
1006 //- /main.rs crate:main deps:dep
1007 fn main($0foo: dep::Foo) {}
1008 "#,
1009 )
1010 }
1011
1012 #[test]
1013 fn record_struct_usage_in_macro_call() {
1014 check_assist(
1016 destructure_struct_binding,
1017 r#"
1018//- minicore: write, fmt
1019use core::fmt::Write;
1020struct Foo { y: i8 }
1021
1022fn main() {
1023 let mut s = String::new();
1024 let $0x = Foo { y: 8 };
1025 write!(s, "{}", x.y).unwrap();
1026}
1027"#,
1028 r#"
1029use core::fmt::Write;
1030struct Foo { y: i8 }
1031
1032fn main() {
1033 let mut s = String::new();
1034 let Foo { y } = Foo { y: 8 };
1035 write!(s, "{}", y).unwrap();
1036}
1037"#,
1038 )
1039 }
1040}