Skip to main content

wowlab_tidy/languages/rust/rules/interop/
future_send_assert.rs

1#[cfg(test)]
2use googletest::prelude::*;
3use ra_ap_syntax::ast;
4
5use crate::{AstCtx, Example, Violation};
6
7#[rustfmt::skip]
8const EXAMPLES: &[Example] = &[
9    Example {
10        label: "Future impl without Send assertion",
11        code: "struct Foo;\nimpl Future for Foo { type Output = (); }",
12        pass: false,
13    },
14    Example {
15        label: "qualified Future impl without Send assertion",
16        code: "struct Foo;\nimpl std::future::Future for Foo { type Output = (); }",
17        pass: false,
18    },
19    Example {
20        label: "Future impl with Send assertion",
21        code: "struct Foo;\nimpl Future for Foo { type Output = (); }\nconst fn assert_send<T: Send>() {}\nconst _: () = assert_send::<Foo>();",
22        pass: true,
23    },
24    Example {
25        label: "two implementors, one unasserted",
26        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>();",
27        pass: false,
28    },
29    Example {
30        label: "no Future impl",
31        code: "struct Foo;\nimpl Iterator for Foo { type Item = u8; }",
32        pass: true,
33    },
34    Example {
35        label: "Future impl in test module",
36        code: "#[cfg(test)]\nmod tests {\n    struct Foo;\n    impl Future for Foo { type Output = (); }\n}",
37        pass: true,
38    },
39];
40
41crate::ast_rule!(
42    future_send_assert,
43    "Require a compile-time `Send` assertion for every explicit `impl Future` in the same file.",
44    "Explicitly declared futures silently turning !Send breaks Tokio and runtime-abstraction consumers; a const assertion catches the regression at compile time (M-TYPES-SEND).",
45    Low,
46);
47
48fn check_future_send_assert(ctx: &AstCtx<'_>) -> Vec<Violation> {
49    ctx.nodes::<ast::Impl>()
50        .filter(|item_impl| !ctx.is_in_test(item_impl))
51        .filter_map(|item_impl| {
52            let name = future_impl_target(&item_impl)?;
53
54            (!is_asserted(ctx.file.contents, &name)).then(|| {
55                ctx.violation(
56                    &item_impl,
57                    format!(
58                        "`impl Future for {name}` without a compile-time Send assertion — add `const _: () = assert_send::<{name}>();`"
59                    ),
60                )
61            })
62        })
63        .collect()
64}
65
66fn future_impl_target(item_impl: &ast::Impl) -> Option<String> {
67    let ast::Type::PathType(trait_type) = item_impl.trait_()? else {
68        return None;
69    };
70
71    if !super::support::path_last_is(trait_type.path()?, "Future") {
72        return None;
73    }
74
75    super::support::type_name(item_impl.self_ty()?)
76}
77
78fn is_asserted(contents: &str, name: &str) -> bool {
79    contents.match_indices("assert_send").any(|(idx, needle)| {
80        let tail = contents.get(idx + needle.len()..).unwrap_or("");
81        let window = tail.split('>').next().unwrap_or("");
82
83        window
84            .split(|c: char| !c.is_alphanumeric() && c != '_')
85            .any(|word| word == name)
86    })
87}
88
89crate::tidy_ast_test!(check_future_send_assert, {
90    crate::example_tests!(EXAMPLES, check_future_send_assert);
91
92    #[gtest]
93    fn two_unasserted_implementors_flag_twice() -> Result<()> {
94        let v = run(
95            "struct Foo;\nstruct Bar;\nimpl Future for Foo { type Output = (); }\nimpl Future for Bar { type Output = (); }",
96        );
97        verify_eq!(v.len(), 2)?;
98
99        Ok(())
100    }
101});