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#[macro_export]
58macro_rules! try_cek {
59 ($result:expr) => {
60 match $result {
61 Ok(s) => s,
62 Err(cek) => {
63 let cek: $crate::CatchableErrorKind = cek;
65 return Ok(Err(cek));
66 }
67 }
68 };
69}
70
71#[macro_export]
81macro_rules! try_cek_to_value {
82 ($result:expr) => {
83 match $result {
84 Ok(s) => s,
85 Err(cek) => {
86 let cek: $crate::CatchableErrorKind = cek;
88 return Ok(Value::Catchable(Box::new(cek)));
89 }
90 }
91 };
92}
93
94#[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 #[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 #[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 #[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 #[error("list index '{index}' is out of bounds")]
156 IndexOutOfBounds { index: i64 },
157
158 #[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 #[error("could not resolve path: {0}")]
176 RelativePathResolution(String),
177
178 #[error("dynamically evaluated keys are not allowed in {0}")]
180 DynamicKeyInScope(&'static str),
181
182 #[error("variable not found")]
184 UnknownStaticVariable,
185
186 #[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 #[error("variable has already been defined")]
197 VariableAlreadyDefined(Option<Span>),
198
199 #[error("only functions and builtins can be called, but this is a '{0}'")]
201 NotCallable(&'static str),
202
203 #[error("infinite recursion encountered")]
205 InfiniteRecursion {
206 first_force: Span,
207 suspended_at: Option<Span>,
208 content_span: Option<Span>,
209 },
210
211 #[error("failed to parse Nix code:")]
213 ParseErrors(Vec<rnix::ParseError>),
214
215 #[error("while evaluating this as native code ({gen_type})")]
218 NativeError {
219 gen_type: &'static str,
220 err: Box<Error>,
221 },
222
223 #[error("while evaluating this Nix code")]
226 BytecodeError(Box<Error>),
227
228 #[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 #[error("string '{}' does not represent an absolute path", .0.to_string_lossy())]
244 NotAnAbsolutePath(PathBuf),
245
246 #[error("invalid integer: {0}")]
248 ParseIntError(ParseIntError),
249
250 #[error("cannot merge a nested attribute set into the inherited entry '{name}'")]
253 UnmergeableInherit { name: SmolStr },
254
255 #[error("nested attribute sets or keys can only be merged with literal attribute sets")]
258 UnmergeableValue,
259
260 #[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 #[error("compiler errors occured while importing '{}'", .path.to_string_lossy())]
271 ImportCompilerError { path: PathBuf, errors: Vec<Error> },
272
273 #[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 #[error("Error converting JSON to a Nix value or back: {0}")]
294 JsonError(String),
295
296 #[error("a {0} cannot be converted to JSON")]
298 NotSerialisableToJson(&'static str),
299
300 #[error("Error converting TOML to a Nix value: {0}")]
302 FromTomlError(String),
303
304 #[error("Unexpected agrument `{0}` passed to builtin")]
306 UnexpectedArgumentBuiltin(NixString),
307
308 #[error("Unexpected argument `{arg}` supplied to function")]
310 UnexpectedArgumentFormals { arg: NixString, formals_span: Span },
311
312 #[error("Invalid UTF-8 in string")]
314 Utf8,
315
316 #[error("Invalid hash: {0}")]
317 InvalidHash(String),
318
319 #[error("{0}")]
322 SnixError(Arc<dyn error::Error + Send + Sync>),
323
324 #[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 #[error("feature not yet implemented in Snix: {0}")]
346 NotImplemented(&'static str),
347
348 #[error("internal ErrorKind::WithContext variant leaked")]
350 WithContext {
351 context: String,
352 underlying: Box<ErrorKind>,
353 },
354
355 #[error("unexpected context string")]
357 UnexpectedContext,
358
359 #[error("{0}")]
366 CatchableError(CatchableErrorKind),
367
368 #[error("unknown hash type '{0}'")]
371 UnknownHashType(String),
372
373 #[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 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
486fn 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 rnix::SyntaxKind::NODE_ERROR => todo!("NODE_ERROR found, open a bug with a reproducer"),
584 _ => todo!(),
585 }
586}
587
588fn 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
612fn spans_for_parse_errors(file: &File, errors: &[rnix::ParseError]) -> Vec<SpanLabel> {
615 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 rnix::ParseError::UnexpectedDoubleBind(range) => (
685 range.span_for(file),
686 "this pattern was bound more than once".into(),
687 ),
688
689 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 pub fn fancy_format_stderr(&self) {
719 Emitter::stderr(ColorConfig::Auto, Some(&*self.source.codemap())).emit(&self.diagnostics());
720 }
721
722 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 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 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 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 ErrorKind::SnixError(_) => "E997",
829
830 ErrorKind::SnixBug { .. } => "E998",
833
834 ErrorKind::CatchableError(CatchableErrorKind::UnimplementedFeature(_))
836 | ErrorKind::NotImplemented(_) => "E999",
837
838 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 _ => {
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 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 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 ErrorKind::NativeError { err: next, .. } | ErrorKind::BytecodeError(next) => {
964 let mut diagnostics: Vec<Diagnostic> = vec![];
966
967 let mut next = *next.clone();
970
971 let mut this_message = self.to_string();
973
974 let mut this_span = self.span;
976
977 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, });
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
1018fn 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
1026pub trait AddContext {
1028 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}