rulewright 0.1.1

Executable engineering standards for Rust workspaces
Documentation
use ra_ap_syntax::ast;

use crate::{AstCtx, Example, Violation};

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example {
        label: "cast to u8",
        code: "fn f() { let x = 42u64 as u8; }",
        pass: false,
    },
    Example {
        label: "cast to f32",
        code: "fn f() { let x = 1.0f64 as f32; }",
        pass: false,
    },
    Example {
        label: "cast to i16",
        code: "fn f() { let x = 1000i32 as i16; }",
        pass: false,
    },
    Example {
        label: "cast to u32",
        code: "fn f() { let x = 42u64 as u32; }",
        pass: true,
    },
    Example {
        label: "cast to u64",
        code: "fn f() { let x = 42u32 as u64; }",
        pass: true,
    },
    Example {
        label: "cast to usize",
        code: "fn f() { let x = 42u32 as usize; }",
        pass: true,
    },
    Example {
        label: "cast in test module",
        code: "#[cfg(test)]\nmod tests {\n    fn f() { let x = 42u64 as u8; }\n}",
        pass: true,
    },
];

crate::ast_rule!(
    lossy_cast,
    "Flag `as` casts to types that lose precision (`f32`, `u8`, `u16`, `i8`, `i16`).",
    "Casting to a smaller type (u64 as u8) silently truncates. Use try_into() to catch overflow at runtime.",
    Medium,
);

const LOSSY_TARGETS: &[&str] = &["f32", "u8", "u16", "i8", "i16"];

fn check_lossy_cast(ctx: &AstCtx<'_>) -> Vec<Violation> {
    ctx.nodes::<ast::CastExpr>()
        .filter(|cast| !ctx.is_in_test(cast))
        .filter_map(|cast| {
            let ast::Type::PathType(path) = cast.ty()? else {
                return None;
            };
            let ident = path.path()?.segment()?.name_ref()?;

            LOSSY_TARGETS
                .contains(&ident.text().as_str())
                .then(|| {
                    ctx.violation(
                        &cast,
                        format!(
                            "potentially lossy cast to `{ident}` — use explicit conversion (e.g. try_into(), try_from())"
                        ),
                    )
                })
        })
        .collect()
}

crate::rulewright_ast_test!(check_lossy_cast, {
    crate::example_tests!(EXAMPLES, check_lossy_cast);
});