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    /// Create an error with a custom message.
92    pub fn other(msg: impl Into<String>, span: Span, expr_id: i64) -> Self {
93        Self::new(CheckErrorKind::Other(msg.into()), span, expr_id)
94    }
95
96    /// Get the error message.
97    pub fn message(&self) -> String {
98        self.kind.to_string()
99    }
100}
101
102impl fmt::Display for CheckError {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(f, "{}", self.kind)
105    }
106}
107
108impl std::error::Error for CheckError {}
109
110/// The kind of type checking error.
111#[derive(Debug, Clone)]
112pub enum CheckErrorKind {
113    /// Reference to an undeclared variable or function.
114    UndeclaredReference {
115        /// Container namespace (empty if none).
116        container: String,
117        /// The name that was not found.
118        name: String,
119    },
120
121    /// No function overload matches the provided arguments.
122    NoMatchingOverload {
123        /// The function name.
124        function: String,
125        /// The types of arguments provided.
126        arg_types: Vec<CelType>,
127    },
128
129    /// Type mismatch between expected and actual types.
130    TypeMismatch {
131        /// The expected type.
132        expected: CelType,
133        /// The actual type found.
134        actual: CelType,
135    },
136
137    /// Field not found on a type.
138    UndefinedField {
139        /// The type name.
140        type_name: String,
141        /// The field that was not found.
142        field: String,
143    },
144
145    /// Type is not assignable to another type.
146    NotAssignable {
147        /// The source type.
148        from: CelType,
149        /// The target type.
150        to: CelType,
151    },
152
153    /// Aggregate literal contains heterogeneous types.
154    HeterogeneousAggregate {
155        /// The types found in the aggregate.
156        types: Vec<CelType>,
157    },
158
159    /// Expression cannot be used as a type.
160    NotAType {
161        /// The expression text (for error messages).
162        expr: String,
163    },
164
165    /// General type error with custom message.
166    Other(String),
167}
168
169impl fmt::Display for CheckErrorKind {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        match self {
172            CheckErrorKind::UndeclaredReference { container, name } => {
173                if container.is_empty() {
174                    write!(f, "undeclared reference to '{}'", name)
175                } else {
176                    write!(
177                        f,
178                        "undeclared reference to '{}' in container '{}'",
179                        name, container
180                    )
181                }
182            }
183            CheckErrorKind::NoMatchingOverload {
184                function,
185                arg_types,
186            } => {
187                let types: Vec<_> = arg_types.iter().map(|t| t.display_name()).collect();
188                write!(
189                    f,
190                    "no matching overload for '{}' with argument types ({})",
191                    function,
192                    types.join(", ")
193                )
194            }
195            CheckErrorKind::TypeMismatch { expected, actual } => {
196                write!(
197                    f,
198                    "expected type '{}' but found '{}'",
199                    expected.display_name(),
200                    actual.display_name()
201                )
202            }
203            CheckErrorKind::UndefinedField { type_name, field } => {
204                write!(f, "undefined field '{}' on type '{}'", field, type_name)
205            }
206            CheckErrorKind::NotAssignable { from, to } => {
207                write!(
208                    f,
209                    "type '{}' is not assignable to '{}'",
210                    from.display_name(),
211                    to.display_name()
212                )
213            }
214            CheckErrorKind::HeterogeneousAggregate { types } => {
215                let type_names: Vec<_> = types.iter().map(|t| t.display_name()).collect();
216                write!(
217                    f,
218                    "aggregate literal contains heterogeneous types: {}",
219                    type_names.join(", ")
220                )
221            }
222            CheckErrorKind::NotAType { expr } => {
223                write!(f, "expression '{}' is not a type", expr)
224            }
225            CheckErrorKind::Other(msg) => write!(f, "{}", msg),
226        }
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn test_undeclared_reference() {
236        let err = CheckError::undeclared_reference("foo", 0..3, 1);
237        assert!(err.message().contains("undeclared reference"));
238        assert!(err.message().contains("foo"));
239    }
240
241    #[test]
242    fn test_no_matching_overload() {
243        let err =
244            CheckError::no_matching_overload("_+_", vec![CelType::String, CelType::Int], 0..5, 1);
245        assert!(err.message().contains("no matching overload"));
246        assert!(err.message().contains("_+_"));
247    }
248
249    #[test]
250    fn test_type_mismatch() {
251        let err = CheckError::type_mismatch(CelType::Bool, CelType::Int, 0..1, 1);
252        assert!(err.message().contains("expected"));
253        assert!(err.message().contains("bool"));
254        assert!(err.message().contains("int"));
255    }
256
257    #[test]
258    fn test_undefined_field() {
259        let err = CheckError::undefined_field("MyMessage", "unknown", 0..7, 1);
260        assert!(err.message().contains("undefined field"));
261        assert!(err.message().contains("unknown"));
262        assert!(err.message().contains("MyMessage"));
263    }
264}