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