rulewright 0.1.2

Executable engineering standards for Rust workspaces
Documentation
#[cfg(test)]
use googletest::prelude::*;
use ra_ap_syntax::ast;

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

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example {
        label: "Future impl without Send assertion",
        code: "struct Foo;\nimpl Future for Foo { type Output = (); }",
        pass: false,
    },
    Example {
        label: "qualified Future impl without Send assertion",
        code: "struct Foo;\nimpl std::future::Future for Foo { type Output = (); }",
        pass: false,
    },
    Example {
        label: "Future impl with Send assertion",
        code: "struct Foo;\nimpl Future for Foo { type Output = (); }\nconst fn assert_send<T: Send>() {}\nconst _: () = assert_send::<Foo>();",
        pass: true,
    },
    Example {
        label: "two implementors, one unasserted",
        code: "struct Foo;\nstruct Bar;\nimpl Future for Foo { type Output = (); }\nimpl Future for Bar { type Output = (); }\nconst fn assert_send<T: Send>() {}\nconst _: () = assert_send::<Foo>();",
        pass: false,
    },
    Example {
        label: "no Future impl",
        code: "struct Foo;\nimpl Iterator for Foo { type Item = u8; }",
        pass: true,
    },
    Example {
        label: "Future impl in test module",
        code: "#[cfg(test)]\nmod tests {\n    struct Foo;\n    impl Future for Foo { type Output = (); }\n}",
        pass: true,
    },
];

crate::ast_rule!(
    future_send_assert,
    "Require a compile-time `Send` assertion for every explicit `impl Future` in the same file.",
    "Explicitly declared futures silently turning !Send breaks Tokio and runtime-abstraction consumers; a const assertion catches the regression at compile time.",
    Low,
);

fn check_future_send_assert(ctx: &AstCtx<'_>) -> Vec<Violation> {
    ctx.nodes::<ast::Impl>()
        .filter(|item_impl| !ctx.is_in_test(item_impl))
        .filter_map(|item_impl| {
            let name = future_impl_target(&item_impl)?;

            (!is_asserted(ctx.file.contents, &name)).then(|| {
                ctx.violation(
                    &item_impl,
                    format!(
                        "`impl Future for {name}` without a compile-time Send assertion — add `const _: () = assert_send::<{name}>();`"
                    ),
                )
            })
        })
        .collect()
}

fn future_impl_target(item_impl: &ast::Impl) -> Option<String> {
    let ast::Type::PathType(trait_type) = item_impl.trait_()? else {
        return None;
    };

    if !super::support::path_last_is(trait_type.path()?, "Future") {
        return None;
    }

    super::support::type_name(item_impl.self_ty()?)
}

fn is_asserted(contents: &str, name: &str) -> bool {
    contents.match_indices("assert_send").any(|(idx, needle)| {
        let tail = contents.get(idx + needle.len()..).unwrap_or("");
        let window = tail.split('>').next().unwrap_or("");

        window
            .split(|c: char| !c.is_alphanumeric() && c != '_')
            .any(|word| word == name)
    })
}

crate::rulewright_ast_test!(check_future_send_assert, {
    crate::example_tests!(EXAMPLES, check_future_send_assert);

    #[gtest]
    fn two_unasserted_implementors_flag_twice() -> Result<()> {
        let v = run(
            "struct Foo;\nstruct Bar;\nimpl Future for Foo { type Output = (); }\nimpl Future for Bar { type Output = (); }",
        );
        verify_eq!(v.len(), 2)?;

        Ok(())
    }
});