160 lines
5.2 KiB
Rust
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)
|
|
}
|
|
}
|