use crate::ast::*;
use crate::parser::Loc;
use itertools::Itertools;
use miette::{Diagnostic, LabeledSpan};
use smol_str::SmolStr;
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, PartialEq, Eq, Clone, Error)]
#[error("{error_kind}")]
pub struct EvaluationError {
error_kind: EvaluationErrorKind,
advice: Option<String>,
source_loc: Option<Loc>,
}
const TOO_MANY_ATTRS: usize = 5;
impl Diagnostic for EvaluationError {
fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
match (self.error_kind.help(), self.advice.as_ref()) {
(Some(help), None) => Some(help),
(None, Some(advice)) => Some(Box::new(advice)),
(Some(help), Some(advice)) => Some(Box::new(format!("{help}; {advice}"))),
(None, None) => None,
}
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
self.source_loc
.as_ref()
.map(|loc| &loc.src as &dyn miette::SourceCode)
.or_else(|| self.error_kind.source_code())
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
self.source_loc
.as_ref()
.map(|loc| {
Box::new(std::iter::once(LabeledSpan::underline(loc.span)))
as Box<dyn Iterator<Item = _>>
})
.or_else(|| self.error_kind.labels())
}
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.error_kind.code()
}
fn severity(&self) -> Option<miette::Severity> {
self.error_kind.severity()
}
fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.error_kind.url()
}
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
self.error_kind.diagnostic_source()
}
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
self.error_kind.related()
}
}
impl EvaluationError {
pub fn error_kind(&self) -> &EvaluationErrorKind {
&self.error_kind
}
pub fn source_loc(&self) -> Option<&Loc> {
self.source_loc.as_ref()
}
pub fn advice(&self) -> Option<&str> {
self.advice.as_deref()
}
pub fn set_advice(&mut self, advice: String) {
self.advice = Some(advice);
}
pub(crate) fn with_maybe_source_loc(self, source_loc: Option<Loc>) -> Self {
Self { source_loc, ..self }
}
pub(crate) fn entity_does_not_exist(euid: Arc<EntityUID>, source_loc: Option<Loc>) -> Self {
Self {
error_kind: EvaluationErrorKind::EntityDoesNotExist(euid),
advice: None,
source_loc,
}
}
pub(crate) fn entity_attr_does_not_exist<'a>(
entity: Arc<EntityUID>,
attr: SmolStr,
source_loc: Option<Loc>,
) -> Self {
Self {
error_kind: EvaluationErrorKind::EntityAttrDoesNotExist { entity, attr },
advice: None,
source_loc,
}
}
pub(crate) fn unspecified_entity_access(attr: SmolStr, source_loc: Option<Loc>) -> Self {
Self {
error_kind: EvaluationErrorKind::UnspecifiedEntityAccess(attr),
advice: None,
source_loc,
}
}
pub(crate) fn record_attr_does_not_exist<'a>(
attr: SmolStr,
available_attrs: impl IntoIterator<Item = &'a SmolStr>,
source_loc: Option<Loc>,
) -> Self {
let alternatives = available_attrs
.into_iter()
.take(TOO_MANY_ATTRS)
.cloned()
.collect_vec();
Self {
error_kind: EvaluationErrorKind::RecordAttrDoesNotExist(attr, alternatives),
advice: None,
source_loc,
}
}
pub(crate) fn type_error(expected: Vec<Type>, actual: &Value) -> Self {
Self {
error_kind: EvaluationErrorKind::TypeError {
expected,
actual: actual.type_of(),
},
advice: None,
source_loc: actual.source_loc().cloned(),
}
}
pub(crate) fn type_error_single(expected: Type, actual: &Value) -> Self {
Self::type_error(vec![expected], actual)
}
pub(crate) fn type_error_with_advice(
expected: Vec<Type>,
actual: &Value,
advice: String,
) -> Self {
Self {
error_kind: EvaluationErrorKind::TypeError {
expected,
actual: actual.type_of(),
},
advice: Some(advice),
source_loc: actual.source_loc().cloned(),
}
}
pub(crate) fn type_error_with_advice_single(
expected: Type,
actual: &Value,
advice: String,
) -> Self {
Self::type_error_with_advice(vec![expected], actual, advice)
}
pub(crate) fn wrong_num_arguments(
function_name: Name,
expected: usize,
actual: usize,
source_loc: Option<Loc>,
) -> Self {
Self {
error_kind: EvaluationErrorKind::WrongNumArguments {
function_name,
expected,
actual,
},
advice: None,
source_loc,
}
}
pub(crate) fn unlinked_slot(id: SlotId, source_loc: Option<Loc>) -> Self {
Self {
error_kind: EvaluationErrorKind::UnlinkedSlot(id),
advice: None,
source_loc,
}
}
pub(crate) fn failed_extension_function_application(
extension_name: Name,
msg: String,
source_loc: Option<Loc>,
) -> Self {
Self {
error_kind: EvaluationErrorKind::FailedExtensionFunctionApplication {
extension_name,
msg,
},
advice: None,
source_loc,
}
}
pub(crate) fn non_value(e: Expr) -> Self {
let source_loc = e.source_loc().cloned();
Self {
error_kind: EvaluationErrorKind::NonValue(e),
advice: Some("consider using the partial evaluation APIs".into()),
source_loc,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn recursion_limit(source_loc: Option<Loc>) -> Self {
Self {
error_kind: EvaluationErrorKind::RecursionLimit,
advice: None,
source_loc,
}
}
pub(crate) fn extension_function_lookup(
err: crate::extensions::ExtensionFunctionLookupError,
source_loc: Option<Loc>,
) -> Self {
Self {
error_kind: err.into(),
advice: None,
source_loc,
}
}
pub(crate) fn integer_overflow(err: IntegerOverflowError, source_loc: Option<Loc>) -> Self {
Self {
error_kind: err.into(),
advice: None,
source_loc,
}
}
}
impl From<RestrictedExprError> for EvaluationError {
fn from(err: RestrictedExprError) -> Self {
Self {
error_kind: err.into(),
advice: None,
source_loc: None, }
}
}
#[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
pub enum EvaluationErrorKind {
#[error("entity `{0}` does not exist")]
EntityDoesNotExist(Arc<EntityUID>),
#[error("`{}` does not have the attribute `{}`", &.entity, &.attr)]
EntityAttrDoesNotExist {
entity: Arc<EntityUID>,
attr: SmolStr,
},
#[error("cannot access attribute `{0}` of unspecified entity")]
UnspecifiedEntityAccess(SmolStr),
#[error("record does not have the attribute `{0}`")]
#[diagnostic(help("available attributes: {}", if .1.len() == TOO_MANY_ATTRS { format!("{:?} (truncated, more attributes may exist)", .1) } else { format!("{:?}", .1) } ))]
RecordAttrDoesNotExist(SmolStr, Vec<SmolStr>),
#[error(transparent)]
#[diagnostic(transparent)]
FailedExtensionFunctionLookup(#[from] crate::extensions::ExtensionFunctionLookupError),
#[error("{}", pretty_type_error(expected, actual))]
TypeError {
expected: Vec<Type>,
actual: Type,
},
#[error("wrong number of arguments provided to extension function `{function_name}`: expected {expected}, got {actual}")]
WrongNumArguments {
function_name: Name,
expected: usize,
actual: usize,
},
#[error(transparent)]
#[diagnostic(transparent)]
IntegerOverflow(#[from] IntegerOverflowError),
#[error(transparent)]
#[diagnostic(transparent)]
InvalidRestrictedExpression(#[from] RestrictedExprError),
#[error("template slot `{0}` was not linked")]
UnlinkedSlot(SlotId),
#[error("error while evaluating `{extension_name}` extension function: {msg}")]
FailedExtensionFunctionApplication {
extension_name: Name,
msg: String,
},
#[error("the expression contains unknown(s): `{0}`")]
NonValue(Expr),
#[error("recursion limit reached")]
RecursionLimit,
}
fn pretty_type_error(expected: &[Type], actual: &Type) -> String {
match expected.len() {
#[allow(clippy::unreachable)]
0 => unreachable!("should expect at least one type"),
#[allow(clippy::indexing_slicing)]
1 => format!("type error: expected {}, got {}", expected[0], actual),
_ => {
format!(
"type error: expected one of [{}], got {actual}",
expected.iter().join(", ")
)
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Diagnostic, Error)]
pub enum IntegerOverflowError {
#[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" })]
BinaryOp {
op: BinaryOp,
arg1: Value,
arg2: Value,
},
#[error("integer overflow while attempting to {} the value `{arg}`", match .op { UnaryOp::Neg => "negate", _ => "perform an operation on" })]
UnaryOp {
op: UnaryOp,
arg: Value,
},
}
pub type Result<T> = std::result::Result<T, EvaluationError>;