wowlab_tidy/languages/rust/rules/correctness/
lossy_cast.rs1use ra_ap_syntax::ast;
2
3use crate::{AstCtx, Example, Violation};
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "cast to u8",
9 code: "fn f() { let x = 42u64 as u8; }",
10 pass: false,
11 },
12 Example {
13 label: "cast to f32",
14 code: "fn f() { let x = 1.0f64 as f32; }",
15 pass: false,
16 },
17 Example {
18 label: "cast to i16",
19 code: "fn f() { let x = 1000i32 as i16; }",
20 pass: false,
21 },
22 Example {
23 label: "cast to u32",
24 code: "fn f() { let x = 42u64 as u32; }",
25 pass: true,
26 },
27 Example {
28 label: "cast to u64",
29 code: "fn f() { let x = 42u32 as u64; }",
30 pass: true,
31 },
32 Example {
33 label: "cast to usize",
34 code: "fn f() { let x = 42u32 as usize; }",
35 pass: true,
36 },
37 Example {
38 label: "cast in test module",
39 code: "#[cfg(test)]\nmod tests {\n fn f() { let x = 42u64 as u8; }\n}",
40 pass: true,
41 },
42];
43
44crate::ast_rule!(
45 lossy_cast,
46 "Flag `as` casts to types that lose precision (`f32`, `u8`, `u16`, `i8`, `i16`).",
47 "Casting to a smaller type (u64 as u8) silently truncates. Use try_into() to catch overflow at runtime.",
48 Medium,
49);
50
51const LOSSY_TARGETS: &[&str] = &["f32", "u8", "u16", "i8", "i16"];
52
53fn check_lossy_cast(ctx: &AstCtx<'_>) -> Vec<Violation> {
54 ctx.nodes::<ast::CastExpr>()
55 .filter(|cast| !ctx.is_in_test(cast))
56 .filter_map(|cast| {
57 let ast::Type::PathType(path) = cast.ty()? else {
58 return None;
59 };
60 let ident = path.path()?.segment()?.name_ref()?;
61
62 LOSSY_TARGETS
63 .contains(&ident.text().as_str())
64 .then(|| {
65 ctx.violation(
66 &cast,
67 format!(
68 "potentially lossy cast to `{ident}` — use explicit conversion (e.g. try_into(), try_from())"
69 ),
70 )
71 })
72 })
73 .collect()
74}
75
76crate::tidy_ast_test!(check_lossy_cast, {
77 crate::example_tests!(EXAMPLES, check_lossy_cast);
78});