1use hir::{PathResolution, Semantics};
2use ide_db::{
3 EditionedFileId, RootDatabase,
4 defs::Definition,
5 search::{FileReference, FileReferenceNode, UsageSearchResult},
6};
7use syntax::{
8 SyntaxElement, TextRange,
9 ast::{self, AstNode, AstToken, HasName, syntax_factory::SyntaxFactory},
10};
11
12use crate::{
13 AssistId,
14 assist_context::{AssistContext, Assists},
15};
16
17pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
34 let file_id = ctx.file_id();
35 let range = ctx.selection_trimmed();
36 let InlineData { let_stmt, delete_let, references, target } =
37 if let Some(path_expr) = ctx.find_node_at_offset::<ast::PathExpr>() {
38 inline_usage(&ctx.sema, path_expr, range, file_id)
39 } else if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
40 inline_let(&ctx.sema, let_stmt, range, file_id)
41 } else {
42 None
43 }?;
44 let initializer_expr = let_stmt.initializer()?;
45
46 let wrap_in_parens = references
47 .into_iter()
48 .filter_map(|FileReference { range, name, .. }| match name {
49 FileReferenceNode::NameRef(name) => Some((range, name)),
50 _ => None,
51 })
52 .map(|(range, name_ref)| {
53 if range != name_ref.syntax().text_range() {
54 return None;
57 }
58 let usage_node =
59 name_ref.syntax().ancestors().find(|it| ast::PathExpr::can_cast(it.kind()));
60 let usage_parent_option = usage_node.as_ref().and_then(|it| it.parent());
61 let usage_parent = match usage_parent_option {
62 Some(u) => u,
63 None => return Some((name_ref, false)),
64 };
65 let should_wrap = initializer_expr
66 .needs_parens_in_place_of(&usage_parent, usage_node.as_ref().unwrap());
67 Some((name_ref, should_wrap))
68 })
69 .collect::<Option<Vec<_>>>()?;
70
71 let target = match target {
72 ast::NameOrNameRef::Name(it) => it.syntax().clone(),
73 ast::NameOrNameRef::NameRef(it) => it.syntax().clone(),
74 };
75
76 acc.add(
77 AssistId::refactor_inline("inline_local_variable"),
78 "Inline variable",
79 target.text_range(),
80 move |builder| {
81 let mut editor = builder.make_editor(&target);
82 if delete_let {
83 editor.delete(let_stmt.syntax());
84 if let Some(whitespace) = let_stmt
85 .syntax()
86 .next_sibling_or_token()
87 .and_then(SyntaxElement::into_token)
88 .and_then(ast::Whitespace::cast)
89 {
90 editor.delete(whitespace.syntax());
91 }
92 }
93
94 let make = SyntaxFactory::with_mappings();
95
96 for (name, should_wrap) in wrap_in_parens {
97 let replacement = if should_wrap {
98 make.expr_paren(initializer_expr.clone()).into()
99 } else {
100 initializer_expr.clone()
101 };
102
103 if let Some(record_field) = ast::RecordExprField::for_field_name(&name) {
104 cov_mark::hit!(inline_field_shorthand);
105 let replacement = make.record_expr_field(name, Some(replacement));
106 editor.replace(record_field.syntax(), replacement.syntax());
107 } else {
108 editor.replace(name.syntax(), replacement.syntax());
109 }
110 }
111
112 editor.add_mappings(make.finish_with_mappings());
113 builder.add_file_edits(ctx.vfs_file_id(), editor);
114 },
115 )
116}
117
118struct InlineData {
119 let_stmt: ast::LetStmt,
120 delete_let: bool,
121 target: ast::NameOrNameRef,
122 references: Vec<FileReference>,
123}
124
125fn inline_let(
126 sema: &Semantics<'_, RootDatabase>,
127 let_stmt: ast::LetStmt,
128 range: TextRange,
129 file_id: EditionedFileId,
130) -> Option<InlineData> {
131 let bind_pat = match let_stmt.pat()? {
132 ast::Pat::IdentPat(pat) => pat,
133 _ => return None,
134 };
135 if bind_pat.mut_token().is_some() {
136 cov_mark::hit!(test_not_inline_mut_variable);
137 return None;
138 }
139 if !bind_pat.syntax().text_range().contains_range(range) {
140 cov_mark::hit!(not_applicable_outside_of_bind_pat);
141 return None;
142 }
143
144 let local = sema.to_def(&bind_pat)?;
145 let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
146 match references.remove(&file_id) {
147 Some(references) => Some(InlineData {
148 let_stmt,
149 delete_let: true,
150 target: ast::NameOrNameRef::Name(bind_pat.name()?),
151 references,
152 }),
153 None => {
154 cov_mark::hit!(test_not_applicable_if_variable_unused);
155 None
156 }
157 }
158}
159
160fn inline_usage(
161 sema: &Semantics<'_, RootDatabase>,
162 path_expr: ast::PathExpr,
163 range: TextRange,
164 file_id: EditionedFileId,
165) -> Option<InlineData> {
166 let path = path_expr.path()?;
167 let name = path.as_single_name_ref()?;
168 if !name.syntax().text_range().contains_range(range) {
169 cov_mark::hit!(test_not_inline_selection_too_broad);
170 return None;
171 }
172
173 let local = match sema.resolve_path(&path)? {
174 PathResolution::Local(local) => local,
175 _ => return None,
176 };
177 if local.is_mut(sema.db) {
178 cov_mark::hit!(test_not_inline_mut_variable_use);
179 return None;
180 }
181
182 let sources = local.sources(sema.db);
183 let [source] = sources.as_slice() else {
184 return None;
186 };
187
188 let bind_pat = source.as_ident_pat()?;
189
190 let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?;
191
192 let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
193 let mut references = references.remove(&file_id)?;
194 let delete_let = references.len() == 1;
195 references.retain(|fref| fref.name.as_name_ref() == Some(&name));
196
197 Some(InlineData { let_stmt, delete_let, target: ast::NameOrNameRef::NameRef(name), references })
198}
199
200#[cfg(test)]
201mod tests {
202 use crate::tests::{check_assist, check_assist_not_applicable};
203
204 use super::*;
205
206 #[test]
207 fn test_inline_let_bind_literal_expr() {
208 check_assist(
209 inline_local_variable,
210 r"
211fn bar(a: usize) {}
212fn foo() {
213 let a$0 = 1;
214 a + 1;
215 if a > 10 {
216 }
217
218 while a > 10 {
219
220 }
221 let b = a * 10;
222 bar(a);
223}",
224 r"
225fn bar(a: usize) {}
226fn foo() {
227 1 + 1;
228 if 1 > 10 {
229 }
230
231 while 1 > 10 {
232
233 }
234 let b = 1 * 10;
235 bar(1);
236}",
237 );
238 }
239
240 #[test]
241 fn test_inline_let_bind_bin_expr() {
242 check_assist(
243 inline_local_variable,
244 r"
245fn bar(a: usize) {}
246fn foo() {
247 let a$0 = 1 + 1;
248 a + 1;
249 if a > 10 {
250 }
251
252 while a > 10 {
253
254 }
255 let b = a * 10;
256 bar(a);
257}",
258 r"
259fn bar(a: usize) {}
260fn foo() {
261 1 + 1 + 1;
262 if 1 + 1 > 10 {
263 }
264
265 while 1 + 1 > 10 {
266
267 }
268 let b = (1 + 1) * 10;
269 bar(1 + 1);
270}",
271 );
272 }
273
274 #[test]
275 fn test_inline_let_bind_function_call_expr() {
276 check_assist(
277 inline_local_variable,
278 r"
279fn bar(a: usize) {}
280fn foo() {
281 let a$0 = bar(1);
282 a + 1;
283 if a > 10 {
284 }
285
286 while a > 10 {
287
288 }
289 let b = a * 10;
290 bar(a);
291}",
292 r"
293fn bar(a: usize) {}
294fn foo() {
295 bar(1) + 1;
296 if bar(1) > 10 {
297 }
298
299 while bar(1) > 10 {
300
301 }
302 let b = bar(1) * 10;
303 bar(bar(1));
304}",
305 );
306 }
307
308 #[test]
309 fn test_inline_let_bind_cast_expr() {
310 check_assist(
311 inline_local_variable,
312 r"
313//- minicore: sized
314fn bar(a: usize) -> usize { a }
315fn foo() {
316 let a$0 = bar(1) as u64;
317 a + 1;
318 if a > 10 {
319 }
320
321 while a > 10 {
322
323 }
324 let b = a * 10;
325 bar(a);
326}",
327 r"
328fn bar(a: usize) -> usize { a }
329fn foo() {
330 bar(1) as u64 + 1;
331 if bar(1) as u64 > 10 {
332 }
333
334 while bar(1) as u64 > 10 {
335
336 }
337 let b = bar(1) as u64 * 10;
338 bar(bar(1) as u64);
339}",
340 );
341 }
342
343 #[test]
344 fn test_inline_let_bind_block_expr() {
345 check_assist(
346 inline_local_variable,
347 r"
348fn foo() {
349 let a$0 = { 10 + 1 };
350 a + 1;
351 if a > 10 {
352 }
353
354 while a > 10 {
355
356 }
357 let b = a * 10;
358 bar(a);
359}",
360 r"
361fn foo() {
362 { 10 + 1 } + 1;
363 if { 10 + 1 } > 10 {
364 }
365
366 while { 10 + 1 } > 10 {
367
368 }
369 let b = { 10 + 1 } * 10;
370 bar({ 10 + 1 });
371}",
372 );
373 }
374
375 #[test]
376 fn test_inline_let_bind_paren_expr() {
377 check_assist(
378 inline_local_variable,
379 r"
380fn foo() {
381 let a$0 = ( 10 + 1 );
382 a + 1;
383 if a > 10 {
384 }
385
386 while a > 10 {
387
388 }
389 let b = a * 10;
390 bar(a);
391}",
392 r"
393fn foo() {
394 ( 10 + 1 ) + 1;
395 if ( 10 + 1 ) > 10 {
396 }
397
398 while ( 10 + 1 ) > 10 {
399
400 }
401 let b = ( 10 + 1 ) * 10;
402 bar(( 10 + 1 ));
403}",
404 );
405 }
406
407 #[test]
408 fn test_not_inline_mut_variable() {
409 cov_mark::check!(test_not_inline_mut_variable);
410 check_assist_not_applicable(
411 inline_local_variable,
412 r"
413fn foo() {
414 let mut a$0 = 1 + 1;
415 a + 1;
416}",
417 );
418 }
419
420 #[test]
421 fn test_not_inline_mut_variable_use() {
422 cov_mark::check!(test_not_inline_mut_variable_use);
423 check_assist_not_applicable(
424 inline_local_variable,
425 r"
426fn foo() {
427 let mut a = 1 + 1;
428 a$0 + 1;
429}",
430 );
431 }
432
433 #[test]
434 fn test_call_expr() {
435 check_assist(
436 inline_local_variable,
437 r"
438fn foo() {
439 let a$0 = bar(10 + 1);
440 let b = a * 10;
441 let c = a as usize;
442}",
443 r"
444fn foo() {
445 let b = bar(10 + 1) * 10;
446 let c = bar(10 + 1) as usize;
447}",
448 );
449 }
450
451 #[test]
452 fn test_index_expr() {
453 check_assist(
454 inline_local_variable,
455 r"
456fn foo() {
457 let x = vec![1, 2, 3];
458 let a$0 = x[0];
459 let b = a * 10;
460 let c = a as usize;
461}",
462 r"
463fn foo() {
464 let x = vec![1, 2, 3];
465 let b = x[0] * 10;
466 let c = x[0] as usize;
467}",
468 );
469 }
470
471 #[test]
472 fn test_method_call_expr() {
473 check_assist(
474 inline_local_variable,
475 r"
476fn foo() {
477 let bar = vec![1];
478 let a$0 = bar.len();
479 let b = a * 10;
480 let c = a as usize;
481}",
482 r"
483fn foo() {
484 let bar = vec![1];
485 let b = bar.len() * 10;
486 let c = bar.len() as usize;
487}",
488 );
489 }
490
491 #[test]
492 fn test_field_expr() {
493 check_assist(
494 inline_local_variable,
495 r"
496struct Bar {
497 foo: usize
498}
499
500fn foo() {
501 let bar = Bar { foo: 1 };
502 let a$0 = bar.foo;
503 let b = a * 10;
504 let c = a as usize;
505}",
506 r"
507struct Bar {
508 foo: usize
509}
510
511fn foo() {
512 let bar = Bar { foo: 1 };
513 let b = bar.foo * 10;
514 let c = bar.foo as usize;
515}",
516 );
517 }
518
519 #[test]
520 fn test_try_expr() {
521 check_assist(
522 inline_local_variable,
523 r"
524fn foo() -> Option<usize> {
525 let bar = Some(1);
526 let a$0 = bar?;
527 let b = a * 10;
528 let c = a as usize;
529 None
530}",
531 r"
532fn foo() -> Option<usize> {
533 let bar = Some(1);
534 let b = bar? * 10;
535 let c = bar? as usize;
536 None
537}",
538 );
539 }
540
541 #[test]
542 fn test_ref_expr() {
543 check_assist(
544 inline_local_variable,
545 r"
546fn foo() {
547 let bar = 10;
548 let a$0 = &bar;
549 let b = a * 10;
550}",
551 r"
552fn foo() {
553 let bar = 10;
554 let b = &bar * 10;
555}",
556 );
557 }
558
559 #[test]
560 fn test_tuple_expr() {
561 check_assist(
562 inline_local_variable,
563 r"
564fn foo() {
565 let a$0 = (10, 20);
566 let b = a[0];
567}",
568 r"
569fn foo() {
570 let b = (10, 20)[0];
571}",
572 );
573 }
574
575 #[test]
576 fn test_array_expr() {
577 check_assist(
578 inline_local_variable,
579 r"
580fn foo() {
581 let a$0 = [1, 2, 3];
582 let b = a.len();
583}",
584 r"
585fn foo() {
586 let b = [1, 2, 3].len();
587}",
588 );
589 }
590
591 #[test]
592 fn test_paren() {
593 check_assist(
594 inline_local_variable,
595 r"
596fn foo() {
597 let a$0 = (10 + 20);
598 let b = a * 10;
599 let c = a as usize;
600}",
601 r"
602fn foo() {
603 let b = (10 + 20) * 10;
604 let c = (10 + 20) as usize;
605}",
606 );
607 }
608
609 #[test]
610 fn test_path_expr() {
611 check_assist(
612 inline_local_variable,
613 r"
614fn foo() {
615 let d = 10;
616 let a$0 = d;
617 let b = a * 10;
618 let c = a as usize;
619}",
620 r"
621fn foo() {
622 let d = 10;
623 let b = d * 10;
624 let c = d as usize;
625}",
626 );
627 }
628
629 #[test]
630 fn test_block_expr() {
631 check_assist(
632 inline_local_variable,
633 r"
634fn foo() {
635 let a$0 = { 10 };
636 let b = a * 10;
637 let c = a as usize;
638}",
639 r"
640fn foo() {
641 let b = { 10 } * 10;
642 let c = { 10 } as usize;
643}",
644 );
645 }
646
647 #[test]
648 fn test_used_in_different_expr1() {
649 check_assist(
650 inline_local_variable,
651 r"
652fn foo() {
653 let a$0 = 10 + 20;
654 let b = a * 10;
655 let c = (a, 20);
656 let d = [a, 10];
657 let e = (a);
658}",
659 r"
660fn foo() {
661 let b = (10 + 20) * 10;
662 let c = (10 + 20, 20);
663 let d = [10 + 20, 10];
664 let e = (10 + 20);
665}",
666 );
667 }
668
669 #[test]
670 fn test_used_in_for_expr() {
671 check_assist(
672 inline_local_variable,
673 r"
674fn foo() {
675 let a$0 = vec![10, 20];
676 for i in a {}
677}",
678 r"
679fn foo() {
680 for i in vec![10, 20] {}
681}",
682 );
683 }
684
685 #[test]
686 fn test_used_in_while_expr() {
687 check_assist(
688 inline_local_variable,
689 r"
690fn foo() {
691 let a$0 = 1 > 0;
692 while a {}
693}",
694 r"
695fn foo() {
696 while 1 > 0 {}
697}",
698 );
699 }
700
701 #[test]
702 fn test_used_in_break_expr() {
703 check_assist(
704 inline_local_variable,
705 r"
706fn foo() {
707 let a$0 = 1 + 1;
708 loop {
709 break a;
710 }
711}",
712 r"
713fn foo() {
714 loop {
715 break 1 + 1;
716 }
717}",
718 );
719 }
720
721 #[test]
722 fn test_used_in_return_expr() {
723 check_assist(
724 inline_local_variable,
725 r"
726fn foo() {
727 let a$0 = 1 > 0;
728 return a;
729}",
730 r"
731fn foo() {
732 return 1 > 0;
733}",
734 );
735 }
736
737 #[test]
738 fn test_used_in_match_expr() {
739 check_assist(
740 inline_local_variable,
741 r"
742fn foo() {
743 let a$0 = 1 > 0;
744 match a {}
745}",
746 r"
747fn foo() {
748 match 1 > 0 {}
749}",
750 );
751 }
752
753 #[test]
754 fn inline_field_shorthand() {
755 cov_mark::check!(inline_field_shorthand);
756 check_assist(
757 inline_local_variable,
758 r"
759struct S { foo: i32}
760fn main() {
761 let $0foo = 92;
762 S { foo }
763}
764",
765 r"
766struct S { foo: i32}
767fn main() {
768 S { foo: 92 }
769}
770",
771 );
772 }
773
774 #[test]
775 fn test_not_applicable_if_variable_unused() {
776 cov_mark::check!(test_not_applicable_if_variable_unused);
777 check_assist_not_applicable(
778 inline_local_variable,
779 r"
780fn foo() {
781 let $0a = 0;
782}
783 ",
784 )
785 }
786
787 #[test]
788 fn not_applicable_outside_of_bind_pat() {
789 cov_mark::check!(not_applicable_outside_of_bind_pat);
790 check_assist_not_applicable(
791 inline_local_variable,
792 r"
793fn main() {
794 let x = $01 + 2;
795 x * 4;
796}
797",
798 )
799 }
800
801 #[test]
802 fn works_on_local_usage() {
803 check_assist(
804 inline_local_variable,
805 r#"
806fn f() {
807 let xyz = 0;
808 xyz$0;
809}
810"#,
811 r#"
812fn f() {
813 0;
814}
815"#,
816 );
817 }
818
819 #[test]
820 fn does_not_remove_let_when_multiple_usages() {
821 check_assist(
822 inline_local_variable,
823 r#"
824fn f() {
825 let xyz = 0;
826 xyz$0;
827 xyz;
828}
829"#,
830 r#"
831fn f() {
832 let xyz = 0;
833 0;
834 xyz;
835}
836"#,
837 );
838 }
839
840 #[test]
841 fn not_applicable_with_non_ident_pattern() {
842 check_assist_not_applicable(
843 inline_local_variable,
844 r#"
845fn main() {
846 let (x, y) = (0, 1);
847 x$0;
848}
849"#,
850 );
851 }
852
853 #[test]
854 fn not_applicable_on_local_usage_in_macro() {
855 check_assist_not_applicable(
856 inline_local_variable,
857 r#"
858macro_rules! m {
859 ($i:ident) => { $i }
860}
861fn f() {
862 let xyz = 0;
863 m!(xyz$0); // replacing it would break the macro
864}
865"#,
866 );
867 check_assist_not_applicable(
868 inline_local_variable,
869 r#"
870macro_rules! m {
871 ($i:ident) => { $i }
872}
873fn f() {
874 let xyz$0 = 0;
875 m!(xyz); // replacing it would break the macro
876}
877"#,
878 );
879 }
880
881 #[test]
882 fn test_not_inline_selection_too_broad() {
883 cov_mark::check!(test_not_inline_selection_too_broad);
884 check_assist_not_applicable(
885 inline_local_variable,
886 r#"
887fn f() {
888 let foo = 0;
889 let bar = 0;
890 $0foo + bar$0;
891}
892"#,
893 );
894 }
895
896 #[test]
897 fn test_inline_ref_in_let() {
898 check_assist(
899 inline_local_variable,
900 r#"
901fn f() {
902 let x = {
903 let y = 0;
904 y$0
905 };
906}
907"#,
908 r#"
909fn f() {
910 let x = {
911 0
912 };
913}
914"#,
915 );
916 }
917
918 #[test]
919 fn test_inline_let_unit_struct() {
920 check_assist_not_applicable(
921 inline_local_variable,
922 r#"
923struct S;
924fn f() {
925 let S$0 = S;
926 S;
927}
928"#,
929 );
930 }
931
932 #[test]
933 fn test_inline_closure() {
934 check_assist(
935 inline_local_variable,
936 r#"
937fn main() {
938 let $0f = || 2;
939 let _ = f();
940}
941"#,
942 r#"
943fn main() {
944 let _ = (|| 2)();
945}
946"#,
947 );
948 }
949
950 #[test]
951 fn test_wrap_in_parens() {
952 check_assist(
953 inline_local_variable,
954 r#"
955fn main() {
956 let $0a = 123 < 456;
957 let b = !a;
958}
959"#,
960 r#"
961fn main() {
962 let b = !(123 < 456);
963}
964"#,
965 );
966 check_assist(
967 inline_local_variable,
968 r#"
969trait Foo {
970 fn foo(&self);
971}
972
973impl Foo for bool {
974 fn foo(&self) {}
975}
976
977fn main() {
978 let $0a = 123 < 456;
979 let b = a.foo();
980}
981"#,
982 r#"
983trait Foo {
984 fn foo(&self);
985}
986
987impl Foo for bool {
988 fn foo(&self) {}
989}
990
991fn main() {
992 let b = (123 < 456).foo();
993}
994"#,
995 );
996 }
997}