1use crate::ast::*;
18use crate::parser::Loc;
19use itertools::Itertools;
20use miette::{Diagnostic, LabeledSpan};
21use smol_str::SmolStr;
22use std::sync::Arc;
23use thiserror::Error;
24
25#[derive(Debug, PartialEq, Eq, Clone, Error)]
27#[error("{error_kind}")]
28pub struct EvaluationError {
29 error_kind: EvaluationErrorKind,
31 advice: Option<String>,
33 source_loc: Option<Loc>,
37}
38const TOO_MANY_ATTRS: usize = 5;
40
41impl Diagnostic for EvaluationError {
43 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
44 match (self.error_kind.help(), self.advice.as_ref()) {
45 (Some(help), None) => Some(help),
46 (None, Some(advice)) => Some(Box::new(advice)),
47 (Some(help), Some(advice)) => Some(Box::new(format!("{help}; {advice}"))),
48 (None, None) => None,
49 }
50 }
51
52 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
53 self.source_loc
54 .as_ref()
55 .map(|loc| &loc.src as &dyn miette::SourceCode)
56 .or_else(|| self.error_kind.source_code())
57 }
58
59 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
60 self.source_loc
61 .as_ref()
62 .map(|loc| {
63 Box::new(std::iter::once(LabeledSpan::underline(loc.span)))
64 as Box<dyn Iterator<Item = _>>
65 })
66 .or_else(|| self.error_kind.labels())
67 }
68
69 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
70 self.error_kind.code()
71 }
72
73 fn severity(&self) -> Option<miette::Severity> {
74 self.error_kind.severity()
75 }
76
77 fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
78 self.error_kind.url()
79 }
80
81 fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
82 self.error_kind.diagnostic_source()
83 }
84
85 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
86 self.error_kind.related()
87 }
88}
89
90impl EvaluationError {
91 pub fn error_kind(&self) -> &EvaluationErrorKind {
93 &self.error_kind
94 }
95
96 pub fn source_loc(&self) -> Option<&Loc> {
98 self.source_loc.as_ref()
99 }
100
101 pub fn advice(&self) -> Option<&str> {
103 self.advice.as_deref()
104 }
105
106 pub fn set_advice(&mut self, advice: String) {
108 self.advice = Some(advice);
109 }
110
111 pub(crate) fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
113 Self { source_loc, ..self }
114 }
115
116 pub(crate) fn entity_does_not_exist(euid: Arc<EntityUID>, source_loc: Option<Loc>) -> Self {
118 Self {
119 error_kind: EvaluationErrorKind::EntityDoesNotExist(euid),
120 advice: None,
121 source_loc,
122 }
123 }
124
125 pub(crate) fn entity_attr_does_not_exist<'a>(
127 entity: Arc<EntityUID>,
128 attr: SmolStr,
129 source_loc: Option<Loc>,
130 ) -> Self {
131 Self {
132 error_kind: EvaluationErrorKind::EntityAttrDoesNotExist { entity, attr },
133 advice: None,
134 source_loc,
135 }
136 }
137
138 pub(crate) fn unspecified_entity_access(attr: SmolStr, source_loc: Option<Loc>) -> Self {
140 Self {
141 error_kind: EvaluationErrorKind::UnspecifiedEntityAccess(attr),
142 advice: None,
143 source_loc,
144 }
145 }
146
147 pub(crate) fn record_attr_does_not_exist<'a>(
149 attr: SmolStr,
150 available_attrs: impl IntoIterator<Item = &'a SmolStr>,
151 source_loc: Option<Loc>,
152 ) -> Self {
153 let alternatives = available_attrs
154 .into_iter()
155 .take(TOO_MANY_ATTRS)
156 .cloned()
157 .collect_vec();
158 Self {
159 error_kind: EvaluationErrorKind::RecordAttrDoesNotExist(attr, alternatives),
160 advice: None,
161 source_loc,
162 }
163 }
164
165 pub(crate) fn type_error(expected: Vec<Type>, actual: &Value) -> Self {
167 Self {
168 error_kind: EvaluationErrorKind::TypeError {
169 expected,
170 actual: actual.type_of(),
171 },
172 advice: None,
173 source_loc: actual.source_loc().cloned(),
174 }
175 }
176
177 pub(crate) fn type_error_single(expected: Type, actual: &Value) -> Self {
178 Self::type_error(vec![expected], actual)
179 }
180
181 pub(crate) fn type_error_with_advice(
183 expected: Vec<Type>,
184 actual: &Value,
185 advice: String,
186 ) -> Self {
187 Self {
188 error_kind: EvaluationErrorKind::TypeError {
189 expected,
190 actual: actual.type_of(),
191 },
192 advice: Some(advice),
193 source_loc: actual.source_loc().cloned(),
194 }
195 }
196
197 pub(crate) fn type_error_with_advice_single(
198 expected: Type,
199 actual: &Value,
200 advice: String,
201 ) -> Self {
202 Self::type_error_with_advice(vec![expected], actual, advice)
203 }
204
205 pub(crate) fn wrong_num_arguments(
207 function_name: Name,
208 expected: usize,
209 actual: usize,
210 source_loc: Option<Loc>,
211 ) -> Self {
212 Self {
213 error_kind: EvaluationErrorKind::WrongNumArguments {
214 function_name,
215 expected,
216 actual,
217 },
218 advice: None,
219 source_loc,
220 }
221 }
222
223 pub(crate) fn unlinked_slot(id: SlotId, source_loc: Option<Loc>) -> Self {
225 Self {
226 error_kind: EvaluationErrorKind::UnlinkedSlot(id),
227 advice: None,
228 source_loc,
229 }
230 }
231
232 pub(crate) fn failed_extension_function_application(
234 extension_name: Name,
235 msg: String,
236 source_loc: Option<Loc>,
237 ) -> Self {
238 Self {
239 error_kind: EvaluationErrorKind::FailedExtensionFunctionApplication {
240 extension_name,
241 msg,
242 },
243 advice: None,
244 source_loc,
245 }
246 }
247
248 pub(crate) fn non_value(e: Expr) -> Self {
250 let source_loc = e.source_loc().cloned();
251 Self {
252 error_kind: EvaluationErrorKind::NonValue(e),
253 advice: Some("consider using the partial evaluation APIs".into()),
254 source_loc,
255 }
256 }
257
258 #[cfg(not(target_arch = "wasm32"))]
260 pub(crate) fn recursion_limit(source_loc: Option<Loc>) -> Self {
261 Self {
262 error_kind: EvaluationErrorKind::RecursionLimit,
263 advice: None,
264 source_loc,
265 }
266 }
267
268 pub(crate) fn extension_function_lookup(
269 err: crate::extensions::ExtensionFunctionLookupError,
270 source_loc: Option<Loc>,
271 ) -> Self {
272 Self {
273 error_kind: err.into(),
274 advice: None,
275 source_loc,
276 }
277 }
278
279 pub(crate) fn integer_overflow(err: IntegerOverflowError, source_loc: Option<Loc>) -> Self {
280 Self {
281 error_kind: err.into(),
282 advice: None,
283 source_loc,
284 }
285 }
286}
287
288impl From<RestrictedExprError> for EvaluationError {
289 fn from(err: RestrictedExprError) -> Self {
290 Self {
291 error_kind: err.into(),
292 advice: None,
293 source_loc: None, }
295 }
296}
297
298#[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
300pub enum EvaluationErrorKind {
301 #[error("entity `{0}` does not exist")]
304 EntityDoesNotExist(Arc<EntityUID>),
305
306 #[error("`{}` does not have the attribute `{}`", &.entity, &.attr)]
309 EntityAttrDoesNotExist {
310 entity: Arc<EntityUID>,
312 attr: SmolStr,
314 },
315
316 #[error("cannot access attribute `{0}` of unspecified entity")]
318 UnspecifiedEntityAccess(SmolStr),
319
320 #[error("record does not have the attribute `{0}`")]
323 #[diagnostic(help("available attributes: {}", if .1.len() == TOO_MANY_ATTRS { format!("{:?} (truncated, more attributes may exist)", .1) } else { format!("{:?}", .1) } ))]
324 RecordAttrDoesNotExist(SmolStr, Vec<SmolStr>),
325
326 #[error(transparent)]
328 #[diagnostic(transparent)]
329 FailedExtensionFunctionLookup(#[from] crate::extensions::ExtensionFunctionLookupError),
330
331 #[error("{}", pretty_type_error(expected, actual))]
335 TypeError {
336 expected: Vec<Type>,
338 actual: Type,
340 },
341
342 #[error("wrong number of arguments provided to extension function `{function_name}`: expected {expected}, got {actual}")]
344 WrongNumArguments {
345 function_name: Name,
347 expected: usize,
349 actual: usize,
351 },
352
353 #[error(transparent)]
355 #[diagnostic(transparent)]
356 IntegerOverflow(#[from] IntegerOverflowError),
357
358 #[error(transparent)]
360 #[diagnostic(transparent)]
361 InvalidRestrictedExpression(#[from] RestrictedExprError),
362
363 #[error("template slot `{0}` was not linked")]
366 UnlinkedSlot(SlotId),
367
368 #[error("error while evaluating `{extension_name}` extension function: {msg}")]
370 FailedExtensionFunctionApplication {
371 extension_name: Name,
373 msg: String,
375 },
376
377 #[error("the expression contains unknown(s): `{0}`")]
381 NonValue(Expr),
382
383 #[error("recursion limit reached")]
385 RecursionLimit,
386}
387
388fn pretty_type_error(expected: &[Type], actual: &Type) -> String {
391 match expected.len() {
392 #[allow(clippy::unreachable)]
394 0 => unreachable!("should expect at least one type"),
395 #[allow(clippy::indexing_slicing)]
397 1 => format!("type error: expected {}, got {}", expected[0], actual),
398 _ => {
399 format!(
400 "type error: expected one of [{}], got {actual}",
401 expected.iter().join(", ")
402 )
403 }
404 }
405}
406
407#[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
408pub enum IntegerOverflowError {
409 #[error("integer overflow while attempting to {} the values `{arg1}` and `{arg2}`", match .op { BinaryOp::Add => "add", BinaryOp::Sub => "subtract", BinaryOp::Mul => "multiply", _ => "perform an operation on" })]
411 BinaryOp {
412 op: BinaryOp,
414 arg1: Value,
416 arg2: Value,
418 },
419
420 #[error("integer overflow while attempting to {} the value `{arg}`", match .op { UnaryOp::Neg => "negate", _ => "perform an operation on" })]
422 UnaryOp {
423 op: UnaryOp,
425 arg: Value,
427 },
428}
429
430pub type Result<T> = std::result::Result<T, EvaluationError>;