Files
nixjit/evaluator/nixjit_error/src/lib.rs
2025-09-14 17:39:57 +08:00

160 lines
5.2 KiB
Rust

//! This crate defines the centralized error types and `Result` alias used
//! throughout the entire `nixjit` evaluation pipeline. By consolidating error
//! handling here, we ensure a consistent approach to reporting failures across
//! different stages of processing, from parsing to final evaluation.
use std::rc::Rc;
use thiserror::Error;
/// A specialized `Result` type used for all fallible operations within the
/// `nixjit` crates. It defaults to the crate's `Error` type.
pub type Result<T> = core::result::Result<T, Error>;
/// The primary error enum, encompassing all potential failures that can occur
/// during the lifecycle of a Nix expression's evaluation.
#[derive(Error, Debug)]
pub enum ErrorKind {
/// An error occurred during the initial parsing phase. This typically
/// indicates a syntax error in the Nix source code, as detected by the
/// `rnix` parser.
#[error("error occurred during parse stage: {0}")]
ParseError(String),
/// An error occurred while "downgrading" the `rnix` AST to the
/// High-Level IR (HIR). This can happen if the AST has a structure that is
/// syntactically valid but semantically incorrect for our IR.
#[error("error occurred during downgrade stage: {0}")]
DowngradeError(String),
/// An error occurred during the variable resolution phase, where the HIR is
/// converted to the Low-Level IR (LIR). This is most commonly caused by
/// an unbound or undefined variable.
#[error("error occurred during variable resolve stage: {0}")]
ResolutionError(String),
/// An error occurred during the final evaluation of the LIR. This covers
/// all unrecoverable runtime errors, such as type mismatches (e.g., adding
/// a string to an integer), division by zero, or primitive operation
/// argument arity mismatch
#[error("error occurred during evaluation stage: {0}")]
EvalError(String),
/// Represents an error that can be generated by `throw` or `assert`, and
/// can be caught by `builtins.tryEval`.
#[error("{0}")]
Catchable(String),
/// A catch-all for any error that does not fit into the other categories.
#[error("an unknown or unexpected error occurred")]
Unknown,
}
#[derive(Debug)]
pub struct Error {
pub kind: ErrorKind,
pub span: Option<rnix::TextRange>,
pub source: Option<Rc<str>>,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Basic display
write!(f, "{}", self.kind)?;
// If we have source and span, print context
if let (Some(source), Some(span)) = (&self.source, self.span) {
let start_byte = usize::from(span.start());
let end_byte = usize::from(span.end());
if start_byte > source.len() || end_byte > source.len() {
return Ok(()); // Span is out of bounds
}
let mut start_line = 1;
let mut start_col = 1usize;
let mut line_start_byte = 0;
for (i, c) in source.char_indices() {
if i >= start_byte {
break;
}
if c == '\n' {
start_line += 1;
start_col = 1;
line_start_byte = i + 1;
} else {
start_col += 1;
}
}
let line_end_byte = source[line_start_byte..]
.find('\n')
.map(|i| line_start_byte + i)
.unwrap_or(source.len());
let line_str = &source[line_start_byte..line_end_byte];
let underline_len = if end_byte > start_byte {
end_byte - start_byte
} else {
1
};
write!(f, "\n --> {}:{}", start_line, start_col)?;
write!(f, "\n |\n")?;
writeln!(f, "{:4} | {}", start_line, line_str)?;
write!(
f,
" | {}{}",
" ".repeat(start_col.saturating_sub(1)),
"^".repeat(underline_len)
)?;
}
Ok(())
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.kind)
}
}
impl Error {
pub fn new(kind: ErrorKind) -> Self {
Self {
kind,
span: None,
source: None,
}
}
pub fn with_span(mut self, span: rnix::TextRange) -> Self {
self.span = Some(span);
self
}
pub fn with_source(mut self, source: Rc<str>) -> Self {
self.source = Some(source);
self
}
pub fn parse_error(msg: String) -> Self {
Self::new(ErrorKind::ParseError(msg))
}
pub fn downgrade_error(msg: String) -> Self {
Self::new(ErrorKind::DowngradeError(msg))
}
pub fn resolution_error(msg: String) -> Self {
Self::new(ErrorKind::ResolutionError(msg))
}
pub fn eval_error(msg: String) -> Self {
Self::new(ErrorKind::EvalError(msg))
}
pub fn catchable(msg: String) -> Self {
Self::new(ErrorKind::Catchable(msg))
}
pub fn unknown() -> Self {
Self::new(ErrorKind::Unknown)
}
}