Skip to main content

leekscript_analysis/
error.rs

1//! Semantic error codes and helpers for analysis diagnostics.
2//!
3//! Aligned with leekscript-java's `leekscript.common.Error` where applicable.
4//!
5//! ## Mapping to `LeekScript` Java compiler (Error enum)
6//!
7//! | AnalysisError variant              | Java constant                     | Code |
8//! |------------------------------------|-----------------------------------|------|
9//! | `BreakOutOfLoop`                   | `BREAK_OUT_OF_LOOP`              | E012 |
10//! | `ContinueOutOfLoop`                | `CONTINUE_OUT_OF_LOOP`           | E013 |
11//! | `IncludeOnlyInMainBlock`           | `INCLUDE_ONLY_IN_MAIN_BLOCK`     | E014 |
12//! | `FunctionOnlyInMainBlock`         | `FUNCTION_ONLY_IN_MAIN_BLOCK`    | E019 |
13//! | `VariableNameUnavailable`         | `VARIABLE_NAME_UNAVAILABLE`      | E021 |
14//! | `GlobalOnlyInMainBlock`            | `GLOBAL_ONLY_IN_MAIN_BLOCK`      | E027 |
15//! | `UnknownVariableOrFunction`        | `UNKNOWN_VARIABLE_OR_FUNCTION`   | E033 |
16//! | `DuplicateClassName`              | duplicate class                  | E034 |
17//! | `DuplicateFunctionName`           | duplicate function               | E035 |
18//! | `WrongArity`                       | wrong argument count             | E036 |
19//! | `TypeMismatch`                     | type mismatch / invalid cast     | E037 |
20//! | `OptionalParamsOnlyInStandard...` | optional params in user function | E038 |
21
22use sipha::error::{RelatedLocation, SemanticDiagnostic};
23use sipha::types::Span;
24
25/// Error codes for semantic validation (mirroring leekscript-java Error enum).
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum AnalysisError {
29    /// Unknown variable or function name (Java: `UNKNOWN_VARIABLE_OR_FUNCTION`, 33).
30    UnknownVariableOrFunction,
31    /// Break outside of loop (Java: `BREAK_OUT_OF_LOOP`, 12).
32    BreakOutOfLoop,
33    /// Continue outside of loop (Java: `CONTINUE_OUT_OF_LOOP`, 13).
34    ContinueOutOfLoop,
35    /// Variable/name already declared in this scope (Java: `VARIABLE_NAME_UNAVAILABLE`, 21).
36    VariableNameUnavailable,
37    /// Include only allowed in main block (Java: `INCLUDE_ONLY_IN_MAIN_BLOCK`, 14).
38    IncludeOnlyInMainBlock,
39    /// Function only in main block (Java: `FUNCTION_ONLY_IN_MAIN_BLOCK`, 19).
40    FunctionOnlyInMainBlock,
41    /// Global only in main block (Java: `GLOBAL_ONLY_IN_MAIN_BLOCK`, 27).
42    GlobalOnlyInMainBlock,
43    /// Duplicate class name in main scope (Java: duplicate class).
44    DuplicateClassName,
45    /// Duplicate function name in main scope (Java: duplicate function).
46    DuplicateFunctionName,
47    /// Optional/default parameters only allowed in standard functions or methods, not in user-defined top-level functions.
48    OptionalParamsOnlyInStandardFunctionsOrMethods,
49    /// Function call argument count does not match declaration (arity).
50    WrongArity,
51    /// Type mismatch (e.g. assignment or argument).
52    TypeMismatch,
53}
54
55impl AnalysisError {
56    /// Short code for diagnostics (e.g. E033).
57    #[must_use]
58    pub fn code(self) -> &'static str {
59        match self {
60            Self::UnknownVariableOrFunction => "E033",
61            Self::BreakOutOfLoop => "E012",
62            Self::ContinueOutOfLoop => "E013",
63            Self::VariableNameUnavailable => "E021",
64            Self::IncludeOnlyInMainBlock => "E014",
65            Self::FunctionOnlyInMainBlock => "E019",
66            Self::GlobalOnlyInMainBlock => "E027",
67            Self::DuplicateClassName => "E034",
68            Self::DuplicateFunctionName => "E035",
69            Self::OptionalParamsOnlyInStandardFunctionsOrMethods => "E038",
70            Self::WrongArity => "E036",
71            Self::TypeMismatch => "E037",
72        }
73    }
74
75    /// Human-readable message.
76    #[must_use]
77    pub fn message(self) -> &'static str {
78        match self {
79            Self::UnknownVariableOrFunction => "unknown variable or function",
80            Self::BreakOutOfLoop => "break outside of loop",
81            Self::ContinueOutOfLoop => "continue outside of loop",
82            Self::VariableNameUnavailable => "variable name already used in this scope",
83            Self::IncludeOnlyInMainBlock => "include only allowed in main block",
84            Self::FunctionOnlyInMainBlock => "function declaration only allowed in main block",
85            Self::GlobalOnlyInMainBlock => "global declaration only allowed in main block",
86            Self::DuplicateClassName => "duplicate class name",
87            Self::DuplicateFunctionName => "duplicate function name",
88            Self::OptionalParamsOnlyInStandardFunctionsOrMethods => {
89                "optional/default parameters only allowed in standard functions or methods, not in user-defined functions"
90            }
91            Self::WrongArity => "wrong number of arguments for function call",
92            Self::TypeMismatch => "type mismatch",
93        }
94    }
95
96    /// Build a semantic diagnostic for this error at the given span.
97    #[must_use]
98    pub fn at(self, span: Span) -> SemanticDiagnostic {
99        SemanticDiagnostic::error(span, self.message()).with_code(self.code())
100    }
101
102    /// Build a semantic diagnostic with related locations (e.g. "first declared here").
103    #[must_use]
104    pub fn at_with_related(
105        self,
106        span: Span,
107        related: Vec<(Span, &'static str)>,
108    ) -> SemanticDiagnostic {
109        let related_locations = related
110            .into_iter()
111            .map(|(span, message)| RelatedLocation {
112                span,
113                message: message.to_string(),
114            })
115            .collect();
116        SemanticDiagnostic::error(span, self.message())
117            .with_code(self.code())
118            .with_related(related_locations)
119    }
120}
121
122/// Build a wrong-arity diagnostic with expected vs actual counts.
123pub fn wrong_arity_at(span: Span, expected: usize, actual: usize) -> SemanticDiagnostic {
124    let message = format!("wrong number of arguments (expected {expected}, got {actual})");
125    SemanticDiagnostic::error(span, message).with_code(AnalysisError::WrongArity.code())
126}
127
128/// Build a type mismatch diagnostic (expected vs got).
129pub fn type_mismatch_at(span: Span, expected: &str, got: &str) -> SemanticDiagnostic {
130    let message = format!("type mismatch: expected {expected}, got {got}");
131    SemanticDiagnostic::error(span, message).with_code(AnalysisError::TypeMismatch.code())
132}
133
134/// Build an invalid cast diagnostic.
135pub fn invalid_cast_at(span: Span, from: &str, to: &str) -> SemanticDiagnostic {
136    let message = format!("invalid cast from {from} to {to}");
137    SemanticDiagnostic::error(span, message).with_code(AnalysisError::TypeMismatch.code())
138}