snix_eval/
errors.rs

1use std::error;
2use std::io;
3use std::io::Write;
4use std::path::PathBuf;
5use std::rc::Rc;
6use std::str::Utf8Error;
7use std::string::FromUtf8Error;
8use std::sync::Arc;
9use std::{fmt::Debug, fmt::Display, num::ParseIntError};
10
11use codemap::{File, Span};
12use codemap_diagnostic::{ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle};
13use smol_str::SmolStr;
14
15use crate::spans::ToSpan;
16use crate::value::{CoercionKind, NixString};
17use crate::{SourceCode, Value};
18
19/// Propagates a [catchable error](CatchableErrorKind) as `Ok(Err(_))` or returns the unwrapped value in `Ok`.
20///
21/// You should use the try operator (`?`) to propagate uncatchable errors before passing catchable
22/// errors to `try_cek`.
23///
24/// **Input type:** `Result<T, CatchableErrorKind>`
25///
26/// **Output type:** `T`
27///
28/// **Return type of containing function:** `Result<Result<_, CatchableErrorKind>, _>`
29///
30/// # Example
31///
32/// ```
33/// use snix_eval::{try_cek, CatchableErrorKind};
34///
35/// # #[derive(Debug)] struct MyUncatchableError;
36/// # fn example() -> Result<Result<(), CatchableErrorKind>, MyUncatchableError> {
37/// fn my_fn() -> Result<Result<i32, CatchableErrorKind>, MyUncatchableError> {
38///     Ok(Ok(42))
39/// }
40///
41/// let value: i32 = try_cek!(my_fn()?);
42/// assert_eq!(value, 42);
43///
44/// fn my_other_fn() -> Result<Result<i32, CatchableErrorKind>, MyUncatchableError> {
45///     Ok(Err(CatchableErrorKind::AssertionFailed))
46/// }
47///
48/// try_cek!(my_other_fn()?); // results in `return Ok(Err(cek))`
49/// unreachable!();
50///
51/// # Ok(Ok(()))
52/// # }
53/// # fn main() {
54/// #     pretty_assertions::assert_matches!(example().unwrap(), Err(CatchableErrorKind::AssertionFailed));
55/// # }
56/// ```
57#[macro_export]
58macro_rules! try_cek {
59    ($result:expr) => {
60        match $result {
61            Ok(s) => s,
62            Err(cek) => {
63                // Type-check to avoid accidental misuse
64                let cek: $crate::CatchableErrorKind = cek;
65                return Ok(Err(cek));
66            }
67        }
68    };
69}
70
71/// Propagates a [catchable error](CatchableErrorKind) as `Ok(Value::Catchable(_))` or returns the unwrapped value in `Ok`.
72///
73/// **Input type:** `Result<T, CatchableErrorKind>`
74///
75/// **Output type:** `T`
76///
77/// **Return type of containing function:** `Result<Value, _>`
78///
79/// See [`try_cek!`]'s documentation for more.
80#[macro_export]
81macro_rules! try_cek_to_value {
82    ($result:expr) => {
83        match $result {
84            Ok(s) => s,
85            Err(cek) => {
86                // Type-check to avoid accidental misuse
87                let cek: $crate::CatchableErrorKind = cek;
88                return Ok(Value::Catchable(Box::new(cek)));
89            }
90        }
91    };
92}
93
94/// "CatchableErrorKind" errors -- those which can be detected by
95/// `builtins.tryEval`.
96///
97/// Note: this type is deliberately *not* incorporated as a variant
98/// of ErrorKind, because then Result<Value,ErrorKind> would have
99/// redundant representations for catchable errors, which would make
100/// it too easy to handle errors incorrectly:
101///
102///   - Ok(Value::Catchable(cek))
103///   - Err(ErrorKind::ThisVariantDoesNotExist(cek))
104///
105/// Because CatchableErrorKind is not a variant of ErrorKind, you
106/// will often see functions which return a type like:
107///
108///   Result<Result<T,CatchableErrorKind>,ErrorKind>
109///
110/// ... where T is any type other than Value.  This is unfortunate,
111/// because Rust's magic `?`-syntax does not work on nested Result
112/// values like this.
113// TODO(amjoseph): investigate result<T,Either<CatchableErrorKind,ErrorKind>>
114#[derive(thiserror::Error, Clone, Debug)]
115pub enum CatchableErrorKind {
116    #[error("error thrown: {0}")]
117    Throw(NixString),
118
119    #[error("assertion failed")]
120    AssertionFailed,
121
122    #[error("feature {0} is not implemented yet")]
123    UnimplementedFeature(Box<str>),
124
125    /// Resolving a user-supplied angle brackets path literal failed in some way.
126    #[error("Nix path entry could not be resolved: {0}")]
127    NixPathResolution(Box<str>),
128}
129
130#[derive(thiserror::Error, Clone, Debug)]
131pub enum ErrorKind {
132    /// These are user-generated errors through builtins.
133    #[error("evaluation aborted: {0}")]
134    Abort(String),
135
136    #[error("division by zero")]
137    DivisionByZero,
138
139    #[error("attribute key '{key}' already defined")]
140    DuplicateAttrsKey { key: String },
141
142    /// Attempted to specify an invalid key type (e.g. integer) in a
143    /// dynamic attribute name.
144    #[error(
145        "found attribute name '{}' of type '{}', but attribute names must be strings",
146        .0,
147        .0.type_of()
148    )]
149    InvalidAttributeName(Value),
150
151    #[error("attribute with name '{name}' could not be found in the set")]
152    AttributeNotFound { name: String },
153
154    /// Attempted to index into a list beyond its boundaries.
155    #[error("list index '{index}' is out of bounds")]
156    IndexOutOfBounds { index: i64 },
157
158    /// Attempted to call `builtins.tail` on an empty list.
159    #[error("'tail' called on an empty list")]
160    TailEmptyList,
161
162    #[error("expected value of type '{expected}', but found a '{actual}'")]
163    TypeError {
164        expected: &'static str,
165        actual: &'static str,
166    },
167
168    #[error("can not compare a {lhs} with a {rhs}")]
169    Incomparable {
170        lhs: &'static str,
171        rhs: &'static str,
172    },
173
174    /// Resolving a user-supplied relative or home-relative path literal failed in some way.
175    #[error("could not resolve path: {0}")]
176    RelativePathResolution(String),
177
178    /// Dynamic keys are not allowed in some scopes.
179    #[error("dynamically evaluated keys are not allowed in {0}")]
180    DynamicKeyInScope(&'static str),
181
182    /// Unknown variable in statically known scope.
183    #[error("variable not found")]
184    UnknownStaticVariable,
185
186    /// Unknown variable in dynamic scope (with, rec, ...).
187    #[error(
188        r#"variable '{0}' could not be found
189
190Note that this occured within a `with`-expression. The problem may be related
191to a missing value in the attribute set(s) included via `with`."#
192    )]
193    UnknownDynamicVariable(String),
194
195    /// User is defining the same variable twice at the same depth.
196    #[error("variable has already been defined")]
197    VariableAlreadyDefined(Option<Span>),
198
199    /// Attempt to call something that is not callable.
200    #[error("only functions and builtins can be called, but this is a '{0}'")]
201    NotCallable(&'static str),
202
203    /// Infinite recursion encountered while forcing thunks.
204    #[error("infinite recursion encountered")]
205    InfiniteRecursion {
206        first_force: Span,
207        suspended_at: Option<Span>,
208        content_span: Option<Span>,
209    },
210
211    // Errors themselves ignored here & handled in Self::spans instead
212    #[error("failed to parse Nix code:")]
213    ParseErrors(Vec<rnix::ParseError>),
214
215    /// An error occured while executing some native code (e.g. a
216    /// builtin), and needs to be chained up.
217    #[error("while evaluating this as native code ({gen_type})")]
218    NativeError {
219        gen_type: &'static str,
220        err: Box<Error>,
221    },
222
223    /// An error occured while executing Snix bytecode, but needs to
224    /// be chained up.
225    #[error("while evaluating this Nix code")]
226    BytecodeError(Box<Error>),
227
228    /// Given type can't be coerced to a string in the respective context
229    #[error("cannot ({}) coerce {from} to a string{}",
230        (if .kind.strong { "strongly" } else { "weakly" }),
231        (if *.from == "set" {
232            ", missing a `__toString` or `outPath` attribute"
233        } else {
234            ""
235        })
236    )]
237    NotCoercibleToString {
238        from: &'static str,
239        kind: CoercionKind,
240    },
241
242    /// The given string doesn't represent an absolute path
243    #[error("string '{}' does not represent an absolute path", .0.to_string_lossy())]
244    NotAnAbsolutePath(PathBuf),
245
246    /// An error occurred when parsing an integer
247    #[error("invalid integer: {0}")]
248    ParseIntError(ParseIntError),
249
250    // Errors specific to nested attribute sets and merges thereof.
251    /// Nested attributes can not be merged with an inherited value.
252    #[error("cannot merge a nested attribute set into the inherited entry '{name}'")]
253    UnmergeableInherit { name: SmolStr },
254
255    /// Nested attributes can not be merged with values that are not
256    /// literal attribute sets.
257    #[error("nested attribute sets or keys can only be merged with literal attribute sets")]
258    UnmergeableValue,
259
260    // Errors themselves ignored here & handled in Self::spans instead
261    /// Parse errors occured while importing a file.
262    #[error("parse errors occured while importing '{}'", .path.to_string_lossy())]
263    ImportParseError {
264        path: PathBuf,
265        file: Arc<File>,
266        errors: Vec<rnix::ParseError>,
267    },
268
269    /// Compilation errors occured while importing a file.
270    #[error("compiler errors occured while importing '{}'", .path.to_string_lossy())]
271    ImportCompilerError { path: PathBuf, errors: Vec<Error> },
272
273    /// I/O errors
274    #[error("I/O error: {}",
275        ({
276            let mut msg = String::new();
277
278            if let Some(path) = .path {
279                msg.push_str(&format!("{}: ", path.display()));
280            }
281
282            msg.push_str(&.error.to_string());
283
284            msg
285        })
286    )]
287    IO {
288        path: Option<PathBuf>,
289        error: Rc<io::Error>,
290    },
291
292    /// Errors parsing JSON, or serializing as JSON.
293    #[error("Error converting JSON to a Nix value or back: {0}")]
294    JsonError(String),
295
296    /// Nix value that can not be serialised to JSON.
297    #[error("a {0} cannot be converted to JSON")]
298    NotSerialisableToJson(&'static str),
299
300    /// Errors converting TOML to a value
301    #[error("Error converting TOML to a Nix value: {0}")]
302    FromTomlError(String),
303
304    /// An unexpected argument was supplied to a builtin
305    #[error("Unexpected agrument `{0}` passed to builtin")]
306    UnexpectedArgumentBuiltin(NixString),
307
308    /// An unexpected argument was supplied to a function that takes formal parameters
309    #[error("Unexpected argument `{arg}` supplied to function")]
310    UnexpectedArgumentFormals { arg: NixString, formals_span: Span },
311
312    /// Invalid UTF-8 was encoutered somewhere
313    #[error("Invalid UTF-8 in string")]
314    Utf8,
315
316    #[error("Invalid hash: {0}")]
317    InvalidHash(String),
318
319    /// Variant for errors that bubble up to eval from other Snix
320    /// components.
321    #[error("{0}")]
322    SnixError(Arc<dyn error::Error + Send + Sync>),
323
324    /// Variant for code paths that are known bugs in Snix (usually
325    /// issues with the compiler/VM interaction).
326    #[error("{}",
327        ({
328            let mut disp = format!("Snix bug: {}", .msg);
329
330            if let Some(metadata) = .metadata {
331                disp.push_str(&format!("; metadata: {metadata:?}"));
332            }
333
334            disp
335        })
336    )]
337    SnixBug {
338        msg: &'static str,
339        metadata: Option<Rc<dyn Debug>>,
340    },
341
342    /// Snix internal warning for features triggered by users that are
343    /// not actually implemented yet, and without which eval can not
344    /// proceed.
345    #[error("feature not yet implemented in Snix: {0}")]
346    NotImplemented(&'static str),
347
348    /// Internal variant which should disappear during error construction.
349    #[error("internal ErrorKind::WithContext variant leaked")]
350    WithContext {
351        context: String,
352        underlying: Box<ErrorKind>,
353    },
354
355    /// Unexpected context string
356    #[error("unexpected context string")]
357    UnexpectedContext,
358
359    /// Top-level evaluation result was a catchable Nix error, and
360    /// should fail the evaluation.
361    ///
362    /// This variant **must** only be used at the top-level of
363    /// snix-eval when returning a result to the user, never inside of
364    /// eval code.
365    #[error("{0}")]
366    CatchableError(CatchableErrorKind),
367
368    /// Invalid hash type specified, must be one of "md5", "sha1", "sha256"
369    /// or "sha512"
370    #[error("unknown hash type '{0}'")]
371    UnknownHashType(String),
372
373    /// An invalid regular expression was passed.
374    #[error("invalid regular expression '{0}'")]
375    InvalidRegex(String),
376}
377
378impl error::Error for Error {
379    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
380        match &self.kind {
381            ErrorKind::NativeError { err, .. } | ErrorKind::BytecodeError(err) => err.source(),
382            ErrorKind::ParseErrors(err) => err.first().map(|e| e as &dyn error::Error),
383            ErrorKind::ParseIntError(err) => Some(err),
384            ErrorKind::ImportParseError { errors, .. } => {
385                errors.first().map(|e| e as &dyn error::Error)
386            }
387            ErrorKind::ImportCompilerError { errors, .. } => {
388                errors.first().map(|e| e as &dyn error::Error)
389            }
390            ErrorKind::IO { error, .. } => Some(error.as_ref()),
391            ErrorKind::SnixError(error) => Some(error.as_ref()),
392            _ => None,
393        }
394    }
395}
396
397impl From<ParseIntError> for ErrorKind {
398    fn from(e: ParseIntError) -> Self {
399        Self::ParseIntError(e)
400    }
401}
402
403impl From<Utf8Error> for ErrorKind {
404    fn from(_: Utf8Error) -> Self {
405        Self::NotImplemented("FromUtf8Error not handled: https://b.tvl.fyi/issues/189")
406    }
407}
408
409impl From<FromUtf8Error> for ErrorKind {
410    fn from(_: FromUtf8Error) -> Self {
411        Self::NotImplemented("FromUtf8Error not handled: https://b.tvl.fyi/issues/189")
412    }
413}
414
415impl From<bstr::Utf8Error> for ErrorKind {
416    fn from(_: bstr::Utf8Error) -> Self {
417        Self::Utf8
418    }
419}
420
421impl From<bstr::FromUtf8Error> for ErrorKind {
422    fn from(_value: bstr::FromUtf8Error) -> Self {
423        Self::Utf8
424    }
425}
426
427impl From<io::Error> for ErrorKind {
428    fn from(e: io::Error) -> Self {
429        ErrorKind::IO {
430            path: None,
431            error: Rc::new(e),
432        }
433    }
434}
435
436impl From<serde_json::Error> for ErrorKind {
437    fn from(err: serde_json::Error) -> Self {
438        // Can't just put the `serde_json::Error` in the ErrorKind since it doesn't impl `Clone`
439        Self::JsonError(err.to_string())
440    }
441}
442
443impl From<toml::de::Error> for ErrorKind {
444    fn from(err: toml::de::Error) -> Self {
445        Self::FromTomlError(format!("error in TOML serialization: {err}"))
446    }
447}
448
449#[derive(Clone, Debug)]
450pub struct Error {
451    pub kind: ErrorKind,
452    pub span: Span,
453    pub contexts: Vec<String>,
454    pub source: SourceCode,
455}
456
457impl Error {
458    pub fn new(mut kind: ErrorKind, span: Span, source: SourceCode) -> Self {
459        let mut contexts = vec![];
460        while let ErrorKind::WithContext {
461            context,
462            underlying,
463        } = kind
464        {
465            kind = *underlying;
466            contexts.push(context);
467        }
468
469        Error {
470            kind,
471            span,
472            contexts,
473            source,
474        }
475    }
476}
477
478impl Display for Error {
479    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
480        write!(f, "{}", self.kind)
481    }
482}
483
484pub type EvalResult<T> = Result<T, Error>;
485
486/// Human-readable names for rnix syntaxes.
487fn name_for_syntax(syntax: &rnix::SyntaxKind) -> &'static str {
488    match syntax {
489        rnix::SyntaxKind::TOKEN_COMMENT => "a comment",
490        rnix::SyntaxKind::TOKEN_WHITESPACE => "whitespace",
491        rnix::SyntaxKind::TOKEN_ASSERT => "`assert`-keyword",
492        rnix::SyntaxKind::TOKEN_ELSE => "`else`-keyword",
493        rnix::SyntaxKind::TOKEN_IN => "`in`-keyword",
494        rnix::SyntaxKind::TOKEN_IF => "`if`-keyword",
495        rnix::SyntaxKind::TOKEN_INHERIT => "`inherit`-keyword",
496        rnix::SyntaxKind::TOKEN_CUR_POS => "`__curPos`-symbol",
497        rnix::SyntaxKind::TOKEN_LET => "`let`-keyword",
498        rnix::SyntaxKind::TOKEN_OR => "`or`-keyword",
499        rnix::SyntaxKind::TOKEN_REC => "`rec`-keyword",
500        rnix::SyntaxKind::TOKEN_THEN => "`then`-keyword",
501        rnix::SyntaxKind::TOKEN_WITH => "`with`-keyword",
502        rnix::SyntaxKind::TOKEN_L_BRACE => "{",
503        rnix::SyntaxKind::TOKEN_R_BRACE => "}",
504        rnix::SyntaxKind::TOKEN_L_BRACK => "[",
505        rnix::SyntaxKind::TOKEN_R_BRACK => "]",
506        rnix::SyntaxKind::TOKEN_ASSIGN => "=",
507        rnix::SyntaxKind::TOKEN_AT => "@",
508        rnix::SyntaxKind::TOKEN_COLON => ":",
509        rnix::SyntaxKind::TOKEN_COMMA => "`,`",
510        rnix::SyntaxKind::TOKEN_DOT => ".",
511        rnix::SyntaxKind::TOKEN_ELLIPSIS => "...",
512        rnix::SyntaxKind::TOKEN_QUESTION => "?",
513        rnix::SyntaxKind::TOKEN_SEMICOLON => ";",
514        rnix::SyntaxKind::TOKEN_L_PAREN => "(",
515        rnix::SyntaxKind::TOKEN_R_PAREN => ")",
516        rnix::SyntaxKind::TOKEN_CONCAT => "++",
517        rnix::SyntaxKind::TOKEN_INVERT => "!",
518        rnix::SyntaxKind::TOKEN_UPDATE => "//",
519        rnix::SyntaxKind::TOKEN_ADD => "+",
520        rnix::SyntaxKind::TOKEN_SUB => "-",
521        rnix::SyntaxKind::TOKEN_MUL => "*",
522        rnix::SyntaxKind::TOKEN_DIV => "/",
523        rnix::SyntaxKind::TOKEN_AND_AND => "&&",
524        rnix::SyntaxKind::TOKEN_EQUAL => "==",
525        rnix::SyntaxKind::TOKEN_IMPLICATION => "->",
526        rnix::SyntaxKind::TOKEN_LESS => "<",
527        rnix::SyntaxKind::TOKEN_LESS_OR_EQ => "<=",
528        rnix::SyntaxKind::TOKEN_MORE => ">",
529        rnix::SyntaxKind::TOKEN_MORE_OR_EQ => ">=",
530        rnix::SyntaxKind::TOKEN_NOT_EQUAL => "!=",
531        rnix::SyntaxKind::TOKEN_OR_OR => "||",
532        rnix::SyntaxKind::TOKEN_FLOAT => "a float",
533        rnix::SyntaxKind::TOKEN_IDENT => "an identifier",
534        rnix::SyntaxKind::TOKEN_INTEGER => "an integer",
535        rnix::SyntaxKind::TOKEN_INTERPOL_END => "}",
536        rnix::SyntaxKind::TOKEN_INTERPOL_START => "${",
537        rnix::SyntaxKind::TOKEN_PATH_ABS => "an absolute path",
538        rnix::SyntaxKind::TOKEN_PATH_HOME => "a path relative to home",
539        rnix::SyntaxKind::TOKEN_PATH_REL => "a relative path",
540        rnix::SyntaxKind::TOKEN_PATH_SEARCH => "a path search",
541        rnix::SyntaxKind::TOKEN_URI => "a literal URI",
542        rnix::SyntaxKind::TOKEN_STRING_CONTENT => "content of a string",
543        rnix::SyntaxKind::TOKEN_STRING_END => "\"",
544        rnix::SyntaxKind::TOKEN_STRING_START => "\"",
545        rnix::SyntaxKind::TOKEN_ERROR => "unexpected token",
546
547        rnix::SyntaxKind::NODE_APPLY => "a function application",
548        rnix::SyntaxKind::NODE_ASSERT => "an assertion",
549        rnix::SyntaxKind::NODE_ATTRPATH => "an attribute path",
550        rnix::SyntaxKind::NODE_DYNAMIC => "a dynamic identifier",
551
552        rnix::SyntaxKind::NODE_IDENT => "an identifier",
553        rnix::SyntaxKind::NODE_IF_ELSE => "an `if`-expression",
554        rnix::SyntaxKind::NODE_SELECT => "a `select`-expression",
555        rnix::SyntaxKind::NODE_INHERIT => "inherited values",
556        rnix::SyntaxKind::NODE_INHERIT_FROM => "inherited values",
557        rnix::SyntaxKind::NODE_STRING => "a string",
558        rnix::SyntaxKind::NODE_INTERPOL => "an interpolation",
559        rnix::SyntaxKind::NODE_LAMBDA => "a function",
560        rnix::SyntaxKind::NODE_IDENT_PARAM => "a function parameter",
561        rnix::SyntaxKind::NODE_LEGACY_LET => "a legacy `let`-expression",
562        rnix::SyntaxKind::NODE_LET_IN => "a `let`-expression",
563        rnix::SyntaxKind::NODE_LIST => "a list",
564        rnix::SyntaxKind::NODE_BIN_OP => "a binary operator",
565        rnix::SyntaxKind::NODE_PAREN => "a parenthesised expression",
566        rnix::SyntaxKind::NODE_PATTERN => "a function argument pattern",
567        rnix::SyntaxKind::NODE_PAT_BIND => "an argument pattern binding",
568        rnix::SyntaxKind::NODE_PAT_ENTRY => "an argument pattern entry",
569        rnix::SyntaxKind::NODE_ROOT => "a Nix expression",
570        rnix::SyntaxKind::NODE_ATTR_SET => "an attribute set",
571        rnix::SyntaxKind::NODE_ATTRPATH_VALUE => "an attribute set entry",
572        rnix::SyntaxKind::NODE_UNARY_OP => "a unary operator",
573        rnix::SyntaxKind::NODE_LITERAL => "a literal value",
574        rnix::SyntaxKind::NODE_WITH => "a `with`-expression",
575        rnix::SyntaxKind::NODE_PATH_ABS => "an absolute path",
576        rnix::SyntaxKind::NODE_PATH_HOME => "a path relative to home",
577        rnix::SyntaxKind::NODE_PATH_REL => "a relative path",
578        rnix::SyntaxKind::NODE_PATH_SEARCH => "a path search",
579        rnix::SyntaxKind::NODE_HAS_ATTR => "`?`-operator",
580        rnix::SyntaxKind::NODE_CUR_POS => "`__curPos`",
581
582        // TODO: unsure about this variant, lets crash!
583        rnix::SyntaxKind::NODE_ERROR => todo!("NODE_ERROR found, open a bug with a reproducer"),
584        _ => todo!(),
585    }
586}
587
588/// Construct the string representation for a list of expected parser tokens.
589fn expected_syntax(one_of: &[rnix::SyntaxKind]) -> String {
590    match one_of.len() {
591        0 => "nothing".into(),
592        1 => format!("'{}'", name_for_syntax(&one_of[0])),
593        _ => {
594            let mut out: String = "one of: ".into();
595            let end = one_of.len() - 1;
596
597            for (idx, item) in one_of.iter().enumerate() {
598                if idx != 0 {
599                    out.push_str(", ");
600                } else if idx == end {
601                    out.push_str(", or ");
602                };
603
604                out.push_str(name_for_syntax(item));
605            }
606
607            out
608        }
609    }
610}
611
612/// Process a list of parse errors into a set of span labels, annotating parse
613/// errors.
614fn spans_for_parse_errors(file: &File, errors: &[rnix::ParseError]) -> Vec<SpanLabel> {
615    // rnix has a tendency to emit some identical errors more than once, but
616    // they do not enhance the user experience necessarily, so we filter them
617    // out
618    let mut had_eof = false;
619
620    errors
621        .iter()
622        .enumerate()
623        .filter_map(|(idx, err)| {
624            let (span, label): (Span, String) = match err {
625                rnix::ParseError::Unexpected(range) => (
626                    range.span_for(file),
627                    "found an unexpected syntax element here".into(),
628                ),
629
630                rnix::ParseError::UnexpectedExtra(range) => (
631                    range.span_for(file),
632                    "found unexpected extra elements at the root of the expression".into(),
633                ),
634
635                rnix::ParseError::UnexpectedWanted(found, range, wanted) => {
636                    let span = range.span_for(file);
637                    (
638                        span,
639                        format!(
640                            "found '{}', but expected {}",
641                            name_for_syntax(found),
642                            expected_syntax(wanted),
643                        ),
644                    )
645                }
646
647                rnix::ParseError::UnexpectedEOF => {
648                    if had_eof {
649                        return None;
650                    }
651
652                    had_eof = true;
653
654                    (
655                        file.span,
656                        "code ended unexpectedly while the parser still expected more".into(),
657                    )
658                }
659
660                rnix::ParseError::UnexpectedEOFWanted(wanted) => {
661                    had_eof = true;
662
663                    (
664                        file.span,
665                        format!(
666                            "code ended unexpectedly, but wanted {}",
667                            expected_syntax(wanted)
668                        ),
669                    )
670                }
671
672                rnix::ParseError::DuplicatedArgs(range, name) => (
673                    range.span_for(file),
674                    format!("the function argument pattern '{name}' was bound more than once"),
675                ),
676
677                rnix::ParseError::RecursionLimitExceeded => (
678                    file.span,
679                    "this code exceeds the parser's recursion limit, please report a Snix bug"
680                        .to_string(),
681                ),
682
683                // TODO: can rnix even still throw this? it's semantic!
684                rnix::ParseError::UnexpectedDoubleBind(range) => (
685                    range.span_for(file),
686                    "this pattern was bound more than once".into(),
687                ),
688
689                // The error enum is marked as `#[non_exhaustive]` in rnix,
690                // which disables the compiler error for missing a variant. This
691                // feature makes it possible for users to miss critical updates
692                // of enum variants for a more exciting runtime experience.
693                new => todo!("new parse error variant: {}", new),
694            };
695
696            Some(SpanLabel {
697                span,
698                label: Some(label),
699                style: if idx == 0 {
700                    SpanStyle::Primary
701                } else {
702                    SpanStyle::Secondary
703                },
704            })
705        })
706        .collect()
707}
708
709impl Error {
710    pub fn fancy_format_str(&self) -> String {
711        let mut out = vec![];
712        Emitter::vec(&mut out, Some(&*self.source.codemap())).emit(&self.diagnostics());
713        String::from_utf8_lossy(&out).to_string()
714    }
715
716    /// Render a fancy, human-readable output of this error and print
717    /// it to stderr.
718    pub fn fancy_format_stderr(&self) {
719        Emitter::stderr(ColorConfig::Auto, Some(&*self.source.codemap())).emit(&self.diagnostics());
720    }
721
722    /// Render a fancy, human-readable output of this error and print
723    /// it to a std::io::Write.
724    pub fn fancy_format_write<E: Write + Send>(&self, stderr: &mut E) {
725        Emitter::new(Box::new(stderr), Some(&*self.source.codemap())).emit(&self.diagnostics());
726    }
727
728    /// Create the optional span label displayed as an annotation on
729    /// the underlined span of the error.
730    fn span_label(&self) -> Option<String> {
731        let label = match &self.kind {
732            ErrorKind::DuplicateAttrsKey { .. } => "in this attribute set",
733            ErrorKind::InvalidAttributeName(_) => "in this attribute set",
734            ErrorKind::RelativePathResolution(_) => "in this path literal",
735            ErrorKind::UnexpectedArgumentBuiltin { .. } => "while calling this builtin",
736            ErrorKind::UnexpectedArgumentFormals { .. } => "in this function call",
737            ErrorKind::UnexpectedContext => "in this string",
738
739            // The spans for some errors don't have any more descriptive stuff
740            // in them, or we don't utilise it yet.
741            ErrorKind::Abort(_)
742            | ErrorKind::AttributeNotFound { .. }
743            | ErrorKind::IndexOutOfBounds { .. }
744            | ErrorKind::TailEmptyList
745            | ErrorKind::TypeError { .. }
746            | ErrorKind::Incomparable { .. }
747            | ErrorKind::DivisionByZero
748            | ErrorKind::DynamicKeyInScope(_)
749            | ErrorKind::UnknownStaticVariable
750            | ErrorKind::UnknownDynamicVariable(_)
751            | ErrorKind::VariableAlreadyDefined(_)
752            | ErrorKind::NotCallable(_)
753            | ErrorKind::InfiniteRecursion { .. }
754            | ErrorKind::ParseErrors(_)
755            | ErrorKind::NativeError { .. }
756            | ErrorKind::BytecodeError(_)
757            | ErrorKind::NotCoercibleToString { .. }
758            | ErrorKind::NotAnAbsolutePath(_)
759            | ErrorKind::ParseIntError(_)
760            | ErrorKind::UnmergeableInherit { .. }
761            | ErrorKind::UnmergeableValue
762            | ErrorKind::ImportParseError { .. }
763            | ErrorKind::ImportCompilerError { .. }
764            | ErrorKind::IO { .. }
765            | ErrorKind::JsonError(_)
766            | ErrorKind::NotSerialisableToJson(_)
767            | ErrorKind::FromTomlError(_)
768            | ErrorKind::Utf8
769            | ErrorKind::SnixError(_)
770            | ErrorKind::SnixBug { .. }
771            | ErrorKind::NotImplemented(_)
772            | ErrorKind::WithContext { .. }
773            | ErrorKind::UnknownHashType(_)
774            | ErrorKind::InvalidHash(_)
775            | ErrorKind::InvalidRegex(_)
776            | ErrorKind::CatchableError(_) => return None,
777        };
778
779        Some(label.into())
780    }
781
782    /// Return the unique error code for this variant which can be
783    /// used to refer users to documentation.
784    fn code(&self) -> &'static str {
785        match self.kind {
786            ErrorKind::CatchableError(CatchableErrorKind::Throw(_)) => "E001",
787            ErrorKind::Abort(_) => "E002",
788            ErrorKind::CatchableError(CatchableErrorKind::AssertionFailed) => "E003",
789            ErrorKind::InvalidAttributeName { .. } => "E004",
790            ErrorKind::AttributeNotFound { .. } => "E005",
791            ErrorKind::TypeError { .. } => "E006",
792            ErrorKind::Incomparable { .. } => "E007",
793            ErrorKind::CatchableError(CatchableErrorKind::NixPathResolution(_)) => "E008",
794            ErrorKind::DynamicKeyInScope(_) => "E009",
795            ErrorKind::UnknownStaticVariable => "E010",
796            ErrorKind::UnknownDynamicVariable(_) => "E011",
797            ErrorKind::VariableAlreadyDefined(_) => "E012",
798            ErrorKind::NotCallable(_) => "E013",
799            ErrorKind::InfiniteRecursion { .. } => "E014",
800            ErrorKind::ParseErrors(_) => "E015",
801            ErrorKind::DuplicateAttrsKey { .. } => "E016",
802            ErrorKind::NotCoercibleToString { .. } => "E018",
803            ErrorKind::IndexOutOfBounds { .. } => "E019",
804            ErrorKind::NotAnAbsolutePath(_) => "E020",
805            ErrorKind::ParseIntError(_) => "E021",
806            ErrorKind::TailEmptyList => "E023",
807            ErrorKind::UnmergeableInherit { .. } => "E024",
808            ErrorKind::UnmergeableValue => "E025",
809            ErrorKind::ImportParseError { .. } => "E027",
810            ErrorKind::ImportCompilerError { .. } => "E028",
811            ErrorKind::IO { .. } => "E029",
812            ErrorKind::JsonError { .. } => "E030",
813            ErrorKind::UnexpectedArgumentFormals { .. } => "E031",
814            ErrorKind::RelativePathResolution(_) => "E032",
815            ErrorKind::DivisionByZero => "E033",
816            ErrorKind::FromTomlError(_) => "E035",
817            ErrorKind::NotSerialisableToJson(_) => "E036",
818            ErrorKind::UnexpectedContext => "E037",
819            ErrorKind::Utf8 => "E038",
820            ErrorKind::UnknownHashType(_) => "E039",
821            ErrorKind::UnexpectedArgumentBuiltin { .. } => "E040",
822            ErrorKind::InvalidHash(_) => "E041",
823            ErrorKind::InvalidRegex(_) => "E042",
824
825            // Special error code for errors from other Snix
826            // components. We may want to introduce a code namespacing
827            // system to have these errors pass codes through.
828            ErrorKind::SnixError(_) => "E997",
829
830            // Special error code that is not part of the normal
831            // ordering.
832            ErrorKind::SnixBug { .. } => "E998",
833
834            // Placeholder error while Snix is under construction.
835            ErrorKind::CatchableError(CatchableErrorKind::UnimplementedFeature(_))
836            | ErrorKind::NotImplemented(_) => "E999",
837
838            // Chained errors should yield the code of the innermost
839            // error.
840            ErrorKind::NativeError { ref err, .. } | ErrorKind::BytecodeError(ref err) => {
841                err.code()
842            }
843
844            ErrorKind::WithContext { .. } => {
845                panic!("internal ErrorKind::WithContext variant leaked")
846            }
847        }
848    }
849
850    fn spans(&self) -> Vec<SpanLabel> {
851        let mut spans = match &self.kind {
852            ErrorKind::ImportParseError { errors, file, .. } => {
853                spans_for_parse_errors(file, errors)
854            }
855
856            ErrorKind::ParseErrors(errors) => {
857                let file = self.source.get_file(self.span);
858                spans_for_parse_errors(&file, errors)
859            }
860
861            ErrorKind::UnexpectedArgumentFormals { formals_span, .. } => {
862                vec![
863                    SpanLabel {
864                        label: self.span_label(),
865                        span: self.span,
866                        style: SpanStyle::Primary,
867                    },
868                    SpanLabel {
869                        label: Some("the accepted arguments".into()),
870                        span: *formals_span,
871                        style: SpanStyle::Secondary,
872                    },
873                ]
874            }
875
876            ErrorKind::InfiniteRecursion {
877                first_force,
878                suspended_at,
879                content_span,
880            } => {
881                let mut spans = vec![];
882
883                if let Some(content_span) = content_span {
884                    spans.push(SpanLabel {
885                        label: Some("this lazily-evaluated code".into()),
886                        span: *content_span,
887                        style: SpanStyle::Secondary,
888                    })
889                }
890
891                if let Some(suspended_at) = suspended_at {
892                    spans.push(SpanLabel {
893                        label: Some("which was instantiated here".into()),
894                        span: *suspended_at,
895                        style: SpanStyle::Secondary,
896                    })
897                }
898
899                spans.push(SpanLabel {
900                    label: Some("was first requested to be evaluated here".into()),
901                    span: *first_force,
902                    style: SpanStyle::Secondary,
903                });
904
905                spans.push(SpanLabel {
906                    label: Some("but then requested again here during its own evaluation".into()),
907                    span: self.span,
908                    style: SpanStyle::Primary,
909                });
910
911                spans
912            }
913
914            // All other errors pretty much have the same shape.
915            _ => {
916                vec![SpanLabel {
917                    label: self.span_label(),
918                    span: self.span,
919                    style: SpanStyle::Primary,
920                }]
921            }
922        };
923
924        for ctx in &self.contexts {
925            spans.push(SpanLabel {
926                label: Some(format!("while {ctx}")),
927                span: self.span,
928                style: SpanStyle::Secondary,
929            });
930        }
931
932        spans
933    }
934
935    /// Create the primary diagnostic for a given error.
936    fn diagnostic(&self) -> Diagnostic {
937        Diagnostic {
938            level: Level::Error,
939            message: self.to_string(),
940            spans: self.spans(),
941            code: Some(self.code().into()),
942        }
943    }
944
945    /// Return the primary diagnostic and all further associated diagnostics (if
946    /// any) of an error.
947    fn diagnostics(&self) -> Vec<Diagnostic> {
948        match &self.kind {
949            ErrorKind::ImportCompilerError { errors, .. } => {
950                let mut out = vec![self.diagnostic()];
951                out.extend(errors.iter().map(|e| e.diagnostic()));
952                out
953            }
954
955            // When encountering either of these error kinds, we are dealing
956            // with the top of an error chain.
957            //
958            // An error chain creates a list of diagnostics which provide trace
959            // information.
960            //
961            // We don't know how deep this chain is, so we avoid recursing in
962            // this function while unrolling the chain.
963            ErrorKind::NativeError { err: next, .. } | ErrorKind::BytecodeError(next) => {
964                // Accumulated diagnostics to return.
965                let mut diagnostics: Vec<Diagnostic> = vec![];
966
967                // The next (inner) error to add to the diagnostics, after this
968                // one.
969                let mut next = *next.clone();
970
971                // Diagnostic message for *this* error.
972                let mut this_message = self.to_string();
973
974                // Primary span for *this* error.
975                let mut this_span = self.span;
976
977                // Diagnostic spans for *this* error.
978                let mut this_spans = self.spans();
979
980                loop {
981                    if is_new_span(
982                        this_span,
983                        diagnostics.last().and_then(|last| last.spans.last()),
984                    ) {
985                        diagnostics.push(Diagnostic {
986                            level: Level::Note,
987                            message: this_message,
988                            spans: this_spans,
989                            code: None, // only the top-level error has one
990                        });
991                    }
992
993                    this_message = next.to_string();
994                    this_span = next.span;
995                    this_spans = next.spans();
996
997                    match next.kind {
998                        ErrorKind::NativeError { err: inner, .. }
999                        | ErrorKind::BytecodeError(inner) => {
1000                            next = *inner;
1001                            continue;
1002                        }
1003                        _ => {
1004                            diagnostics.extend(next.diagnostics());
1005                            break;
1006                        }
1007                    }
1008                }
1009
1010                diagnostics
1011            }
1012
1013            _ => vec![self.diagnostic()],
1014        }
1015    }
1016}
1017
1018// Check if this error is in a different span from its immediate ancestor.
1019fn is_new_span(this_span: Span, parent: Option<&SpanLabel>) -> bool {
1020    match parent {
1021        None => true,
1022        Some(parent) => parent.span != this_span,
1023    }
1024}
1025
1026// Convenience methods to add context on other types.
1027pub trait AddContext {
1028    /// Add context to the error-carrying type.
1029    fn context<S: Into<String>>(self, ctx: S) -> Self;
1030}
1031
1032impl AddContext for ErrorKind {
1033    fn context<S: Into<String>>(self, ctx: S) -> Self {
1034        ErrorKind::WithContext {
1035            context: ctx.into(),
1036            underlying: Box::new(self),
1037        }
1038    }
1039}
1040
1041impl<T> AddContext for Result<T, ErrorKind> {
1042    fn context<S: Into<String>>(self, ctx: S) -> Self {
1043        self.map_err(|kind| kind.context(ctx))
1044    }
1045}
1046
1047impl<T> AddContext for Result<T, Error> {
1048    fn context<S: Into<String>>(self, ctx: S) -> Self {
1049        self.map_err(|err| Error {
1050            kind: err.kind.context(ctx),
1051            ..err
1052        })
1053    }
1054}