Skip to main content

miniextendr_lint/rules/
impl_validation.rs

1//! Impl block validation: class system compatibility and label uniqueness.
2//!
3//! - MXL008: Trait impl class system incompatible with inherent impl.
4//! - MXL009: Multiple impl blocks for one type without labels.
5//! - MXL010: Duplicate labels on impl blocks for one type.
6
7use crate::crate_index::CrateIndex;
8use crate::diagnostic::Diagnostic;
9use crate::lint_code::LintCode;
10
11pub fn check(index: &CrateIndex, diagnostics: &mut Vec<Diagnostic>) {
12    for (path, data) in &index.file_data {
13        // MXL008: Class system compatibility
14        //
15        // S3 trait impls are compatible with ALL inherent styles because S3 generics
16        // dispatch on the class attribute, which every class system provides.
17        // S4 trait impls work on S4 inherent (needs proper S4 class with slots).
18        // S7/R6/Env trait impls require matching inherent for dispatch to work.
19        for ati in &data.attributed_trait_impls {
20            let trait_style = ati.class_system.as_deref().unwrap_or("env");
21
22            if let Some((inherent_style, _)) = data.inherent_impl_class_systems.get(&ati.type_name)
23            {
24                let inherent = if inherent_style.is_empty() {
25                    "env"
26                } else {
27                    inherent_style.as_str()
28                };
29
30                // S3 trait dispatch works on any class system's objects
31                let compatible = trait_style == inherent || trait_style == "s3";
32
33                if !compatible {
34                    diagnostics.push(Diagnostic::new(
35                        LintCode::MXL008,
36                        path,
37                        ati.line,
38                        format!(
39                            "#[miniextendr] impl {} for {} uses {}-style, but the inherent \
40                             impl uses {}-style. Trait and inherent impls must use the same \
41                             class system (S3 traits are compatible with all inherent styles). \
42                             Either change the trait impl to #[miniextendr({})] or change \
43                             the inherent impl to #[miniextendr({})].",
44                            ati.trait_name,
45                            ati.type_name,
46                            trait_style,
47                            inherent,
48                            inherent,
49                            trait_style,
50                        ),
51                    ));
52                }
53            }
54        }
55
56        // MXL009 + MXL010: Multiple impl blocks
57        for (type_name, impl_blocks) in &data.impl_blocks_per_type {
58            if impl_blocks.len() <= 1 {
59                continue;
60            }
61
62            // MXL009: Missing labels
63            let missing_labels: Vec<_> = impl_blocks
64                .iter()
65                .filter(|(label, _)| label.is_none())
66                .map(|(_, line)| *line)
67                .collect();
68
69            if !missing_labels.is_empty() {
70                diagnostics.push(Diagnostic::new(
71                    LintCode::MXL009,
72                    path,
73                    impl_blocks[0].1,
74                    format!(
75                        "type `{}` has {} impl blocks but some are missing labels. \
76                         When a type has multiple #[miniextendr] impl blocks, all must have \
77                         distinct labels using #[miniextendr(label = \"...\")]. \
78                         Unlabeled impl blocks at lines: {}",
79                        type_name,
80                        impl_blocks.len(),
81                        missing_labels
82                            .iter()
83                            .map(|l| l.to_string())
84                            .collect::<Vec<_>>()
85                            .join(", ")
86                    ),
87                ));
88            }
89
90            // MXL010: Duplicate labels
91            let mut seen_labels: std::collections::HashMap<&str, usize> =
92                std::collections::HashMap::new();
93            for (label, line) in impl_blocks {
94                if let Some(label) = label {
95                    if let Some(first_line) = seen_labels.get(label.as_str()) {
96                        diagnostics.push(Diagnostic::new(
97                            LintCode::MXL010,
98                            path,
99                            *line,
100                            format!(
101                                "duplicate label \"{}\" for type `{}`. \
102                                 First occurrence at line {}. Each impl block must have \
103                                 a unique label.",
104                                label, type_name, first_line
105                            ),
106                        ));
107                    } else {
108                        seen_labels.insert(label.as_str(), *line);
109                    }
110                }
111            }
112        }
113    }
114}