Skip to content

Commit c459dcc

Browse files
committed
fix: preserve numeric type suffix in inline_const_as_literal
When inlining a numeric scalar const, append the const's type as a literal suffix so the resulting literal keeps its type. Without this, inlining `const N: i32 = 24;` followed by `N.wrapping_add(7i32)` would produce `24.wrapping_add(7i32)`, which fails with E0689 because `24` is left as an ambiguous `{integer}`. The suffix is only attached when the const's outer type is one of the built-in integer or float types and the rendered value ends in a digit, which skips special floats like `inf` and `NaN`. This change was prepared with the assistance of an LLM tool. Closes #22051
1 parent 7f916ab commit c459dcc

1 file changed

Lines changed: 108 additions & 16 deletions

File tree

crates/ide-assists/src/handlers/inline_const_as_literal.rs

Lines changed: 108 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,27 @@ pub(crate) fn inline_const_as_literal(
4242
// FIXME: Add support to handle type aliases for builtin scalar types.
4343
validate_type_recursively(ctx, Some(&konst_ty), false, fuel)?;
4444

45-
let value = konst
45+
let mut value = konst
4646
.eval(ctx.sema.db)
4747
.ok()?
4848
.render(ctx.sema.db, konst.krate(ctx.sema.db).to_display_target(ctx.sema.db));
4949

50+
// Preserve the numeric type by appending a suffix when the const has a known
51+
// scalar numeric type. Without this, inlining `const N: i32 = 24;` followed by
52+
// `N.wrapping_add(7i32)` would yield `24.wrapping_add(7i32)`, which fails to
53+
// type-check (E0689) because `24` is left as an ambiguous `{integer}`.
54+
//
55+
// Only attach the suffix when the rendered value ends in a digit. Special float
56+
// values such as `inf` or `NaN` are skipped, since `inff32` is not a valid
57+
// literal; these cases were already producing non-literal output before this
58+
// change.
59+
if let Some(suffix) = numeric_type_suffix(&konst_ty)
60+
&& value.as_bytes().last().is_some_and(u8::is_ascii_digit)
61+
&& !value.ends_with(suffix)
62+
{
63+
value.push_str(suffix);
64+
}
65+
5066
let id = AssistId::refactor_inline("inline_const_as_literal");
5167

5268
let label = "Inline const as literal".to_owned();
@@ -59,6 +75,36 @@ pub(crate) fn inline_const_as_literal(
5975
None
6076
}
6177

78+
/// Returns the literal-suffix string (e.g. `"i32"`, `"u64"`, `"f32"`) for a scalar
79+
/// numeric type, otherwise `None`.
80+
fn numeric_type_suffix(ty: &hir::Type<'_>) -> Option<&'static str> {
81+
let builtin = ty.as_builtin()?;
82+
if !(builtin.is_int() || builtin.is_uint() || builtin.is_float()) {
83+
return None;
84+
}
85+
// The builtin type's name matches its literal suffix for every scalar numeric
86+
// type (`i32`, `u64`, `f32`, ...), so look the suffix up by name.
87+
Some(match builtin.name().as_str() {
88+
"i8" => "i8",
89+
"i16" => "i16",
90+
"i32" => "i32",
91+
"i64" => "i64",
92+
"i128" => "i128",
93+
"isize" => "isize",
94+
"u8" => "u8",
95+
"u16" => "u16",
96+
"u32" => "u32",
97+
"u64" => "u64",
98+
"u128" => "u128",
99+
"usize" => "usize",
100+
"f16" => "f16",
101+
"f32" => "f32",
102+
"f64" => "f64",
103+
"f128" => "f128",
104+
_ => return None,
105+
})
106+
}
107+
62108
fn validate_type_recursively(
63109
ctx: &AssistContext<'_, '_>,
64110
ty_hir: Option<&hir::Type<'_>>,
@@ -126,6 +172,13 @@ mod tests {
126172
("char", "'c'", CHAR),
127173
];
128174

175+
/// Renders the literal that the assist is expected to inline. For scalar numeric
176+
/// types we now annotate the literal with its type suffix to preserve type info
177+
/// across the inlining (see `numeric_type_suffix`).
178+
fn expected_inlined(ty: &str, val: &str, kind: u8) -> String {
179+
if kind == NUMBER { format!("{val}{ty}") } else { val.to_string() }
180+
}
181+
129182
// -----------Not supported-----------
130183
#[test]
131184
fn inline_const_as_literal_const_fn_call_slice() {
@@ -231,7 +284,8 @@ mod tests {
231284

232285
#[test]
233286
fn inline_const_as_literal_const_expr() {
234-
TEST_PAIRS.iter().for_each(|(ty, val, _)| {
287+
TEST_PAIRS.iter().for_each(|(ty, val, kind)| {
288+
let out = expected_inlined(ty, val, *kind);
235289
check_assist(
236290
inline_const_as_literal,
237291
&format!(
@@ -243,7 +297,7 @@ mod tests {
243297
&format!(
244298
r#"
245299
const ABC: {ty} = {val};
246-
fn a() {{ {val} }}
300+
fn a() {{ {out} }}
247301
"#
248302
),
249303
);
@@ -252,7 +306,8 @@ mod tests {
252306

253307
#[test]
254308
fn inline_const_as_literal_const_block_expr() {
255-
TEST_PAIRS.iter().for_each(|(ty, val, _)| {
309+
TEST_PAIRS.iter().for_each(|(ty, val, kind)| {
310+
let out = expected_inlined(ty, val, *kind);
256311
check_assist(
257312
inline_const_as_literal,
258313
&format!(
@@ -264,7 +319,7 @@ mod tests {
264319
&format!(
265320
r#"
266321
const ABC: {ty} = {{ {val} }};
267-
fn a() {{ {val} }}
322+
fn a() {{ {out} }}
268323
"#
269324
),
270325
);
@@ -273,7 +328,8 @@ mod tests {
273328

274329
#[test]
275330
fn inline_const_as_literal_const_block_eval_expr() {
276-
TEST_PAIRS.iter().for_each(|(ty, val, _)| {
331+
TEST_PAIRS.iter().for_each(|(ty, val, kind)| {
332+
let out = expected_inlined(ty, val, *kind);
277333
check_assist(
278334
inline_const_as_literal,
279335
&format!(
@@ -285,7 +341,7 @@ mod tests {
285341
&format!(
286342
r#"
287343
const ABC: {ty} = {{ true; {val} }};
288-
fn a() {{ {val} }}
344+
fn a() {{ {out} }}
289345
"#
290346
),
291347
);
@@ -294,7 +350,8 @@ mod tests {
294350

295351
#[test]
296352
fn inline_const_as_literal_const_block_eval_block_expr() {
297-
TEST_PAIRS.iter().for_each(|(ty, val, _)| {
353+
TEST_PAIRS.iter().for_each(|(ty, val, kind)| {
354+
let out = expected_inlined(ty, val, *kind);
298355
check_assist(
299356
inline_const_as_literal,
300357
&format!(
@@ -306,7 +363,7 @@ mod tests {
306363
&format!(
307364
r#"
308365
const ABC: {ty} = {{ true; {{ {val} }} }};
309-
fn a() {{ {val} }}
366+
fn a() {{ {out} }}
310367
"#
311368
),
312369
);
@@ -315,7 +372,8 @@ mod tests {
315372

316373
#[test]
317374
fn inline_const_as_literal_const_fn_call_block_nested_builtin() {
318-
TEST_PAIRS.iter().for_each(|(ty, val, _)| {
375+
TEST_PAIRS.iter().for_each(|(ty, val, kind)| {
376+
let out = expected_inlined(ty, val, *kind);
319377
check_assist(
320378
inline_const_as_literal,
321379
&format!(
@@ -329,7 +387,7 @@ mod tests {
329387
r#"
330388
const fn abc() -> {ty} {{ {{ {{ {{ {val} }} }} }} }}
331389
const ABC: {ty} = abc();
332-
fn a() {{ {val} }}
390+
fn a() {{ {out} }}
333391
"#
334392
),
335393
);
@@ -361,7 +419,8 @@ mod tests {
361419

362420
#[test]
363421
fn inline_const_as_literal_const_fn_call_builtin() {
364-
TEST_PAIRS.iter().for_each(|(ty, val, _)| {
422+
TEST_PAIRS.iter().for_each(|(ty, val, kind)| {
423+
let out = expected_inlined(ty, val, *kind);
365424
check_assist(
366425
inline_const_as_literal,
367426
&format!(
@@ -375,7 +434,7 @@ mod tests {
375434
r#"
376435
const fn abc() -> {ty} {{ {val} }}
377436
const ABC: {ty} = abc();
378-
fn a() {{ {val} }}
437+
fn a() {{ {out} }}
379438
"#
380439
),
381440
);
@@ -392,7 +451,7 @@ mod tests {
392451
"#,
393452
r#"
394453
const ABC: i32 = 1 + 2 + 3;
395-
fn a() { 6 }
454+
fn a() { 6i32 }
396455
"#,
397456
);
398457
}
@@ -406,7 +465,7 @@ mod tests {
406465
"#,
407466
r#"
408467
const ABC: i32 = { 1 + 2 + 3 };
409-
fn a() { 6 }
468+
fn a() { 6i32 }
410469
"#,
411470
);
412471
}
@@ -421,7 +480,7 @@ mod tests {
421480
"#,
422481
r#"
423482
const ABC: i32 = { (1 + 2 + 3) };
424-
fn a() { 6 }
483+
fn a() { 6i32 }
425484
"#,
426485
);
427486
}
@@ -710,4 +769,37 @@ mod tests {
710769
"#,
711770
);
712771
}
772+
773+
// Regression test for #22051: the inlined numeric literal must carry the
774+
// const's type suffix, otherwise method calls on the receiver fail to
775+
// type-check (E0689) because the literal is left as `{integer}`.
776+
#[test]
777+
fn inline_const_as_literal_preserves_int_suffix_on_method_receiver() {
778+
check_assist(
779+
inline_const_as_literal,
780+
r#"
781+
const BASE: i32 = 24;
782+
fn main() { let _ = BAS$0E.wrapping_add(7i32); }
783+
"#,
784+
r#"
785+
const BASE: i32 = 24;
786+
fn main() { let _ = 24i32.wrapping_add(7i32); }
787+
"#,
788+
);
789+
}
790+
791+
#[test]
792+
fn inline_const_as_literal_preserves_float_suffix() {
793+
check_assist(
794+
inline_const_as_literal,
795+
r#"
796+
const BASE: f32 = 1.5;
797+
fn main() { let _ = BAS$0E; }
798+
"#,
799+
r#"
800+
const BASE: f32 = 1.5;
801+
fn main() { let _ = 1.5f32; }
802+
"#,
803+
);
804+
}
713805
}

0 commit comments

Comments
 (0)