1use crate::ast::*;
18use crate::extensions::ExtensionFunctionLookupError;
19use crate::parser::Loc;
20use miette::Diagnostic;
21use nonempty::{nonempty, NonEmpty};
22use smol_str::SmolStr;
23use std::sync::Arc;
24use thiserror::Error;
25
26const TOO_MANY_ATTRS: usize = 5;
28
29#[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
35pub enum EvaluationError {
36 #[error(transparent)]
39 #[diagnostic(transparent)]
40 EntityDoesNotExist(#[from] evaluation_errors::EntityDoesNotExistError),
41
42 #[error(transparent)]
45 #[diagnostic(transparent)]
46 EntityAttrDoesNotExist(#[from] evaluation_errors::EntityAttrDoesNotExistError),
47
48 #[error(transparent)]
51 #[diagnostic(transparent)]
52 RecordAttrDoesNotExist(#[from] evaluation_errors::RecordAttrDoesNotExistError),
53
54 #[error(transparent)]
56 #[diagnostic(transparent)]
57 FailedExtensionFunctionLookup(#[from] ExtensionFunctionLookupError),
58
59 #[error(transparent)]
62 #[diagnostic(transparent)]
63 TypeError(#[from] evaluation_errors::TypeError),
64
65 #[error(transparent)]
67 #[diagnostic(transparent)]
68 WrongNumArguments(#[from] evaluation_errors::WrongNumArgumentsError),
69
70 #[error(transparent)]
72 #[diagnostic(transparent)]
73 IntegerOverflow(#[from] evaluation_errors::IntegerOverflowError),
74
75 #[error(transparent)]
77 #[diagnostic(transparent)]
78 UnlinkedSlot(#[from] evaluation_errors::UnlinkedSlotError),
79
80 #[error(transparent)]
82 #[diagnostic(transparent)]
83 FailedExtensionFunctionExecution(#[from] evaluation_errors::ExtensionFunctionExecutionError),
84
85 #[error(transparent)]
89 #[diagnostic(transparent)]
90 NonValue(#[from] evaluation_errors::NonValueError),
91
92 #[cfg(feature = "tolerant-ast")]
94 #[error(transparent)]
95 #[diagnostic(transparent)]
96 ASTErrorExpr(#[from] evaluation_errors::ASTErrorExprError),
97
98 #[error(transparent)]
100 #[diagnostic(transparent)]
101 RecursionLimit(#[from] evaluation_errors::RecursionLimitError),
102}
103
104impl EvaluationError {
105 pub(crate) fn source_loc(&self) -> Option<&Loc> {
107 match self {
108 Self::EntityDoesNotExist(e) => e.source_loc.as_ref(),
109 Self::EntityAttrDoesNotExist(e) => e.source_loc.as_ref(),
110 Self::RecordAttrDoesNotExist(e) => e.source_loc.as_ref(),
111 Self::FailedExtensionFunctionLookup(e) => e.source_loc(),
112 Self::TypeError(e) => e.source_loc.as_ref(),
113 Self::WrongNumArguments(e) => e.source_loc.as_ref(),
114 Self::IntegerOverflow(e) => e.source_loc(),
115 Self::UnlinkedSlot(e) => e.source_loc.as_ref(),
116 Self::FailedExtensionFunctionExecution(e) => e.source_loc.as_ref(),
117 Self::NonValue(e) => e.source_loc.as_ref(),
118 Self::RecursionLimit(e) => e.source_loc.as_ref(),
119 #[cfg(feature = "tolerant-ast")]
120 Self::ASTErrorExpr(e) => e.source_loc.as_ref(),
121 }
122 }
123
124 pub(crate) fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
126 match self {
127 Self::EntityDoesNotExist(e) => {
128 Self::EntityDoesNotExist(evaluation_errors::EntityDoesNotExistError {
129 source_loc,
130 ..e
131 })
132 }
133 Self::EntityAttrDoesNotExist(e) => {
134 Self::EntityAttrDoesNotExist(evaluation_errors::EntityAttrDoesNotExistError {
135 source_loc,
136 ..e
137 })
138 }
139 Self::RecordAttrDoesNotExist(e) => {
140 Self::RecordAttrDoesNotExist(evaluation_errors::RecordAttrDoesNotExistError {
141 source_loc,
142 ..e
143 })
144 }
145 Self::FailedExtensionFunctionLookup(e) => {
146 Self::FailedExtensionFunctionLookup(e.with_maybe_source_loc(source_loc))
147 }
148 Self::TypeError(e) => Self::TypeError(evaluation_errors::TypeError { source_loc, ..e }),
149 Self::WrongNumArguments(e) => {
150 Self::WrongNumArguments(evaluation_errors::WrongNumArgumentsError {
151 source_loc,
152 ..e
153 })
154 }
155 Self::IntegerOverflow(e) => Self::IntegerOverflow(e.with_maybe_source_loc(source_loc)),
156 Self::UnlinkedSlot(e) => {
157 Self::UnlinkedSlot(evaluation_errors::UnlinkedSlotError { source_loc, ..e })
158 }
159 Self::FailedExtensionFunctionExecution(e) => Self::FailedExtensionFunctionExecution(
160 evaluation_errors::ExtensionFunctionExecutionError { source_loc, ..e },
161 ),
162 Self::NonValue(e) => {
163 Self::NonValue(evaluation_errors::NonValueError { source_loc, ..e })
164 }
165 Self::RecursionLimit(_) => {
166 Self::RecursionLimit(evaluation_errors::RecursionLimitError { source_loc })
167 }
168 #[cfg(feature = "tolerant-ast")]
169 Self::ASTErrorExpr(_) => {
170 Self::ASTErrorExpr(evaluation_errors::ASTErrorExprError { source_loc })
171 }
172 }
173 }
174
175 pub(crate) fn entity_does_not_exist(uid: Arc<EntityUID>, source_loc: Option<Loc>) -> Self {
177 evaluation_errors::EntityDoesNotExistError { uid, source_loc }.into()
178 }
179
180 pub(crate) fn entity_attr_does_not_exist<'a>(
184 entity: Arc<EntityUID>,
185 attr: SmolStr,
186 available_attrs: impl IntoIterator<Item = &'a SmolStr>,
187 does_attr_exist_as_a_tag: bool,
188 total_attrs: usize,
189 source_loc: Option<Loc>,
190 ) -> Self {
191 evaluation_errors::EntityAttrDoesNotExistError {
192 entity,
193 attr_or_tag: attr,
194 was_attr: true,
195 exists_the_other_kind: does_attr_exist_as_a_tag,
196 available_attrs_or_tags: available_attrs
197 .into_iter()
198 .take(TOO_MANY_ATTRS)
199 .cloned()
200 .collect::<Vec<_>>(),
201 total_attrs_or_tags: total_attrs,
202 source_loc,
203 }
204 .into()
205 }
206
207 pub(crate) fn entity_tag_does_not_exist<'a>(
211 entity: Arc<EntityUID>,
212 tag: SmolStr,
213 available_tags: impl IntoIterator<Item = &'a SmolStr>,
214 does_tag_exist_as_an_attr: bool,
215 total_tags: usize,
216 source_loc: Option<Loc>,
217 ) -> Self {
218 evaluation_errors::EntityAttrDoesNotExistError {
219 entity,
220 attr_or_tag: tag,
221 was_attr: false,
222 exists_the_other_kind: does_tag_exist_as_an_attr,
223 available_attrs_or_tags: available_tags
224 .into_iter()
225 .take(TOO_MANY_ATTRS)
226 .cloned()
227 .collect::<Vec<_>>(),
228 total_attrs_or_tags: total_tags,
229 source_loc,
230 }
231 .into()
232 }
233
234 pub(crate) fn record_attr_does_not_exist<'a>(
236 attr: SmolStr,
237 available_attrs: impl IntoIterator<Item = &'a SmolStr>,
238 total_attrs: usize,
239 source_loc: Option<Loc>,
240 ) -> Self {
241 evaluation_errors::RecordAttrDoesNotExistError {
242 attr,
243 available_attrs: available_attrs
244 .into_iter()
245 .take(TOO_MANY_ATTRS)
246 .cloned()
247 .collect(),
248 total_attrs,
249 source_loc,
250 }
251 .into()
252 }
253
254 pub(crate) fn type_error(expected: NonEmpty<Type>, actual: &Value) -> Self {
256 evaluation_errors::TypeError {
257 expected,
258 actual: actual.type_of(),
259 advice: None,
260 source_loc: actual.source_loc().cloned(),
261 }
262 .into()
263 }
264
265 pub(crate) fn type_error_single(expected: Type, actual: &Value) -> Self {
266 Self::type_error(nonempty![expected], actual)
267 }
268
269 pub(crate) fn type_error_with_advice(
271 expected: NonEmpty<Type>,
272 actual: &Value,
273 advice: String,
274 ) -> Self {
275 evaluation_errors::TypeError {
276 expected,
277 actual: actual.type_of(),
278 advice: Some(advice),
279 source_loc: actual.source_loc().cloned(),
280 }
281 .into()
282 }
283
284 pub(crate) fn type_error_with_advice_single(
285 expected: Type,
286 actual: &Value,
287 advice: String,
288 ) -> Self {
289 Self::type_error_with_advice(nonempty![expected], actual, advice)
290 }
291
292 pub(crate) fn wrong_num_arguments(
294 function_name: Name,
295 expected: usize,
296 actual: usize,
297 source_loc: Option<Loc>,
298 ) -> Self {
299 evaluation_errors::WrongNumArgumentsError {
300 function_name,
301 expected,
302 actual,
303 source_loc,
304 }
305 .into()
306 }
307
308 pub(crate) fn unlinked_slot(slot: SlotId, source_loc: Option<Loc>) -> Self {
310 evaluation_errors::UnlinkedSlotError { slot, source_loc }.into()
311 }
312
313 pub(crate) fn failed_extension_function_application(
315 extension_name: Name,
316 msg: String,
317 source_loc: Option<Loc>,
318 advice: Option<String>,
319 ) -> Self {
320 evaluation_errors::ExtensionFunctionExecutionError {
321 extension_name,
322 msg,
323 advice,
324 source_loc,
325 }
326 .into()
327 }
328
329 pub(crate) fn non_value(expr: Expr) -> Self {
331 let source_loc = expr.source_loc().cloned();
332 evaluation_errors::NonValueError { expr, source_loc }.into()
333 }
334
335 pub(crate) fn recursion_limit(source_loc: Option<Loc>) -> Self {
337 evaluation_errors::RecursionLimitError { source_loc }.into()
338 }
339}
340
341pub mod evaluation_errors {
343 use crate::ast::{BinaryOp, EntityUID, Expr, SlotId, Type, UnaryOp, Value};
344 use crate::parser::Loc;
345 use itertools::Itertools;
346 use miette::Diagnostic;
347 use nonempty::NonEmpty;
348 use smol_str::SmolStr;
349 use std::fmt::Write;
350 use std::sync::Arc;
351 use thiserror::Error;
352
353 use super::Name;
354
355 #[derive(Debug, PartialEq, Eq, Clone, Error)]
361 #[error("entity `{uid}` does not exist")]
362 pub struct EntityDoesNotExistError {
363 pub(crate) uid: Arc<EntityUID>,
365 pub(crate) source_loc: Option<Loc>,
367 }
368
369 impl Diagnostic for EntityDoesNotExistError {
379 impl_diagnostic_from_source_loc_opt_field!(source_loc);
380 }
381
382 #[derive(Debug, PartialEq, Eq, Clone, Error)]
389 #[error("`{entity}` does not have the {} `{attr_or_tag}`", if *.was_attr { "attribute" } else { "tag" })]
390 pub struct EntityAttrDoesNotExistError {
391 pub(crate) entity: Arc<EntityUID>,
393 pub(crate) attr_or_tag: SmolStr,
395 pub(crate) was_attr: bool,
397 pub(crate) exists_the_other_kind: bool,
400 pub(crate) available_attrs_or_tags: Vec<SmolStr>,
402 pub(crate) total_attrs_or_tags: usize,
404 pub(crate) source_loc: Option<Loc>,
406 }
407
408 impl Diagnostic for EntityAttrDoesNotExistError {
409 impl_diagnostic_from_source_loc_opt_field!(source_loc);
410
411 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
412 let mut help_text = if self.available_attrs_or_tags.is_empty() {
413 format!(
414 "`{}` does not have any {}",
415 &self.entity,
416 if self.was_attr { "attributes" } else { "tags" }
417 )
418 } else if self.available_attrs_or_tags.len() == self.total_attrs_or_tags {
419 format!(
420 "available {}: [{}]",
421 if self.was_attr { "attributes" } else { "tags" },
422 self.available_attrs_or_tags.iter().join(",")
423 )
424 } else {
425 format!(
426 "available {}: [{}, ... ({} more attributes) ]",
427 if self.was_attr { "attributes" } else { "tags" },
428 self.available_attrs_or_tags.iter().join(","),
429 self.total_attrs_or_tags - self.available_attrs_or_tags.len()
430 )
431 };
432 if self.exists_the_other_kind {
433 #[expect(clippy::unwrap_used, reason = "A `write!` to a `String` cannot fail")]
434 write!(
435 &mut help_text,
436 "; note that {} (not {}) named `{}` does exist",
437 if self.was_attr {
438 "a tag"
439 } else {
440 "an attribute"
441 },
442 if self.was_attr {
443 "an attribute"
444 } else {
445 "a tag"
446 },
447 self.attr_or_tag,
448 )
449 .unwrap()
450 }
451 Some(Box::new(help_text))
452 }
453 }
454
455 #[derive(Debug, PartialEq, Eq, Clone, Error)]
462 #[error("record does not have the attribute `{attr}`")]
463 pub struct RecordAttrDoesNotExistError {
464 pub(crate) attr: SmolStr,
466 pub(crate) available_attrs: Vec<SmolStr>,
468 pub(crate) total_attrs: usize,
470 pub(crate) source_loc: Option<Loc>,
472 }
473
474 impl Diagnostic for RecordAttrDoesNotExistError {
475 impl_diagnostic_from_source_loc_opt_field!(source_loc);
476
477 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
478 if self.available_attrs.is_empty() {
479 Some(Box::new("record does not have any attributes"))
480 } else if self.available_attrs.len() == self.total_attrs {
481 Some(Box::new(format!(
482 "available attributes: {:?}",
483 self.available_attrs
484 )))
485 } else {
486 Some(Box::new(format!(
487 "available attributes: [{}, ... ({} more attributes) ]",
488 self.available_attrs.iter().join(","),
489 self.total_attrs - self.available_attrs.len()
490 )))
491 }
492 }
493 }
494
495 #[derive(Debug, PartialEq, Eq, Clone, Error)]
502 pub struct TypeError {
503 pub(crate) expected: NonEmpty<Type>,
505 pub(crate) actual: Type,
507 pub(crate) advice: Option<String>,
509 pub(crate) source_loc: Option<Loc>,
511 }
512
513 impl Diagnostic for TypeError {
514 impl_diagnostic_from_source_loc_opt_field!(source_loc);
515
516 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
517 self.advice.as_ref().map(|advice| Box::new(advice) as _)
518 }
519 }
520
521 impl std::fmt::Display for TypeError {
522 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523 if self.expected.len() == 1 {
524 write!(
525 f,
526 "type error: expected {}, got {}",
527 self.expected.first(),
528 self.actual
529 )
530 } else {
531 write!(
532 f,
533 "type error: expected one of [{}], got {}",
534 self.expected.iter().join(", "),
535 self.actual
536 )
537 }
538 }
539 }
540
541 #[derive(Debug, PartialEq, Eq, Clone, Error)]
547 #[error("wrong number of arguments provided to extension function `{function_name}`: expected {expected}, got {actual}")]
548 pub struct WrongNumArgumentsError {
549 pub(crate) function_name: Name,
551 pub(crate) expected: usize,
553 pub(crate) actual: usize,
555 pub(crate) source_loc: Option<Loc>,
557 }
558
559 impl Diagnostic for WrongNumArgumentsError {
560 impl_diagnostic_from_source_loc_opt_field!(source_loc);
561 }
562
563 #[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
569 pub enum IntegerOverflowError {
570 #[error(transparent)]
572 #[diagnostic(transparent)]
573 BinaryOp(#[from] BinaryOpOverflowError),
574
575 #[error(transparent)]
577 #[diagnostic(transparent)]
578 UnaryOp(#[from] UnaryOpOverflowError),
579 }
580
581 impl IntegerOverflowError {
582 pub(crate) fn source_loc(&self) -> Option<&Loc> {
583 match self {
584 Self::BinaryOp(e) => e.source_loc.as_ref(),
585 Self::UnaryOp(e) => e.source_loc.as_ref(),
586 }
587 }
588
589 pub(crate) fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
590 match self {
591 Self::BinaryOp(e) => Self::BinaryOp(BinaryOpOverflowError { source_loc, ..e }),
592 Self::UnaryOp(e) => Self::UnaryOp(UnaryOpOverflowError { source_loc, ..e }),
593 }
594 }
595 }
596
597 #[derive(Debug, PartialEq, Eq, Clone, Error)]
603 #[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" })]
604 pub struct BinaryOpOverflowError {
605 pub(crate) op: BinaryOp,
607 pub(crate) arg1: Value,
609 pub(crate) arg2: Value,
611 pub(crate) source_loc: Option<Loc>,
613 }
614
615 impl Diagnostic for BinaryOpOverflowError {
616 impl_diagnostic_from_source_loc_opt_field!(source_loc);
617 }
618
619 #[derive(Debug, PartialEq, Eq, Clone, Error)]
625 #[error("integer overflow while attempting to {} the value `{arg}`", match .op { UnaryOp::Neg => "negate", _ => "perform an operation on" })]
626 pub struct UnaryOpOverflowError {
627 pub(crate) op: UnaryOp,
629 pub(crate) arg: Value,
631 pub(crate) source_loc: Option<Loc>,
633 }
634
635 impl Diagnostic for UnaryOpOverflowError {
636 impl_diagnostic_from_source_loc_opt_field!(source_loc);
637 }
638
639 #[derive(Debug, PartialEq, Eq, Clone, Error)]
645 #[error("template slot `{slot}` was not linked")]
646 pub struct UnlinkedSlotError {
647 pub(crate) slot: SlotId,
649 pub(crate) source_loc: Option<Loc>,
651 }
652
653 impl Diagnostic for UnlinkedSlotError {
654 impl_diagnostic_from_source_loc_opt_field!(source_loc);
655 }
656
657 #[derive(Debug, PartialEq, Eq, Clone, Error)]
663 #[error("error while evaluating `{extension_name}` extension function: {msg}")]
664 pub struct ExtensionFunctionExecutionError {
665 pub(crate) extension_name: Name,
667 pub(crate) msg: String,
669 pub(crate) advice: Option<String>,
671 pub(crate) source_loc: Option<Loc>,
673 }
674
675 impl Diagnostic for ExtensionFunctionExecutionError {
676 impl_diagnostic_from_source_loc_opt_field!(source_loc);
677
678 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
679 self.advice.as_ref().map(|v| Box::new(v) as _)
680 }
681 }
682
683 impl ExtensionFunctionExecutionError {
684 pub fn extension_name(&self) -> String {
686 self.extension_name.to_string()
687 }
688 }
689
690 #[derive(Debug, PartialEq, Eq, Clone, Error)]
698 #[error("the expression contains unknown(s): `{expr}`")]
699 pub struct NonValueError {
700 pub(crate) expr: Expr,
702 pub(crate) source_loc: Option<Loc>,
704 }
705
706 impl Diagnostic for NonValueError {
707 impl_diagnostic_from_source_loc_opt_field!(source_loc);
708
709 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
710 Some(Box::new("consider using the partial evaluation APIs"))
711 }
712 }
713
714 #[cfg(feature = "tolerant-ast")]
720 #[derive(Debug, PartialEq, Eq, Clone, Error)]
721 #[error("the expression contains an error")]
722 pub struct ASTErrorExprError {
723 pub(crate) source_loc: Option<Loc>,
725 }
726
727 #[cfg(feature = "tolerant-ast")]
728 impl Diagnostic for ASTErrorExprError {
729 impl_diagnostic_from_source_loc_opt_field!(source_loc);
730
731 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
732 Some(Box::new("Represents an AST node that failed to parse"))
733 }
734 }
735
736 #[derive(Debug, PartialEq, Eq, Clone, Error)]
742 #[error("recursion limit reached")]
743 pub struct RecursionLimitError {
744 pub(crate) source_loc: Option<Loc>,
746 }
747
748 impl Diagnostic for RecursionLimitError {
749 impl_diagnostic_from_source_loc_opt_field!(source_loc);
750 }
751}
752
753pub type Result<T> = std::result::Result<T, EvaluationError>;