ide_assists/handlers/
inline_const_as_literal.rs

1use hir::HasCrate;
2use syntax::{AstNode, ast};
3
4use crate::{AssistContext, AssistId, Assists};
5
6// Assist: inline_const_as_literal
7//
8// Evaluate and inline const variable as literal.
9//
10// ```
11// const STRING: &str = "Hello, World!";
12//
13// fn something() -> &'static str {
14//     STRING$0
15// }
16// ```
17// ->
18// ```
19// const STRING: &str = "Hello, World!";
20//
21// fn something() -> &'static str {
22//     "Hello, World!"
23// }
24// ```
25pub(crate) fn inline_const_as_literal(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
26    let variable = ctx.find_node_at_offset::<ast::PathExpr>()?;
27
28    if let hir::PathResolution::Def(hir::ModuleDef::Const(konst)) =
29        ctx.sema.resolve_path(&variable.path()?)?
30    {
31        let konst_ty = konst.ty(ctx.sema.db);
32
33        // Used as the upper limit for recursive calls if no TCO is available
34        let fuel = 20;
35
36        // There is no way to have a const static reference to a type that contains a interior
37        // mutability cell.
38
39        // FIXME: Add support to handle type aliases for builtin scalar types.
40        validate_type_recursively(ctx, Some(&konst_ty), false, fuel)?;
41
42        let value = konst
43            .eval(ctx.sema.db)
44            .ok()?
45            .render(ctx.sema.db, konst.krate(ctx.sema.db).to_display_target(ctx.sema.db));
46
47        let id = AssistId::refactor_inline("inline_const_as_literal");
48
49        let label = "Inline const as literal".to_owned();
50        let target = variable.syntax().text_range();
51
52        return acc.add(id, label, target, |edit| {
53            edit.replace(variable.syntax().text_range(), value);
54        });
55    }
56    None
57}
58
59fn validate_type_recursively(
60    ctx: &AssistContext<'_>,
61    ty_hir: Option<&hir::Type<'_>>,
62    refed: bool,
63    fuel: i32,
64) -> Option<()> {
65    match (fuel > 0, ty_hir) {
66        (true, Some(ty)) if ty.is_reference() => validate_type_recursively(
67            ctx,
68            ty.as_reference().map(|(ty, _)| ty).as_ref(),
69            true,
70            // FIXME: Saving fuel when `&` repeating might not be a good idea if there's no TCO.
71            if refed { fuel } else { fuel - 1 },
72        ),
73        (true, Some(ty)) if ty.is_array() => validate_type_recursively(
74            ctx,
75            ty.as_array(ctx.db()).map(|(ty, _)| ty).as_ref(),
76            false,
77            fuel - 1,
78        ),
79        (true, Some(ty)) if ty.is_tuple() => ty
80            .tuple_fields(ctx.db())
81            .iter()
82            .all(|ty| validate_type_recursively(ctx, Some(ty), false, fuel - 1).is_some())
83            .then_some(()),
84        (true, Some(ty)) if refed && ty.is_slice() => {
85            validate_type_recursively(ctx, ty.as_slice().as_ref(), false, fuel - 1)
86        }
87        (_, Some(ty)) => match ty.as_builtin() {
88            // `const A: str` is not correct, but `const A: &builtin` is.
89            Some(builtin) if refed || !builtin.is_str() => Some(()),
90            _ => None,
91        },
92        _ => None,
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use crate::tests::{check_assist, check_assist_not_applicable};
100
101    const NUMBER: u8 = 1;
102    const BOOL: u8 = 2;
103    const STR: u8 = 4;
104    const CHAR: u8 = 8;
105
106    const TEST_PAIRS: &[(&str, &str, u8)] = &[
107        ("u8", "0", NUMBER),
108        ("u16", "0", NUMBER),
109        ("u32", "0", NUMBER),
110        ("u64", "0", NUMBER),
111        ("u128", "0", NUMBER),
112        ("usize", "0", NUMBER),
113        ("usize", "16", NUMBER),
114        ("i8", "0", NUMBER),
115        ("i16", "0", NUMBER),
116        ("i32", "0", NUMBER),
117        ("i64", "0", NUMBER),
118        ("i128", "0", NUMBER),
119        ("isize", "0", NUMBER),
120        ("isize", "16", NUMBER),
121        ("bool", "false", BOOL),
122        ("&str", "\"str\"", STR),
123        ("char", "'c'", CHAR),
124    ];
125
126    // -----------Not supported-----------
127    #[test]
128    fn inline_const_as_literal_const_fn_call_slice() {
129        TEST_PAIRS.iter().for_each(|(ty, val, _)| {
130            check_assist_not_applicable(
131                inline_const_as_literal,
132                &format!(
133                    r#"
134                    const fn abc() -> &[{ty}] {{ &[{val}] }}
135                    const ABC: &[{ty}] = abc();
136                    fn a() {{ A$0BC }}
137                    "#
138                ),
139            );
140        });
141    }
142
143    #[test]
144    fn inline_const_as_literal_expr_as_str_lit_not_applicable_const() {
145        check_assist_not_applicable(
146            inline_const_as_literal,
147            r#"
148            const STR$0ING: &str = "Hello, World!";
149
150            fn something() -> &'static str {
151                STRING
152            }
153            "#,
154        );
155    }
156
157    #[test]
158    fn inline_const_as_struct_() {
159        check_assist_not_applicable(
160            inline_const_as_literal,
161            r#"
162            struct A;
163            const STRUKT: A = A;
164
165            fn something() -> A {
166                STRU$0KT
167            }
168            "#,
169        );
170    }
171
172    #[test]
173    fn inline_const_as_enum_() {
174        check_assist_not_applicable(
175            inline_const_as_literal,
176            r#"
177            enum A { A, B, C }
178            const ENUM: A = A::A;
179
180            fn something() -> A {
181                EN$0UM
182            }
183            "#,
184        );
185    }
186
187    #[test]
188    fn inline_const_as_tuple_closure() {
189        check_assist_not_applicable(
190            inline_const_as_literal,
191            r#"
192            const CLOSURE: (&dyn Fn(i32) -> i32) = (&|num| -> i32 { num });
193            fn something() -> (&dyn Fn(i32) -> i32) {
194                STRU$0KT
195            }
196            "#,
197        );
198    }
199
200    #[test]
201    fn inline_const_as_closure_() {
202        check_assist_not_applicable(
203            inline_const_as_literal,
204            r#"
205            const CLOSURE: &dyn Fn(i32) -> i32 = &|num| -> i32 { num };
206            fn something() -> &dyn Fn(i32) -> i32 {
207                STRU$0KT
208            }
209            "#,
210        );
211    }
212
213    #[test]
214    fn inline_const_as_fn_() {
215        check_assist_not_applicable(
216            inline_const_as_literal,
217            r#"
218            struct S(i32);
219            const CON: fn(i32) -> S = S;
220            fn something() {
221                let x = CO$0N;
222            }
223            "#,
224        );
225    }
226
227    // ----------------------------
228
229    #[test]
230    fn inline_const_as_literal_const_expr() {
231        TEST_PAIRS.iter().for_each(|(ty, val, _)| {
232            check_assist(
233                inline_const_as_literal,
234                &format!(
235                    r#"
236                    const ABC: {ty} = {val};
237                    fn a() {{ A$0BC }}
238                    "#
239                ),
240                &format!(
241                    r#"
242                    const ABC: {ty} = {val};
243                    fn a() {{ {val} }}
244                    "#
245                ),
246            );
247        });
248    }
249
250    #[test]
251    fn inline_const_as_literal_const_block_expr() {
252        TEST_PAIRS.iter().for_each(|(ty, val, _)| {
253            check_assist(
254                inline_const_as_literal,
255                &format!(
256                    r#"
257                    const ABC: {ty} = {{ {val} }};
258                    fn a() {{ A$0BC }}
259                    "#
260                ),
261                &format!(
262                    r#"
263                    const ABC: {ty} = {{ {val} }};
264                    fn a() {{ {val} }}
265                    "#
266                ),
267            );
268        });
269    }
270
271    #[test]
272    fn inline_const_as_literal_const_block_eval_expr() {
273        TEST_PAIRS.iter().for_each(|(ty, val, _)| {
274            check_assist(
275                inline_const_as_literal,
276                &format!(
277                    r#"
278                    const ABC: {ty} = {{ true; {val} }};
279                    fn a() {{ A$0BC }}
280                    "#
281                ),
282                &format!(
283                    r#"
284                    const ABC: {ty} = {{ true; {val} }};
285                    fn a() {{ {val} }}
286                    "#
287                ),
288            );
289        });
290    }
291
292    #[test]
293    fn inline_const_as_literal_const_block_eval_block_expr() {
294        TEST_PAIRS.iter().for_each(|(ty, val, _)| {
295            check_assist(
296                inline_const_as_literal,
297                &format!(
298                    r#"
299                    const ABC: {ty} = {{ true; {{ {val} }} }};
300                    fn a() {{ A$0BC }}
301                    "#
302                ),
303                &format!(
304                    r#"
305                    const ABC: {ty} = {{ true; {{ {val} }} }};
306                    fn a() {{ {val} }}
307                    "#
308                ),
309            );
310        });
311    }
312
313    #[test]
314    fn inline_const_as_literal_const_fn_call_block_nested_builtin() {
315        TEST_PAIRS.iter().for_each(|(ty, val, _)| {
316            check_assist(
317                inline_const_as_literal,
318                &format!(
319                    r#"
320                    const fn abc() -> {ty} {{ {{ {{ {{ {val} }} }} }} }}
321                    const ABC: {ty} = abc();
322                    fn a() {{ A$0BC }}
323                    "#
324                ),
325                &format!(
326                    r#"
327                    const fn abc() -> {ty} {{ {{ {{ {{ {val} }} }} }} }}
328                    const ABC: {ty} = abc();
329                    fn a() {{ {val} }}
330                    "#
331                ),
332            );
333        });
334    }
335
336    #[test]
337    fn inline_const_as_literal_const_fn_call_tuple() {
338        TEST_PAIRS.iter().for_each(|(ty, val, _)| {
339            check_assist(
340                inline_const_as_literal,
341                &format!(
342                    r#"
343                    const fn abc() -> ({ty}, {ty}) {{ ({val}, {val}) }}
344                    const ABC: ({ty}, {ty}) = abc();
345                    fn a() {{ A$0BC }}
346                    "#
347                ),
348                &format!(
349                    r#"
350                    const fn abc() -> ({ty}, {ty}) {{ ({val}, {val}) }}
351                    const ABC: ({ty}, {ty}) = abc();
352                    fn a() {{ ({val}, {val}) }}
353                    "#
354                ),
355            );
356        });
357    }
358
359    #[test]
360    fn inline_const_as_literal_const_fn_call_builtin() {
361        TEST_PAIRS.iter().for_each(|(ty, val, _)| {
362            check_assist(
363                inline_const_as_literal,
364                &format!(
365                    r#"
366					const fn abc() -> {ty} {{ {val} }}
367					const ABC: {ty} = abc();
368					fn a() {{ A$0BC }}
369					"#
370                ),
371                &format!(
372                    r#"
373					const fn abc() -> {ty} {{ {val} }}
374					const ABC: {ty} = abc();
375					fn a() {{ {val} }}
376					"#
377                ),
378            );
379        });
380    }
381
382    #[test]
383    fn inline_const_as_literal_scalar_operators() {
384        check_assist(
385            inline_const_as_literal,
386            r#"
387            const ABC: i32 = 1 + 2 + 3;
388            fn a() { A$0BC }
389            "#,
390            r#"
391            const ABC: i32 = 1 + 2 + 3;
392            fn a() { 6 }
393            "#,
394        );
395    }
396    #[test]
397    fn inline_const_as_literal_block_scalar_calculate_expr() {
398        check_assist(
399            inline_const_as_literal,
400            r#"
401            const ABC: i32 = { 1 + 2 + 3 };
402            fn a() { A$0BC }
403            "#,
404            r#"
405            const ABC: i32 = { 1 + 2 + 3 };
406            fn a() { 6 }
407            "#,
408        );
409    }
410
411    #[test]
412    fn inline_const_as_literal_block_scalar_calculate_param_expr() {
413        check_assist(
414            inline_const_as_literal,
415            r#"
416            const ABC: i32 = { (1 + 2 + 3) };
417            fn a() { A$0BC }
418            "#,
419            r#"
420            const ABC: i32 = { (1 + 2 + 3) };
421            fn a() { 6 }
422            "#,
423        );
424    }
425
426    #[test]
427    fn inline_const_as_literal_block_tuple_scalar_calculate_block_expr() {
428        check_assist(
429            inline_const_as_literal,
430            r#"
431            const ABC: (i32, i32) = { (1, { 2 + 3 }) };
432            fn a() { A$0BC }
433            "#,
434            r#"
435            const ABC: (i32, i32) = { (1, { 2 + 3 }) };
436            fn a() { (1, 5) }
437            "#,
438        );
439    }
440
441    // FIXME: Add support for nested ref slices when using `render_eval`
442    #[test]
443    fn inline_const_as_literal_block_slice() {
444        check_assist_not_applicable(
445            inline_const_as_literal,
446            r#"
447            const ABC: &[&[&[&[&[&[i32]]]]]] = { &[&[&[&[&[&[10, 20, 30]]]]]] };
448            fn a() { A$0BC }
449            "#,
450        );
451    }
452
453    // FIXME: Add support for unary tuple expressions when using `render_eval`.
454    // `const fn abc() -> (i32) { (1) }` will results in `1` instead of `(1)` because it's evaluated
455    // as a paren expr.
456    #[test]
457    fn inline_const_as_literal_block_tuple() {
458        check_assist(
459            inline_const_as_literal,
460            r#"
461            const ABC: (([i32; 3]), (i32), ((&str, i32), i32), i32) = { (([1, 2, 3]), (10), (("hello", 10), 20), 30) };
462            fn a() { A$0BC }
463            "#,
464            r#"
465            const ABC: (([i32; 3]), (i32), ((&str, i32), i32), i32) = { (([1, 2, 3]), (10), (("hello", 10), 20), 30) };
466            fn a() { ([1, 2, 3], 10, (("hello", 10), 20), 30) }
467            "#,
468        );
469    }
470
471    #[test]
472    fn inline_const_as_literal_block_slice_single() {
473        check_assist(
474            inline_const_as_literal,
475            r#"
476            const ABC: [i32; 1] = { [10] };
477            fn a() { A$0BC }
478            "#,
479            r#"
480            const ABC: [i32; 1] = { [10] };
481            fn a() { [10] }
482            "#,
483        );
484    }
485
486    #[test]
487    fn inline_const_as_literal_block_array() {
488        check_assist(
489            inline_const_as_literal,
490            r#"
491            const ABC: [[[i32; 1]; 1]; 1] = { [[[10]]] };
492            fn a() { A$0BC }
493            "#,
494            r#"
495            const ABC: [[[i32; 1]; 1]; 1] = { [[[10]]] };
496            fn a() { [[[10]]] }
497            "#,
498        );
499    }
500
501    #[test]
502    fn inline_const_as_literal_block_recursive() {
503        check_assist(
504            inline_const_as_literal,
505            r#"
506            const ABC: &str = { { { { "hello" } } } };
507            fn a() { A$0BC }
508            "#,
509            r#"
510            const ABC: &str = { { { { "hello" } } } };
511            fn a() { "hello" }
512            "#,
513        );
514    }
515
516    #[test]
517    fn inline_const_as_literal_expr_as_str_lit() {
518        check_assist(
519            inline_const_as_literal,
520            r#"
521            const STRING: &str = "Hello, World!";
522
523            fn something() -> &'static str {
524                STR$0ING
525            }
526            "#,
527            r#"
528            const STRING: &str = "Hello, World!";
529
530            fn something() -> &'static str {
531                "Hello, World!"
532            }
533            "#,
534        );
535    }
536
537    #[test]
538    fn inline_const_as_literal_eval_const_block_expr_to_str_lit() {
539        check_assist(
540            inline_const_as_literal,
541            r#"
542            const STRING: &str = {
543                let x = 9;
544                if x + 10 == 21 {
545                    "Hello, World!"
546                } else {
547                    "World, Hello!"
548                }
549            };
550
551            fn something() -> &'static str {
552                STR$0ING
553            }
554            "#,
555            r#"
556            const STRING: &str = {
557                let x = 9;
558                if x + 10 == 21 {
559                    "Hello, World!"
560                } else {
561                    "World, Hello!"
562                }
563            };
564
565            fn something() -> &'static str {
566                "World, Hello!"
567            }
568            "#,
569        );
570    }
571
572    #[test]
573    fn inline_const_as_literal_eval_const_block_macro_expr_to_str_lit() {
574        check_assist(
575            inline_const_as_literal,
576            r#"
577            macro_rules! co {() => {"World, Hello!"};}
578            const STRING: &str = { co!() };
579
580            fn something() -> &'static str {
581                STR$0ING
582            }
583            "#,
584            r#"
585            macro_rules! co {() => {"World, Hello!"};}
586            const STRING: &str = { co!() };
587
588            fn something() -> &'static str {
589                "World, Hello!"
590            }
591            "#,
592        );
593    }
594
595    #[test]
596    fn inline_const_as_literal_eval_const_match_expr_to_str_lit() {
597        check_assist(
598            inline_const_as_literal,
599            r#"
600            const STRING: &str = match 9 + 10 {
601                0..18 => "Hello, World!",
602                _ => "World, Hello!"
603            };
604
605            fn something() -> &'static str {
606                STR$0ING
607            }
608            "#,
609            r#"
610            const STRING: &str = match 9 + 10 {
611                0..18 => "Hello, World!",
612                _ => "World, Hello!"
613            };
614
615            fn something() -> &'static str {
616                "World, Hello!"
617            }
618            "#,
619        );
620    }
621
622    #[test]
623    fn inline_const_as_literal_eval_const_if_expr_to_str_lit() {
624        check_assist(
625            inline_const_as_literal,
626            r#"
627            const STRING: &str = if 1 + 2 == 4 {
628                "Hello, World!"
629            } else {
630                "World, Hello!"
631            }
632
633            fn something() -> &'static str {
634                STR$0ING
635            }
636            "#,
637            r#"
638            const STRING: &str = if 1 + 2 == 4 {
639                "Hello, World!"
640            } else {
641                "World, Hello!"
642            }
643
644            fn something() -> &'static str {
645                "World, Hello!"
646            }
647            "#,
648        );
649    }
650
651    #[test]
652    fn inline_const_as_literal_eval_const_macro_expr_to_str_lit() {
653        check_assist(
654            inline_const_as_literal,
655            r#"
656            macro_rules! co {() => {"World, Hello!"};}
657            const STRING: &str = co!();
658
659            fn something() -> &'static str {
660                STR$0ING
661            }
662            "#,
663            r#"
664            macro_rules! co {() => {"World, Hello!"};}
665            const STRING: &str = co!();
666
667            fn something() -> &'static str {
668                "World, Hello!"
669            }
670            "#,
671        );
672    }
673
674    #[test]
675    fn inline_const_as_literal_eval_const_call_expr_to_str_lit() {
676        check_assist(
677            inline_const_as_literal,
678            r#"
679            const fn const_call() -> &'static str {"World, Hello!"}
680            const STRING: &str = const_call();
681
682            fn something() -> &'static str {
683                STR$0ING
684            }
685            "#,
686            r#"
687            const fn const_call() -> &'static str {"World, Hello!"}
688            const STRING: &str = const_call();
689
690            fn something() -> &'static str {
691                "World, Hello!"
692            }
693            "#,
694        );
695    }
696
697    #[test]
698    fn inline_const_as_literal_expr_as_str_lit_not_applicable() {
699        check_assist_not_applicable(
700            inline_const_as_literal,
701            r#"
702            const STRING: &str = "Hello, World!";
703
704            fn something() -> &'static str {
705                STRING $0
706            }
707            "#,
708        );
709    }
710}