Skip to main content

plc_diagnostics/diagnostics/
diagnostics_registry.rs

1/// Returns a diagnostics map with the error code, default severity and a description
2use lazy_static::lazy_static;
3use rustc_hash::FxHashMap;
4use serde::{Deserialize, Serialize};
5
6use crate::diagnostician::DiagnosticAssessor;
7
8use super::{
9    Diagnostic,
10    Severity::{self, Error, Ignore, Info, Warning},
11};
12
13macro_rules! add_diagnostic {
14    ($($number:ident, $severity:expr, $desc:expr,)*) => {
15        { let mut m : FxHashMap<&str, DiagnosticEntry> = FxHashMap::default();
16            $(
17                {
18                    let code = stringify!($number);
19                    m.insert(code, DiagnosticEntry{ code, severity: $severity, description: $desc});
20                }
21            )*
22            m
23        }}
24}
25
26pub struct DiagnosticsRegistry(FxHashMap<&'static str, DiagnosticEntry>);
27
28#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
29pub struct DiagnosticEntry {
30    code: &'static str,
31    severity: Severity,
32    description: &'static str,
33}
34
35impl Default for DiagnosticsRegistry {
36    fn default() -> Self {
37        Self::new(DIAGNOSTICS.clone())
38    }
39}
40
41impl DiagnosticsRegistry {
42    /// Creates an empty registry.
43    fn new(map: FxHashMap<&'static str, DiagnosticEntry>) -> Self {
44        DiagnosticsRegistry(map)
45    }
46
47    pub fn with_configuration(mut self, config: DiagnosticsConfiguration) -> Self {
48        for (severity, codes) in config.0 {
49            for code in &codes {
50                if let Some(entry) = self.0.get_mut(code.as_str()) {
51                    entry.severity = severity
52                }
53            }
54        }
55        self
56    }
57}
58
59impl DiagnosticAssessor for DiagnosticsRegistry {
60    /// Assesses the diagnostic based on the current registered map. If no entry is found, the
61    /// default diagnostic is returned
62    fn assess(&self, d: &Diagnostic) -> Severity {
63        self.0.get(d.get_error_code()).map(|it| it.severity).unwrap_or_else(|| {
64            log::warn!("Unrecognized error code {}. Using default severity", d.get_error_code());
65            Severity::default()
66        })
67    }
68
69    fn explain(&self, error: &str) -> String {
70        let Some(info) = self.0.get(error) else {
71            return format!("Unknown error {error}");
72        };
73        format!(
74            r"Explanation for error {error}:
75{}
76",
77            info.description
78        )
79    }
80
81    fn get_diagnostic_configuration(&self) -> String {
82        let config: DiagnosticsConfiguration = self.into();
83        serde_json::ser::to_string(&config).expect("Cannot fail")
84    }
85}
86
87#[derive(Serialize, Deserialize, PartialEq, Eq, Default)]
88#[serde(transparent)]
89pub struct DiagnosticsConfiguration(FxHashMap<Severity, Vec<String>>);
90
91impl From<&DiagnosticsRegistry> for DiagnosticsConfiguration {
92    fn from(registry: &DiagnosticsRegistry) -> Self {
93        let mut res = DiagnosticsConfiguration::default();
94        for val in registry.0.values() {
95            let entry = res.0.entry(val.severity).or_default();
96            entry.push(val.code.into());
97        }
98        res
99    }
100}
101
102#[rustfmt::skip]
103lazy_static! {
104    pub static ref DIAGNOSTICS: FxHashMap<&'static str, DiagnosticEntry> = add_diagnostic!(
105        E001,   Error,      include_str!("./error_codes/E001.md"), // General Error
106        E002,   Error,      include_str!("./error_codes/E002.md"), // General IO Error
107        E003,   Error,      include_str!("./error_codes/E003.md"), // Parameter Error
108        E004,   Error,      include_str!("./error_codes/E004.md"), // Duplicate Symbol
109        E005,   Error,      include_str!("./error_codes/E005.md"), // Generic LLVM Error
110        E006,   Error,      include_str!("./error_codes/E006.md"), // Missing Token
111        E007,   Error,      include_str!("./error_codes/E007.md"), // Unexpected Token
112        E008,   Error,      include_str!("./error_codes/E008.md"), // Invalid Range
113        E009,   Error,      include_str!("./error_codes/E009.md"), // Mismatched Parantheses
114        E010,   Error,      include_str!("./error_codes/E010.md"), // Invalid Time Literal
115        E011,   Error,      include_str!("./error_codes/E011.md"), // Invalid Number
116        E012,   Error,      include_str!("./error_codes/E012.md"), // Missing Case Condition
117        E013,   Warning,    include_str!("./error_codes/E013.md"), // Keywords shoud contain underscores
118        E014,   Warning,    include_str!("./error_codes/E014.md"), // Wrong parantheses type
119        E015,   Ignore,     include_str!("./error_codes/E015.md"), // Pointer is not standard; ignored due to internal feedback it being too noisy
120        E016,   Warning,    include_str!("./error_codes/E016.md"), // Return types cannot have a default value
121        E017,   Error,      include_str!("./error_codes/E017.md"), // Classes cannot contain implementations
122        E018,   Error,      include_str!("./error_codes/E018.md"), // Duplicate Label
123        E019,   Error,      include_str!("./error_codes/E019.md"), // Classes cannot contain IN_OUT variables
124        E020,   Error,      include_str!("./error_codes/E020.md"), // Classes cannot contain a return type
125        E021,   Error,      include_str!("./error_codes/E021.md"), // Re-declaration of variable
126        E022,   Warning,    include_str!("./error_codes/E022.md"), // Missing action container
127        E023,   Warning,    include_str!("./error_codes/E023.md"), // Statement with no effect
128        E024,   Warning,    include_str!("./error_codes/E024.md"), // Invalid pragma location
129        E025,   Error,      include_str!("./error_codes/E025.md"), // Missing return type
130        E026,   Error,      include_str!("./error_codes/E026.md"),
131        E027,   Error,      include_str!("./error_codes/E027.md"),
132        E028,   Error,      include_str!("./error_codes/E028.md"),
133        E029,   Error,      include_str!("./error_codes/E029.md"),
134        E030,   Error,      include_str!("./error_codes/E030.md"),
135        E031,   Error,      include_str!("./error_codes/E031.md"),
136        E032,   Error,      include_str!("./error_codes/E032.md"),
137        E033,   Error,      include_str!("./error_codes/E033.md"),
138        E034,   Error,      include_str!("./error_codes/E034.md"),
139        E035,   Error,      include_str!("./error_codes/E035.md"),
140        E036,   Error,      include_str!("./error_codes/E036.md"),
141        E037,   Error,      include_str!("./error_codes/E037.md"),
142        E038,   Error,      include_str!("./error_codes/E038.md"), // Missing type
143        E039,   Warning,    include_str!("./error_codes/E039.md"),
144        E040,   Warning,    include_str!("./error_codes/E040.md"),
145        E041,   Error,      include_str!("./error_codes/E041.md"),
146        E042,   Warning,    include_str!("./error_codes/E042.md"), // TODO: Use me, I'm free
147        E043,   Error,      include_str!("./error_codes/E043.md"),
148        E044,   Error,      include_str!("./error_codes/E044.md"),
149        E045,   Error,      include_str!("./error_codes/E045.md"),
150        E046,   Error,      include_str!("./error_codes/E046.md"),
151        E047,   Warning,    include_str!("./error_codes/E047.md"), // VLAs are always by reference
152        E048,   Error,      include_str!("./error_codes/E048.md"),
153        E049,   Warning,    include_str!("./error_codes/E049.md"),
154        E050,   Error,      include_str!("./error_codes/E050.md"),
155        E051,   Error,      include_str!("./error_codes/E051.md"),
156        E052,   Error,      include_str!("./error_codes/E052.md"),
157        E053,   Error,      include_str!("./error_codes/E053.md"),
158        E054,   Error,      include_str!("./error_codes/E054.md"),
159        E055,   Error,      include_str!("./error_codes/E055.md"),
160        E056,   Error,      include_str!("./error_codes/E056.md"),
161        E057,   Error,      include_str!("./error_codes/E057.md"),
162        E058,   Error,      include_str!("./error_codes/E058.md"),
163        E059,   Error,      include_str!("./error_codes/E059.md"),
164        E060,   Info,       include_str!("./error_codes/E060.md"), // Variable direct access with %
165        E061,   Error,      include_str!("./error_codes/E061.md"),
166        E062,   Error,      include_str!("./error_codes/E062.md"),
167        E063,   Error,      include_str!("./error_codes/E063.md"),
168        E064,   Error,      include_str!("./error_codes/E064.md"),
169        E065,   Error,      include_str!("./error_codes/E065.md"),
170        E066,   Error,      include_str!("./error_codes/E066.md"),
171        E067,   Warning,    include_str!("./error_codes/E067.md"), // Implicit typecast
172        E068,   Error,      include_str!("./error_codes/E068.md"),
173        E069,   Error,      include_str!("./error_codes/E069.md"),
174        E070,   Error,      include_str!("./error_codes/E070.md"),
175        E071,   Error,      include_str!("./error_codes/E071.md"),
176        E072,   Error,      include_str!("./error_codes/E072.md"),
177        E073,   Error,      include_str!("./error_codes/E073.md"),
178        E074,   Error,      include_str!("./error_codes/E074.md"),
179        E075,   Error,      include_str!("./error_codes/E075.md"),
180        E076,   Error,      include_str!("./error_codes/E076.md"),
181        E077,   Error,      include_str!("./error_codes/E077.md"),
182        E078,   Error,      include_str!("./error_codes/E078.md"),
183        E079,   Error,      include_str!("./error_codes/E079.md"),
184        E080,   Error,      include_str!("./error_codes/E080.md"),
185        E081,   Error,      include_str!("./error_codes/E081.md"),   // Duplicate CFC connector
186        E082,   Error,      include_str!("./error_codes/E082.md"),   // Dangling CFC continuation
187        E083,   Error,      include_str!("./error_codes/E083.md"),   // Unsupported CFC expression
188        E084,   Warning,    include_str!("./error_codes/E084.md"),   // Unconnected CFC element
189        E085,   Error,      include_str!("./error_codes/E085.md"),   // Disconnected CFC return
190        E086,   Error,      include_str!("./error_codes/E086.md"),   // Open CFC connector
191        E087,   Error,      include_str!("./error_codes/E087.md"),
192        E088,   Error,      include_str!("./error_codes/E088.md"),
193        E089,   Error,      include_str!("./error_codes/E089.md"),
194        E090,   Warning,    include_str!("./error_codes/E090.md"),  // Incompatible reference Assignment
195        E091,   Warning,    include_str!("./error_codes/E091.md"),
196        E092,   Info,       include_str!("./error_codes/E092.md"),
197        E093,   Warning,    include_str!("./error_codes/E093.md"),
198        E094,   Error,      include_str!("./error_codes/E094.md"),
199        E095,   Error,      include_str!("./error_codes/E095.md"),  // Action call without `()`
200        E096,   Warning,    include_str!("./error_codes/E096.md"),  // Integer Condition
201        E097,   Error,      include_str!("./error_codes/E097.md"),  // Invalid Array Range
202        E098,   Error,      include_str!("./error_codes/E098.md"),  // Invalid `REF=` assignment
203        E099,   Error,      include_str!("./error_codes/E099.md"),  // Invalid `REFERENCE TO` declaration
204        E100,   Error,      include_str!("./error_codes/E100.md"),  // Immutable variable address
205        E101,   Error,      include_str!("./error_codes/E101.md"),  // Invalid VAR_CONFIG / Template Variable Declaration
206        E102,   Error,      include_str!("./error_codes/E102.md"),  // Template variable without hardware binding
207        E103,   Error,      include_str!("./error_codes/E103.md"),  // Immutable Hardware Binding
208        E104,   Error,      include_str!("./error_codes/E104.md"),  // Config Variable With Incomplete Address
209        E105,   Error,      include_str!("./error_codes/E105.md"),  // CONSTANT keyword in POU
210        E106,   Warning,    include_str!("./error_codes/E106.md"),  // VAR_EXTERNAL have no effect
211        E107,   Error,      include_str!("./error_codes/E107.md"),  // Missing configuration for template variable
212        E108,   Error,      include_str!("./error_codes/E108.md"),  // Template variable is configured multiple times
213        E109,   Error,      include_str!("./error_codes/E109.md"),  // Stateful pointer variable initialized with temporary value
214        E110,   Error,      include_str!("./error_codes/E110.md"),  // Invalid POU Type for Interface Implementation
215        E111,   Error,      include_str!("./error_codes/E111.md"),  // Duplicate interface methods with different signatures
216        E112,   Error,      include_str!("./error_codes/E112.md"),  // Incomplete interface implementation
217        E113,   Error,      include_str!("./error_codes/E113.md"),  // Interface default method implementation
218        E114,   Error,      include_str!("./error_codes/E114.md"),  // Multiple extensions of same POU
219        E115,   Error,      include_str!("./error_codes/E115.md"),  // Property in unsupported POU type
220        E116,   Error,      include_str!("./error_codes/E116.md"),  // Property defined in unsupported variable block
221        E118,   Info,       include_str!("./error_codes/E118.md"),  // Follow-up error to 112
222        E119,   Error,      include_str!("./error_codes/E119.md"),  // Invalid use of `SUPER` keyword
223        E120,   Error,      include_str!("./error_codes/E120.md"),  // Invalid use of `THIS` keyword
224        E121,   Error,      include_str!("./error_codes/E121.md"),  // Recursive type alias
225        E122,   Error,      include_str!("./error_codes/E122.md"),  // Invalid enum base type
226        E123,   Error,      include_str!("./error_codes/E123.md"),  // Division by zero
227        E124,   Error,      include_str!("./error_codes/E124.md"),  // Invalid escape sequence in string literal
228        E125,   Error,      include_str!("./error_codes/E125.md"),  // Incompatible POINTER TO types in class/FB hierarchy
229        E126,   Error,      include_str!("./error_codes/E126.md"),  // Incompatible types in interface polymorphism
230        E127,   Warning,    include_str!("./error_codes/E127.md"),  // Array initialized with fewer elements than expected
231        E128,   Error,      include_str!("./error_codes/E128.md"),  // Invalid nested property assignment target
232        E129,   Error,      include_str!("./error_codes/E129.md"),  // Direct interface reference call
233        E130,   Error,      include_str!("./error_codes/E130.md"),  // Array size exceeds supported limit
234        E117,   Error,      include_str!("./error_codes/E117.md"),  // Non-constant array boundary
235        E131,   Error,      include_str!("./error_codes/E131.md"),  // Positional argument collides with later named argument
236        E132,   Ignore,    include_str!("./error_codes/E132.md"),  // Mixing implicit and explicit call parameters
237        E133,   Error,      include_str!("./error_codes/E133.md"),  // AND_THEN/OR_ELSE with non-boolean operands
238        E134,   Error,      include_str!("./error_codes/E134.md"),  // Invalid --hwmap-file argument
239        E135,   Warning,    include_str!("./error_codes/E135.md"),  // '=>' used for a non-output parameter
240        E136,   Error,      include_str!("./error_codes/E136.md"),  // Incomplete hardware address in FUNCTION/METHOD
241        E137,   Error,      include_str!("./error_codes/E137.md"),  // FB-level VAR_TEMP referenced from a METHOD
242        E138,   Error,      include_str!("./error_codes/E138.md"),  // Reserved keyword used as a name
243        E139,   Error,      include_str!("./error_codes/E139.md"),  // Linker invocation failed (spawn / cmdline)
244        E140,   Error,      include_str!("./error_codes/E140.md"),  // ':=' used for an output parameter
245        E141,   Error,      include_str!("./error_codes/E141.md"),  // Member access on a non-auto-deref pointer base
246        E142,   Error,      include_str!("./error_codes/E142.md"),  // Undefined CFC jump target
247        E143,   Warning,    include_str!("./error_codes/E143.md"),  // Unused CFC label
248        E144,   Error,      include_str!("./error_codes/E144.md"),  // Duplicate CFC label
249        E145,   Warning,    include_str!("./error_codes/E145.md"),  // Disconnected CFC jump
250        E146,   Error,      include_str!("./error_codes/E146.md"),  // Unknown CFC block type
251        E147,   Error,      include_str!("./error_codes/E147.md"),  // Undeclared CFC block output
252        E148,   Warning,    include_str!("./error_codes/E148.md"),  // Temporal literal overflow or underflow
253        E149,   Error,      include_str!("./error_codes/E149.md"),  // Unresolved generic CFC block output
254        E150,   Warning,    include_str!("./error_codes/E150.md"),  // ABS on an unsigned value has no effect
255        E151,   Error,      include_str!("./error_codes/E151.md"),  // Unary NOT with unsupported operand type
256        E152,   Error,      include_str!("./error_codes/E152.md"),  // Unconnected CFC EN pin
257        E153,   Error,      include_str!("./error_codes/E153.md"),  // CFC ENO cycle
258        E154,   Error,      include_str!("./error_codes/E154.md"),  // Negated CFC reference assignment
259        E155,   Error,      include_str!("./error_codes/E155.md"),  // Duplicate CFC return pin
260    );
261}
262
263#[cfg(test)]
264mod tests {
265    use crate::{
266        diagnostician::DiagnosticAssessor,
267        diagnostics::{diagnostics_registry::DiagnosticsConfiguration, Diagnostic, Severity},
268    };
269
270    use super::DiagnosticsRegistry;
271
272    #[test]
273    fn deserialize_empty_json() {
274        let error_config = "{}";
275
276        let DiagnosticsConfiguration(configuration) = serde_json::de::from_str(error_config).unwrap();
277        assert!(configuration.is_empty());
278    }
279
280    #[test]
281    fn deserialize_json() {
282        let error_config = r#"{
283            "error": ["E001", "E002"],
284            "warning": ["E003", "E004"],
285            "info": ["E005"],
286            "ignore": ["E010"]
287        }"#;
288
289        let DiagnosticsConfiguration(configuration) = serde_json::de::from_str(error_config).unwrap();
290        assert_eq!(configuration.len(), 4);
291        assert_eq!(configuration.get(&Severity::Error).unwrap(), &["E001", "E002"]);
292        assert_eq!(configuration.get(&Severity::Warning).unwrap(), &["E003", "E004"]);
293        assert_eq!(configuration.get(&Severity::Info).unwrap(), &["E005"]);
294        assert_eq!(configuration.get(&Severity::Ignore).unwrap(), &["E010"]);
295    }
296
297    #[test]
298    fn overridden_errors_are_assessed_correctly() {
299        let error_config = r#"{
300            "error": ["E090"],
301            "warning": ["E001"],
302            "info": ["E002"],
303            "ignore": ["E003"]
304        }"#;
305
306        let configuration = serde_json::de::from_str(error_config).unwrap();
307        let diagnostics_registry = DiagnosticsRegistry::default().with_configuration(configuration);
308
309        let e090 = Diagnostic::new("Warning->Error").with_error_code("E090");
310        let e001 = Diagnostic::new("Error->Warning").with_error_code("E001");
311        let e002 = Diagnostic::new("Error->Info").with_error_code("E002");
312        let e003 = Diagnostic::new("Error->Ignore").with_error_code("E003");
313
314        assert_eq!(diagnostics_registry.assess(&e090), Severity::Error);
315        assert_eq!(diagnostics_registry.assess(&e001), Severity::Warning);
316        assert_eq!(diagnostics_registry.assess(&e002), Severity::Info);
317        assert_eq!(diagnostics_registry.assess(&e003), Severity::Ignore);
318    }
319}