Skip to main content

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