Skip to main content

cel_core/checker/
errors.rs

1//! Error types for type checking.
2//!
3//! This module provides structured error types for reporting type check failures.
4
5use crate::types::{CelType, Span};
6use std::fmt;
7
8/// A type checking error.
9#[derive(Debug, Clone)]
10pub struct CheckError {
11    /// The kind of error.
12    pub kind: CheckErrorKind,
13    /// The source span where the error occurred.
14    pub span: Span,
15    /// The expression ID where the error occurred.
16    pub expr_id: i64,
17}
18
19impl CheckError {
20    /// Create a new check error.
21    pub fn new(kind: CheckErrorKind, span: Span, expr_id: i64) -> Self {
22        Self {
23            kind,
24            span,
25            expr_id,
26        }
27    }
28
29    /// Create an undeclared reference error.
30    pub fn undeclared_reference(name: &str, span: Span, expr_id: i64) -> Self {
31        Self::new(
32            CheckErrorKind::UndeclaredReference {
33                container: String::new(),
34                name: name.to_string(),
35            },
36            span,
37            expr_id,
38        )
39    }
40
41    /// Create an undeclared reference error with container.
42    pub fn undeclared_reference_in(container: &str, name: &str, span: Span, expr_id: i64) -> Self {
43        Self::new(
44            CheckErrorKind::UndeclaredReference {
45                container: container.to_string(),
46                name: name.to_string(),
47            },
48            span,
49            expr_id,
50        )
51    }
52
53    /// Create a no matching overload error.
54    pub fn no_matching_overload(
55        function: &str,
56        arg_types: Vec<CelType>,
57        span: Span,
58        expr_id: i64,
59    ) -> Self {
60        Self::new(
61            CheckErrorKind::NoMatchingOverload {
62                function: function.to_string(),
63                arg_types,
64            },
65            span,
66            expr_id,
67        )
68    }
69
70    /// Create a type mismatch error.
71    pub fn type_mismatch(expected: CelType, actual: CelType, span: Span, expr_id: i64) -> Self {
72        Self::new(
73            CheckErrorKind::TypeMismatch { expected, actual },
74            span,
75            expr_id,
76        )
77    }
78
79    /// Create an undefined field error.
80    pub fn undefined_field(type_name: &str, field: &str, span: Span, expr_id: i64) -> Self {
81        Self::new(
82            CheckErrorKind::UndefinedField {
83                type_name: type_name.to_string(),
84                field: field.to_string(),
85            },
86            span,
87            expr_id,
88        )
89    }
90
91    /// Get the error message.
92    pub fn message(&self) -> String {
93        self.kind.to_string()
94    }
95}
96
97impl fmt::Display for CheckError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        write!(f, "{}", self.kind)
100    }
101}
102
103impl std::error::Error for CheckError {}
104
105/// The kind of type checking error.
106#[derive(Debug, Clone)]
107pub enum CheckErrorKind {
108    /// Reference to an undeclared variable or function.
109    UndeclaredReference {
110        /// Container namespace (empty if none).
111        container: String,
112        /// The name that was not found.
113        name: String,
114    },
115
116    /// No function overload matches the provided arguments.
117    NoMatchingOverload {
118        /// The function name.
119        function: String,
120        /// The types of arguments provided.
121        arg_types: Vec<CelType>,
122    },
123
124    /// Type mismatch between expected and actual types.
125    TypeMismatch {
126        /// The expected type.
127        expected: CelType,
128        /// The actual type found.
129        actual: CelType,
130    },
131
132    /// Field not found on a type.
133    UndefinedField {
134        /// The type name.
135        type_name: String,
136        /// The field that was not found.
137        field: String,
138    },
139
140    /// Type is not assignable to another type.
141    NotAssignable {
142        /// The source type.
143        from: CelType,
144        /// The target type.
145        to: CelType,
146    },
147
148    /// Aggregate literal contains heterogeneous types.
149    HeterogeneousAggregate {
150        /// The types found in the aggregate.
151        types: Vec<CelType>,
152    },
153
154    /// Expression cannot be used as a type.
155    NotAType {
156        /// The expression text (for error messages).
157        expr: String,
158    },
159
160    /// General type error with custom message.
161    Other(String),
162}
163
164impl fmt::Display for CheckErrorKind {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        match self {
167            CheckErrorKind::UndeclaredReference { container, name } => {
168                if container.is_empty() {
169                    write!(f, "undeclared reference to '{}'", name)
170                } else {
171                    write!(
172                        f,
173                        "undeclared reference to '{}' in container '{}'",
174                        name, container
175                    )
176                }
177            }
178            CheckErrorKind::NoMatchingOverload {
179                function,
180                arg_types,
181            } => {
182                let types: Vec<_> = arg_types.iter().map(|t| t.display_name()).collect();
183                write!(
184                    f,
185                    "no matching overload for '{}' with argument types ({})",
186                    function,
187                    types.join(", ")
188                )
189            }
190            CheckErrorKind::TypeMismatch { expected, actual } => {
191                write!(
192                    f,
193                    "expected type '{}' but found '{}'",
194                    expected.display_name(),
195                    actual.display_name()
196                )
197            }
198            CheckErrorKind::UndefinedField { type_name, field } => {
199                write!(f, "undefined field '{}' on type '{}'", field, type_name)
200            }
201            CheckErrorKind::NotAssignable { from, to } => {
202                write!(
203                    f,
204                    "type '{}' is not assignable to '{}'",
205                    from.display_name(),
206                    to.display_name()
207                )
208            }
209            CheckErrorKind::HeterogeneousAggregate { types } => {
210                let type_names: Vec<_> = types.iter().map(|t| t.display_name()).collect();
211                write!(
212                    f,
213                    "aggregate literal contains heterogeneous types: {}",
214                    type_names.join(", ")
215                )
216            }
217            CheckErrorKind::NotAType { expr } => {
218                write!(f, "expression '{}' is not a type", expr)
219            }
220            CheckErrorKind::Other(msg) => write!(f, "{}", msg),
221        }
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_undeclared_reference() {
231        let err = CheckError::undeclared_reference("foo", 0..3, 1);
232        assert!(err.message().contains("undeclared reference"));
233        assert!(err.message().contains("foo"));
234    }
235
236    #[test]
237    fn test_no_matching_overload() {
238        let err =
239            CheckError::no_matching_overload("_+_", vec![CelType::String, CelType::Int], 0..5, 1);
240        assert!(err.message().contains("no matching overload"));
241        assert!(err.message().contains("_+_"));
242    }
243
244    #[test]
245    fn test_type_mismatch() {
246        let err = CheckError::type_mismatch(CelType::Bool, CelType::Int, 0..1, 1);
247        assert!(err.message().contains("expected"));
248        assert!(err.message().contains("bool"));
249        assert!(err.message().contains("int"));
250    }
251
252    #[test]
253    fn test_undefined_field() {
254        let err = CheckError::undefined_field("MyMessage", "unknown", 0..7, 1);
255        assert!(err.message().contains("undefined field"));
256        assert!(err.message().contains("unknown"));
257        assert!(err.message().contains("MyMessage"));
258    }
259}