Skip to main content

wowlab_tidy/languages/rust/rules/
support.rs

1use ra_ap_syntax::{
2    AstNode, Edition, SourceFile,
3    ast::{self, HasModuleItem, LiteralKind},
4};
5
6const ONE_BYTE: u64 = 1;
7const TWO_BYTES: u64 = 2;
8const FOUR_BYTES: u64 = 4;
9const EIGHT_BYTES: u64 = 8;
10const SIXTEEN_BYTES: u64 = 16;
11const THREE_WORDS: u64 = 24;
12
13pub(super) fn parse_use(source: &str) -> Option<ast::Use> {
14    let parse = SourceFile::parse(source, Edition::Edition2024);
15
16    parse
17        .errors()
18        .is_empty()
19        .then(|| parse.tree())?
20        .items()
21        .find_map(|item| match item {
22            ast::Item::Use(item) => Some(item),
23            _ => None,
24        })
25}
26
27pub(super) fn type_name(ty: &ast::Type) -> Option<String> {
28    let ast::Type::PathType(path_type) = ty else {
29        return None;
30    };
31
32    path_type
33        .path()?
34        .segment()?
35        .name_ref()
36        .map(|name| name.text().to_string())
37}
38
39pub(super) fn is_inside_trait(function: &ast::Fn) -> bool {
40    function
41        .syntax()
42        .ancestors()
43        .skip(1)
44        .take_while(|node| ast::Fn::cast(node.clone()).is_none())
45        .any(|node| ast::Trait::cast(node).is_some())
46}
47
48pub(super) fn is_item_or_impl_fn(function: &ast::Fn) -> bool {
49    let Some(parent) = function.syntax().parent() else {
50        return false;
51    };
52
53    if ast::ExternItemList::cast(parent.clone()).is_some() {
54        return false;
55    }
56
57    let Some(items) = ast::AssocItemList::cast(parent) else {
58        return true;
59    };
60
61    items.syntax().parent().and_then(ast::Impl::cast).is_some()
62}
63
64pub(super) fn path_names(path: &ast::Path) -> Vec<String> {
65    let mut names = Vec::new();
66    let mut current = Some(path.clone());
67
68    while let Some(path) = current {
69        if let Some(name) = path.segment().and_then(|segment| segment.name_ref()) {
70            // #t(rust_alloc_in_loop) collected path segments must outlive the temporary syntax node
71            names.push(name.text().to_string());
72        }
73
74        current = path.qualifier();
75    }
76
77    names.reverse();
78
79    names
80}
81
82// #t(fn: rust_recursive_fn) composite types form a shallow syntax tree and need recursive size aggregation
83pub(super) fn estimate_type_size(ty: &ast::Type) -> Option<u64> {
84    match ty {
85        ast::Type::PathType(path) => estimate_path_size(path),
86        ast::Type::ArrayType(array) => {
87            let element = estimate_type_size(&array.ty()?)?;
88            let length = parse_int_expr(&array.const_arg()?.expr()?)?;
89
90            Some(element.saturating_mul(length))
91        }
92        ast::Type::TupleType(tuple) => tuple.fields().try_fold(0u64, |total, element| {
93            Some(total.saturating_add(estimate_type_size(&element)?))
94        }),
95        ast::Type::RefType(_) | ast::Type::PtrType(_) => Some(EIGHT_BYTES),
96        _ => None,
97    }
98}
99
100fn estimate_path_size(path: &ast::PathType) -> Option<u64> {
101    let name = path.path()?.segment()?.name_ref()?;
102
103    match name.text().as_str() {
104        "bool" | "u8" | "i8" => Some(ONE_BYTE),
105        "u16" | "i16" => Some(TWO_BYTES),
106        "u32" | "i32" | "f32" => Some(FOUR_BYTES),
107        "u64" | "i64" | "f64" | "usize" | "isize" | "Box" | "Arc" | "Rc" => Some(EIGHT_BYTES),
108        "u128" | "i128" => Some(SIXTEEN_BYTES),
109        "Vec" | "String" => Some(THREE_WORDS),
110        _ => None,
111    }
112}
113
114pub(super) fn parse_int_expr(expr: &ast::Expr) -> Option<u64> {
115    let ast::Expr::Literal(literal) = expr else {
116        return None;
117    };
118    let LiteralKind::IntNumber(number) = literal.kind() else {
119        return None;
120    };
121
122    number.value().ok()?.try_into().ok()
123}