//! 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 = core::result::Result; /// 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, pub source: Option>, } 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) -> 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) } }