Skip to main content

wowlab_tidy/languages/rust/rules/style/
inherent_before_trait_impl.rs

1use ra_ap_syntax::{AstNode, ast};
2use wowlab_types::sim::FastMap;
3
4use crate::{AstCtx, Example, Violation};
5
6#[rustfmt::skip]
7const EXAMPLES: &[Example] = &[
8    Example {
9        label: "inherent impl first",
10        code: "struct Item;\nimpl Item { fn new() -> Self { Self } }\nimpl Default for Item { fn default() -> Self { Self } }",
11        pass: true,
12    },
13    Example {
14        label: "trait impl first",
15        code: "struct Item;\nimpl Default for Item { fn default() -> Self { Self } }\nimpl Item { fn new() -> Self { Self } }",
16        pass: false,
17    },
18    Example {
19        label: "different types",
20        code: "struct One; struct Two;\nimpl Default for One { fn default() -> Self { Self } }\nimpl Two {}",
21        pass: true,
22    },
23    Example {
24        label: "trait impl only",
25        code: "struct Item;\nimpl Default for Item { fn default() -> Self { Self } }",
26        pass: true,
27    },
28];
29
30crate::ast_rule!(
31    inherent_before_trait_impl,
32    "Require an inherent impl to precede trait impls for the same local type.",
33    "Putting the type's own API first makes its primary behavior easier to discover.",
34    Low,
35);
36
37// #t(fn: rust_alloc_in_loop) normalized self-type keys are owned by the cross-impl index
38fn check_inherent_before_trait_impl(ctx: &AstCtx<'_>) -> Vec<Violation> {
39    let mut first_inherent = FastMap::<String, usize>::default();
40    let mut trait_impls = Vec::new();
41
42    for item_impl in ctx.nodes::<ast::Impl>() {
43        let Some(self_type) = item_impl.self_ty() else {
44            continue;
45        };
46        let key: String = self_type
47            .syntax()
48            .text()
49            .to_string()
50            .chars()
51            .filter(|ch| !ch.is_whitespace())
52            .collect();
53        let offset: usize = item_impl.syntax().text_range().start().into();
54
55        if item_impl.trait_().is_none() {
56            first_inherent.entry(key).or_insert(offset);
57        } else {
58            trait_impls.push((key, offset, item_impl));
59        }
60    }
61
62    trait_impls
63        .into_iter()
64        .filter_map(|(key, offset, item_impl)| {
65            first_inherent
66                .get(&key)
67                .is_some_and(|inherent_offset| offset < *inherent_offset)
68                .then(|| {
69                    ctx.violation(
70                        &item_impl,
71                        "trait impl appears before the inherent impl for this type",
72                    )
73                })
74        })
75        .collect()
76}
77
78crate::tidy_ast_test!(check_inherent_before_trait_impl, {
79    crate::example_tests!(EXAMPLES, check_inherent_before_trait_impl);
80});