tvix_eval/
errors.rs

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