Skip to main content

wowlab_tidy/languages/rust/rules/performance/
support.rs

1use ra_ap_syntax::{
2    AstNode, SyntaxNode,
3    ast::{self, HasArgList, HasLoopBody, HasName, LiteralKind},
4};
5
6pub(super) use super::super::support::{estimate_type_size, parse_int_expr};
7
8const ONE_BYTE: u64 = 1;
9const TWO_BYTES: u64 = 2;
10const FOUR_BYTES: u64 = 4;
11const EIGHT_BYTES: u64 = 8;
12const SIXTEEN_BYTES: u64 = 16;
13
14pub(super) fn loop_body<N>(node: &N) -> Option<ast::BlockExpr>
15where
16    N: AstNode,
17{
18    let syntax = node.syntax().clone();
19
20    if let Some(loop_expr) = ast::ForExpr::cast(syntax.clone()) {
21        return loop_expr.loop_body();
22    }
23
24    if let Some(loop_expr) = ast::WhileExpr::cast(syntax.clone()) {
25        return loop_expr.loop_body();
26    }
27
28    ast::LoopExpr::cast(syntax).and_then(|loop_expr| loop_expr.loop_body())
29}
30
31pub(super) fn loop_source<N>(node: &N) -> Option<ast::Expr>
32where
33    N: AstNode,
34{
35    let syntax = node.syntax().clone();
36
37    if let Some(loop_expr) = ast::ForExpr::cast(syntax.clone()) {
38        return loop_expr.iterable();
39    }
40
41    ast::WhileExpr::cast(syntax).and_then(|loop_expr| loop_expr.condition())
42}
43
44pub(super) fn is_inside_loop_body<N>(node: &N) -> bool
45where
46    N: AstNode,
47{
48    node.syntax().ancestors().any(|ancestor| {
49        ast::BlockExpr::cast(ancestor).is_some_and(|block| {
50            block.syntax().parent().is_some_and(|parent| {
51                ast::ForExpr::can_cast(parent.kind())
52                    || ast::WhileExpr::can_cast(parent.kind())
53                    || ast::LoopExpr::can_cast(parent.kind())
54            })
55        })
56    })
57}
58
59pub(super) fn is_in_async_context<N>(node: &N) -> bool
60where
61    N: AstNode,
62{
63    for ancestor in node.syntax().ancestors().skip(1) {
64        // #t(rust_clone_in_loop) SyntaxNode clone is reference-counted and O(1)
65        if let Some(closure) = ast::ClosureExpr::cast(ancestor.clone()) {
66            return closure.async_token().is_some();
67        }
68
69        // #t(rust_clone_in_loop) SyntaxNode clone is reference-counted and O(1)
70        if let Some(block) = ast::BlockExpr::cast(ancestor.clone()) {
71            if block.async_token().is_some() {
72                return true;
73            }
74
75            continue;
76        }
77
78        if let Some(function) = ast::Fn::cast(ancestor) {
79            return function.async_token().is_some();
80        }
81    }
82
83    false
84}
85
86pub(super) use super::super::support::path_names;
87
88pub(super) fn path_expr_name(expr: &ast::Expr) -> Option<String> {
89    let ast::Expr::PathExpr(path_expr) = expr else {
90        return None;
91    };
92    let path = path_expr.path()?;
93
94    (path.qualifier().is_none()).then(|| {
95        path.segment()?
96            .name_ref()
97            .map(|name| name.text().to_string())
98    })?
99}
100
101pub(super) fn path_expr_last_name(expr: &ast::Expr) -> Option<String> {
102    let ast::Expr::PathExpr(path_expr) = expr else {
103        return None;
104    };
105
106    path_expr
107        .path()?
108        .segment()?
109        .name_ref()
110        .map(|name| name.text().to_string())
111}
112
113pub(super) fn method_name(call: &ast::MethodCallExpr) -> Option<String> {
114    call.name_ref().map(|name| name.text().to_string())
115}
116
117pub(super) fn has_no_args<N>(call: &N) -> bool
118where
119    N: HasArgList,
120{
121    call.arg_list()
122        .is_none_or(|arguments| arguments.args().next().is_none())
123}
124
125pub(super) fn ident_pattern_name(pattern: ast::Pat) -> Option<String> {
126    let ast::Pat::IdentPat(pattern) = pattern else {
127        return None;
128    };
129
130    pattern.name().map(|name| name.text().to_string())
131}
132
133pub(super) fn syntax_contains_ident(syntax: &SyntaxNode, name: &str) -> bool {
134    syntax
135        .descendants()
136        .filter_map(ast::PathExpr::cast)
137        .any(|path_expr| {
138            path_expr.path().is_some_and(|path| {
139                path.qualifier().is_none()
140                    && path
141                        .segment()
142                        .and_then(|segment| segment.name_ref())
143                        .is_some_and(|ident| ident.text() == name)
144            })
145        })
146}
147
148pub(super) fn literal_elem_size(expr: &ast::Expr) -> Option<u64> {
149    let ast::Expr::Literal(literal) = expr else {
150        return None;
151    };
152    let suffix = match literal.kind() {
153        LiteralKind::IntNumber(number) => number.suffix().map(str::to_owned),
154        LiteralKind::FloatNumber(number) => number.suffix().map(str::to_owned),
155        _ => None,
156    }?;
157
158    match suffix.as_str() {
159        "u8" | "i8" => Some(ONE_BYTE),
160        "u16" | "i16" => Some(TWO_BYTES),
161        "u32" | "i32" | "f32" => Some(FOUR_BYTES),
162        "u64" | "i64" | "usize" | "isize" | "f64" => Some(EIGHT_BYTES),
163        "u128" | "i128" => Some(SIXTEEN_BYTES),
164        _ => None,
165    }
166}