ide_assists/handlers/
inline_local_variable.rs

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
17// Assist: inline_local_variable
18//
19// Inlines a local variable.
20//
21// ```
22// fn main() {
23//     let x$0 = 1 + 2;
24//     x * 4;
25// }
26// ```
27// ->
28// ```
29// fn main() {
30//     (1 + 2) * 4;
31// }
32// ```
33pub(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                // Do not rename inside macros
55                // FIXME: This feels like a bad heuristic for macros
56                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        // Not applicable with locals with multiple definitions (i.e. or patterns)
185        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}