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