wowlab_tidy/languages/rust/rules/interop/
future_send_assert.rs1#[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});