smelt_stdlib/category.rs
1//! Diagnostic categories shared across the TypeScript and Python frontends.
2//!
3//! The category is decided at the point a diagnostic is raised, where the
4//! frontend knows *why* lowering failed — a missing builtin, an unlowered
5//! construct, a typed-subset violation, and so on. Tooling (the library
6//! probes, a future LSP) consumes the category instead of re-deriving it by
7//! pattern-matching on human-readable message text.
8
9use serde::{Deserialize, Serialize};
10
11/// Coarse, machine-stable classification of a Smelt frontend diagnostic.
12///
13/// This is orthogonal to the per-site `code`/`message`: many distinct codes map
14/// to the same category. Reports group by category to answer "is this blocked
15/// on missing stdlib coverage or on an unimplemented lowering?".
16#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
17#[non_exhaustive]
18#[serde(rename_all = "kebab-case")]
19pub enum DiagnosticCategory {
20 /// Source text could not be parsed.
21 Parse,
22 /// A known JS/Python builtin or stdlib API that Smelt recognizes but has
23 /// not implemented lowering or runtime support for yet.
24 MissingStdlib,
25 /// A language construct Smelt does not lower into Rust yet.
26 UnsupportedLowering,
27 /// Source violates Smelt's strict typed-subset requirements, such as a
28 /// missing required type annotation. The fix is in the source, not Smelt.
29 TypeConstraint,
30 /// A name could not be resolved to any declaration or known builtin.
31 UnresolvedReference,
32 /// Internal invariant violation or a not-yet-classified diagnostic.
33 Internal,
34}
35
36impl DiagnosticCategory {
37 /// Stable kebab-case identifier, e.g. for JSON output or report grouping.
38 #[must_use]
39 pub const fn as_str(self) -> &'static str {
40 match self {
41 Self::Parse => "parse",
42 Self::MissingStdlib => "missing-stdlib",
43 Self::UnsupportedLowering => "unsupported-lowering",
44 Self::TypeConstraint => "type-constraint",
45 Self::UnresolvedReference => "unresolved-reference",
46 Self::Internal => "internal",
47 }
48 }
49
50 /// Short human-readable label for report tables.
51 #[must_use]
52 pub const fn label(self) -> &'static str {
53 match self {
54 Self::Parse => "parse error",
55 Self::MissingStdlib => "missing stdlib",
56 Self::UnsupportedLowering => "non-working Rust (unlowered)",
57 Self::TypeConstraint => "typed-subset violation",
58 Self::UnresolvedReference => "unresolved reference",
59 Self::Internal => "internal",
60 }
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 /// Every category round-trips through its stable string identifier.
69 #[test]
70 fn as_str_is_stable_kebab_case() {
71 assert_eq!(DiagnosticCategory::MissingStdlib.as_str(), "missing-stdlib");
72 assert_eq!(
73 DiagnosticCategory::UnsupportedLowering.as_str(),
74 "unsupported-lowering"
75 );
76 assert_eq!(DiagnosticCategory::Parse.as_str(), "parse");
77 }
78
79 /// Serde uses the same kebab-case spelling as `as_str`.
80 #[test]
81 fn serde_matches_as_str() -> Result<(), serde_json::Error> {
82 let json = serde_json::to_string(&DiagnosticCategory::TypeConstraint)?;
83 assert_eq!(json, "\"type-constraint\"");
84 let back: DiagnosticCategory = serde_json::from_str(&json)?;
85 assert_eq!(back, DiagnosticCategory::TypeConstraint);
86 Ok(())
87 }
88}