Skip to main content

wowlab_tidy/languages/rust/rules/safety/
mutex_in_async.rs

1use ra_ap_syntax::{
2    AstNode,
3    ast::{self},
4};
5
6use crate::{AstCtx, Example, Violation};
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[
10    Example {
11        label: "std Mutex in async fn",
12        code: "async fn f() { let _m = std::sync::Mutex::new(0); }",
13        pass: false,
14    },
15    Example {
16        label: "std Mutex in sync fn",
17        code: "fn f() { let _m = std::sync::Mutex::new(0); }",
18        pass: true,
19    },
20    Example {
21        label: "std Mutex type in async fn",
22        code: "async fn f() { let _m: std::sync::Mutex<i32> = std::sync::Mutex::new(0); }",
23        pass: false,
24    },
25    Example {
26        label: "Mutex in async in test",
27        code: "#[cfg(test)]\nmod tests {\n    async fn t() { let _m = std::sync::Mutex::new(0); }\n}",
28        pass: true,
29    },
30];
31
32crate::ast_rule!(
33    mutex_in_async,
34    "Flag `std::sync::Mutex` usage in async functions (use tokio::sync::Mutex).",
35    "std::sync::Mutex blocks the entire async runtime thread while held. Use tokio::sync::Mutex in async code.",
36    High,
37);
38
39fn check_mutex_in_async(ctx: &AstCtx<'_>) -> Vec<Violation> {
40    let expression_paths = ctx
41        .nodes::<ast::PathExpr>()
42        .filter(|path| is_in_async_fn(path) && !ctx.is_in_test(path))
43        .filter(|path| path.path().is_some_and(|path| is_std_mutex_path(&path)));
44    let expression_violations = expression_paths
45        .map(|path| {
46            ctx.violation(
47                &path,
48                "std::sync::Mutex in async function — use tokio::sync::Mutex to avoid blocking the runtime",
49            )
50        });
51    let type_paths = ctx
52        .nodes::<ast::PathType>()
53        .filter(|path| is_in_async_fn(path) && !ctx.is_in_test(path))
54        .filter(|path| path.path().is_some_and(|path| is_std_mutex_path(&path)));
55    let type_violations = type_paths.map(|path| {
56        ctx.violation(
57            &path,
58            "std::sync::Mutex type in async function — use tokio::sync::Mutex",
59        )
60    });
61
62    expression_violations.chain(type_violations).collect()
63}
64
65fn is_in_async_fn<N>(node: &N) -> bool
66where
67    N: AstNode,
68{
69    node.syntax()
70        .ancestors()
71        .skip(1)
72        .find_map(ast::Fn::cast)
73        .is_some_and(|function| function.async_token().is_some())
74}
75
76// #t(fn: rust_recursive_fn) syntax path qualifier depth is parser-bounded and naturally recursive
77fn is_std_mutex_path(path: &ast::Path) -> bool {
78    let current_is_mutex = path
79        .segment()
80        .and_then(|segment| segment.name_ref())
81        .is_some_and(|name| name.text() == "Mutex");
82    let sync_qualifier = path.qualifier().filter(|qualifier| {
83        qualifier
84            .segment()
85            .and_then(|segment| segment.name_ref())
86            .is_some_and(|name| name.text() == "sync")
87    });
88
89    if current_is_mutex && sync_qualifier.is_some() {
90        let parent_segment = sync_qualifier
91            .and_then(|sync| sync.qualifier())
92            .and_then(|qualifier| qualifier.segment());
93        let parent = parent_segment.and_then(|segment| segment.name_ref());
94
95        return parent.is_none_or(|name| name.text() != "tokio");
96    }
97
98    path.qualifier()
99        .is_some_and(|qualifier| is_std_mutex_path(&qualifier))
100}
101
102crate::tidy_ast_test!(check_mutex_in_async, {
103    crate::example_tests!(EXAMPLES, check_mutex_in_async);
104});