nu_errors/
lib.rs

1use bigdecimal::BigDecimal;
2use codespan_reporting::diagnostic::{Diagnostic, Label};
3use derive_new::new;
4use getset::Getters;
5use nu_ansi_term::Color;
6use nu_source::{
7    DbgDocBldr, DebugDocBuilder, HasFallibleSpan, PrettyDebug, Span, Spanned, SpannedItem,
8};
9use num_bigint::BigInt;
10use num_traits::ToPrimitive;
11use serde::{Deserialize, Serialize};
12use std::fmt;
13use std::ops::Range;
14
15/// A structured reason for a ParseError. Note that parsing in nu is more like macro expansion in
16/// other languages, so the kinds of errors that can occur during parsing are more contextual than
17/// you might expect.
18#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
19pub enum ParseErrorReason {
20    /// The parser encountered an EOF rather than what it was expecting
21    Eof { expected: String, span: Span },
22    /// The parser expected to see the end of a token stream (possibly the token
23    /// stream from inside a delimited token node), but found something else.
24    ExtraTokens { actual: Spanned<String> },
25    /// The parser encountered something other than what it was expecting
26    Mismatch {
27        expected: String,
28        actual: Spanned<String>,
29    },
30
31    /// Unclosed delimiter
32    Unclosed { delimiter: String, span: Span },
33
34    /// An unexpected internal error has occurred
35    GeneralError {
36        message: String,
37        label: Spanned<String>,
38    },
39
40    /// The parser tried to parse an argument for a command, but it failed for
41    /// some reason
42    ArgumentError {
43        command: Spanned<String>,
44        error: ArgumentError,
45    },
46}
47
48/// A newtype for `ParseErrorReason`
49#[derive(Debug, Clone, Getters, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
50pub struct ParseError {
51    #[get = "pub"]
52    reason: ParseErrorReason,
53}
54
55impl ParseError {
56    /// Construct a [ParseErrorReason::Eof](ParseErrorReason::Eof)
57    pub fn unexpected_eof(expected: impl Into<String>, span: Span) -> ParseError {
58        ParseError {
59            reason: ParseErrorReason::Eof {
60                expected: expected.into(),
61                span,
62            },
63        }
64    }
65
66    /// Construct a [ParseErrorReason::ExtraTokens](ParseErrorReason::ExtraTokens)
67    pub fn extra_tokens(actual: Spanned<impl Into<String>>) -> ParseError {
68        let Spanned { span, item } = actual;
69
70        ParseError {
71            reason: ParseErrorReason::ExtraTokens {
72                actual: item.into().spanned(span),
73            },
74        }
75    }
76
77    /// Construct a [ParseErrorReason::Mismatch](ParseErrorReason::Mismatch)
78    pub fn mismatch(expected: impl Into<String>, actual: Spanned<impl Into<String>>) -> ParseError {
79        let Spanned { span, item } = actual;
80
81        ParseError {
82            reason: ParseErrorReason::Mismatch {
83                expected: expected.into(),
84                actual: item.into().spanned(span),
85            },
86        }
87    }
88
89    /// Construct a [ParseErrorReason::GeneralError](ParseErrorReason::GeneralError)
90    pub fn general_error(
91        message: impl Into<String>,
92        label: Spanned<impl Into<String>>,
93    ) -> ParseError {
94        ParseError {
95            reason: ParseErrorReason::GeneralError {
96                message: message.into(),
97                label: label.item.into().spanned(label.span),
98            },
99        }
100    }
101
102    /// Construct a [ParseErrorReason::ArgumentError](ParseErrorReason::ArgumentError)
103    pub fn argument_error(command: Spanned<impl Into<String>>, kind: ArgumentError) -> ParseError {
104        ParseError {
105            reason: ParseErrorReason::ArgumentError {
106                command: command.item.into().spanned(command.span),
107                error: kind,
108            },
109        }
110    }
111
112    /// Unclosed delimiter
113    pub fn unclosed(delimiter: String, span: Span) -> ParseError {
114        ParseError {
115            reason: ParseErrorReason::Unclosed { delimiter, span },
116        }
117    }
118}
119
120/// Convert a [ParseError](ParseError) into a [ShellError](ShellError)
121impl From<ParseError> for ShellError {
122    fn from(error: ParseError) -> ShellError {
123        match error.reason {
124            ParseErrorReason::Eof { expected, span } => ShellError::unexpected_eof(expected, span),
125            ParseErrorReason::ExtraTokens { actual } => ShellError::type_error("nothing", actual),
126            ParseErrorReason::Mismatch { actual, expected } => {
127                ShellError::type_error(expected, actual)
128            }
129            ParseErrorReason::GeneralError { message, label } => {
130                ShellError::labeled_error(&message, &label.item, &label.span)
131            }
132            ParseErrorReason::ArgumentError { command, error } => {
133                ShellError::argument_error(command, error)
134            }
135            ParseErrorReason::Unclosed { delimiter, span } => ShellError::labeled_error(
136                "Unclosed delimiter",
137                format!("expected '{}'", delimiter),
138                span,
139            ),
140        }
141    }
142}
143
144/// ArgumentError describes various ways that the parser could fail because of unexpected arguments.
145/// Nu commands are like a combination of functions and macros, and these errors correspond to
146/// problems that could be identified during expansion based on the syntactic signature of a
147/// command.
148#[derive(Debug, Eq, PartialEq, Clone, Ord, Hash, PartialOrd, Serialize, Deserialize)]
149pub enum ArgumentError {
150    /// The command specified a mandatory flag, but it was missing.
151    MissingMandatoryFlag(String),
152    /// The command specified a mandatory positional argument, but it was missing.
153    MissingMandatoryPositional(String),
154    /// A flag was found, and it should have been followed by a value, but no value was found
155    MissingValueForName(String),
156    /// An argument was found, but the command does not recognize it
157    UnexpectedArgument(Spanned<String>),
158    /// An flag was found, but the command does not recognize it
159    UnexpectedFlag(Spanned<String>),
160    /// A sequence of characters was found that was not syntactically valid (but would have
161    /// been valid if the command was an external command)
162    InvalidExternalWord,
163    /// A bad value in this location
164    BadValue(String),
165}
166
167impl PrettyDebug for ArgumentError {
168    fn pretty(&self) -> DebugDocBuilder {
169        match self {
170            ArgumentError::MissingMandatoryFlag(flag) => {
171                DbgDocBldr::description("missing `")
172                    + DbgDocBldr::description(flag)
173                    + DbgDocBldr::description("` as mandatory flag")
174            }
175            ArgumentError::UnexpectedArgument(name) => {
176                DbgDocBldr::description("unexpected `")
177                    + DbgDocBldr::description(&name.item)
178                    + DbgDocBldr::description("` is not supported")
179            }
180            ArgumentError::UnexpectedFlag(name) => {
181                DbgDocBldr::description("unexpected `")
182                    + DbgDocBldr::description(&name.item)
183                    + DbgDocBldr::description("` is not supported")
184            }
185            ArgumentError::MissingMandatoryPositional(pos) => {
186                DbgDocBldr::description("missing `")
187                    + DbgDocBldr::description(pos)
188                    + DbgDocBldr::description("` as mandatory positional argument")
189            }
190            ArgumentError::MissingValueForName(name) => {
191                DbgDocBldr::description("missing value for flag `")
192                    + DbgDocBldr::description(name)
193                    + DbgDocBldr::description("`")
194            }
195            ArgumentError::InvalidExternalWord => DbgDocBldr::description("invalid word"),
196            ArgumentError::BadValue(msg) => {
197                DbgDocBldr::description("bad value `")
198                    + DbgDocBldr::description(msg)
199                    + DbgDocBldr::description("`")
200            }
201        }
202    }
203}
204
205/// A `ShellError` is a proximate error and a possible cause, which could have its own cause,
206/// creating a cause chain.
207#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize, Deserialize, Hash)]
208pub struct ShellError {
209    pub error: ProximateShellError,
210    pub notes: Vec<String>,
211}
212
213/// `PrettyDebug` is for internal debugging. For user-facing debugging, [into_diagnostic](ShellError::into_diagnostic)
214/// is used, which prints an error, highlighting spans.
215impl PrettyDebug for ShellError {
216    fn pretty(&self) -> DebugDocBuilder {
217        match &self.error {
218            ProximateShellError::SyntaxError { problem } => {
219                DbgDocBldr::error("Syntax Error")
220                    + DbgDocBldr::space()
221                    + DbgDocBldr::delimit("(", DbgDocBldr::description(&problem.item), ")")
222            }
223            ProximateShellError::UnexpectedEof { .. } => DbgDocBldr::error("Unexpected end"),
224            ProximateShellError::TypeError { expected, actual } => {
225                DbgDocBldr::error("Type Error")
226                    + DbgDocBldr::space()
227                    + DbgDocBldr::delimit(
228                        "(",
229                        DbgDocBldr::description("expected:")
230                            + DbgDocBldr::space()
231                            + DbgDocBldr::description(expected)
232                            + DbgDocBldr::description(",")
233                            + DbgDocBldr::space()
234                            + DbgDocBldr::description("actual:")
235                            + DbgDocBldr::space()
236                            + DbgDocBldr::option(actual.item.as_ref().map(DbgDocBldr::description)),
237                        ")",
238                    )
239            }
240            ProximateShellError::MissingProperty { subpath, expr } => {
241                DbgDocBldr::error("Missing Property")
242                    + DbgDocBldr::space()
243                    + DbgDocBldr::delimit(
244                        "(",
245                        DbgDocBldr::description("expr:")
246                            + DbgDocBldr::space()
247                            + DbgDocBldr::description(&expr.item)
248                            + DbgDocBldr::description(",")
249                            + DbgDocBldr::space()
250                            + DbgDocBldr::description("subpath:")
251                            + DbgDocBldr::space()
252                            + DbgDocBldr::description(&subpath.item),
253                        ")",
254                    )
255            }
256            ProximateShellError::InvalidIntegerIndex { subpath, .. } => {
257                DbgDocBldr::error("Invalid integer index")
258                    + DbgDocBldr::space()
259                    + DbgDocBldr::delimit(
260                        "(",
261                        DbgDocBldr::description("subpath:")
262                            + DbgDocBldr::space()
263                            + DbgDocBldr::description(&subpath.item),
264                        ")",
265                    )
266            }
267            ProximateShellError::MissingValue { reason, .. } => {
268                DbgDocBldr::error("Missing Value")
269                    + DbgDocBldr::space()
270                    + DbgDocBldr::delimit(
271                        "(",
272                        DbgDocBldr::description("reason:")
273                            + DbgDocBldr::space()
274                            + DbgDocBldr::description(reason),
275                        ")",
276                    )
277            }
278            ProximateShellError::ArgumentError { command, error } => {
279                DbgDocBldr::error("Argument Error")
280                    + DbgDocBldr::space()
281                    + DbgDocBldr::delimit(
282                        "(",
283                        DbgDocBldr::description("command:")
284                            + DbgDocBldr::space()
285                            + DbgDocBldr::description(&command.item)
286                            + DbgDocBldr::description(",")
287                            + DbgDocBldr::space()
288                            + DbgDocBldr::description("error:")
289                            + DbgDocBldr::space()
290                            + error.pretty(),
291                        ")",
292                    )
293            }
294            ProximateShellError::RangeError {
295                kind,
296                actual_kind,
297                operation,
298            } => {
299                DbgDocBldr::error("Range Error")
300                    + DbgDocBldr::space()
301                    + DbgDocBldr::delimit(
302                        "(",
303                        DbgDocBldr::description("expected:")
304                            + DbgDocBldr::space()
305                            + kind.pretty()
306                            + DbgDocBldr::description(",")
307                            + DbgDocBldr::space()
308                            + DbgDocBldr::description("actual:")
309                            + DbgDocBldr::space()
310                            + DbgDocBldr::description(&actual_kind.item)
311                            + DbgDocBldr::description(",")
312                            + DbgDocBldr::space()
313                            + DbgDocBldr::description("operation:")
314                            + DbgDocBldr::space()
315                            + DbgDocBldr::description(operation),
316                        ")",
317                    )
318            }
319            ProximateShellError::Diagnostic(_) => DbgDocBldr::error("diagnostic"),
320            ProximateShellError::CoerceError { left, right } => {
321                DbgDocBldr::error("Coercion Error")
322                    + DbgDocBldr::space()
323                    + DbgDocBldr::delimit(
324                        "(",
325                        DbgDocBldr::description("left:")
326                            + DbgDocBldr::space()
327                            + DbgDocBldr::description(&left.item)
328                            + DbgDocBldr::description(",")
329                            + DbgDocBldr::space()
330                            + DbgDocBldr::description("right:")
331                            + DbgDocBldr::space()
332                            + DbgDocBldr::description(&right.item),
333                        ")",
334                    )
335            }
336            ProximateShellError::UntaggedRuntimeError { reason } => {
337                DbgDocBldr::error("Unknown Error")
338                    + DbgDocBldr::delimit("(", DbgDocBldr::description(reason), ")")
339            }
340            ProximateShellError::Unimplemented { reason } => {
341                DbgDocBldr::error("Unimplemented")
342                    + DbgDocBldr::delimit("(", DbgDocBldr::description(reason), ")")
343            }
344        }
345    }
346}
347
348impl std::fmt::Display for ShellError {
349    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350        write!(f, "{}", self.pretty().display())
351    }
352}
353
354impl serde::de::Error for ShellError {
355    fn custom<T>(msg: T) -> Self
356    where
357        T: std::fmt::Display,
358    {
359        ShellError::untagged_runtime_error(msg.to_string())
360    }
361}
362
363impl ShellError {
364    /// An error that describes a mismatch between the given type and the expected type
365    pub fn type_error(
366        expected: impl Into<String>,
367        actual: Spanned<impl Into<String>>,
368    ) -> ShellError {
369        ProximateShellError::TypeError {
370            expected: expected.into(),
371            actual: actual.map(|i| Some(i.into())),
372        }
373        .start()
374    }
375
376    pub fn missing_property(
377        subpath: Spanned<impl Into<String>>,
378        expr: Spanned<impl Into<String>>,
379    ) -> ShellError {
380        ProximateShellError::MissingProperty {
381            subpath: subpath.map(|s| s.into()),
382            expr: expr.map(|e| e.into()),
383        }
384        .start()
385    }
386
387    pub fn missing_value(span: impl Into<Option<Span>>, reason: impl Into<String>) -> ShellError {
388        ProximateShellError::MissingValue {
389            span: span.into(),
390            reason: reason.into(),
391        }
392        .start()
393    }
394
395    pub fn invalid_integer_index(
396        subpath: Spanned<impl Into<String>>,
397        integer: impl Into<Span>,
398    ) -> ShellError {
399        ProximateShellError::InvalidIntegerIndex {
400            subpath: subpath.map(|s| s.into()),
401            integer: integer.into(),
402        }
403        .start()
404    }
405
406    pub fn untagged_runtime_error(error: impl Into<String>) -> ShellError {
407        ProximateShellError::UntaggedRuntimeError {
408            reason: error.into(),
409        }
410        .start()
411    }
412
413    pub fn unexpected_eof(expected: impl Into<String>, span: impl Into<Span>) -> ShellError {
414        ProximateShellError::UnexpectedEof {
415            expected: expected.into(),
416            span: span.into(),
417        }
418        .start()
419    }
420
421    pub fn range_error(
422        expected: impl Into<ExpectedRange>,
423        actual: &Spanned<impl fmt::Debug>,
424        operation: impl Into<String>,
425    ) -> ShellError {
426        ProximateShellError::RangeError {
427            kind: expected.into(),
428            actual_kind: format!("{:?}", actual.item).spanned(actual.span),
429            operation: operation.into(),
430        }
431        .start()
432    }
433
434    pub fn syntax_error(problem: Spanned<impl Into<String>>) -> ShellError {
435        ProximateShellError::SyntaxError {
436            problem: problem.map(|p| p.into()),
437        }
438        .start()
439    }
440
441    pub fn coerce_error(
442        left: Spanned<impl Into<String>>,
443        right: Spanned<impl Into<String>>,
444    ) -> ShellError {
445        ProximateShellError::CoerceError {
446            left: left.map(|l| l.into()),
447            right: right.map(|r| r.into()),
448        }
449        .start()
450    }
451
452    pub fn argument_error(command: Spanned<impl Into<String>>, kind: ArgumentError) -> ShellError {
453        ProximateShellError::ArgumentError {
454            command: command.map(|c| c.into()),
455            error: kind,
456        }
457        .start()
458    }
459
460    pub fn diagnostic(diagnostic: Diagnostic<usize>) -> ShellError {
461        ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }).start()
462    }
463
464    pub fn into_diagnostic(self) -> Diagnostic<usize> {
465        let d = match self.error {
466            ProximateShellError::MissingValue { span, reason } => {
467                let mut d = Diagnostic::bug().with_message(format!("Internal Error (missing value) :: {}", reason));
468
469                if let Some(span) = span {
470                    d = d.with_labels(vec![Label::primary(0, span)]);
471                }
472
473                d
474            }
475            ProximateShellError::ArgumentError {
476                command,
477                error,
478            } => match error {
479                ArgumentError::InvalidExternalWord => Diagnostic::error().with_message("Invalid bare word for Nu command (did you intend to invoke an external command?)")
480                .with_labels(vec![Label::primary(0, command.span)]),
481                ArgumentError::UnexpectedArgument(argument) => Diagnostic::error().with_message(
482                    format!(
483                        "{} unexpected {}",
484                        Color::Cyan.paint(&command.item),
485                        Color::Green.bold().paint(&argument.item)
486                    )
487                )
488                .with_labels(
489                    vec![Label::primary(0, argument.span).with_message(
490                        format!("unexpected argument (try {} -h)", &command.item))]
491                ),
492                ArgumentError::UnexpectedFlag(flag) => Diagnostic::error().with_message(
493                    format!(
494                        "{} unexpected {}",
495                        Color::Cyan.paint(&command.item),
496                        Color::Green.bold().paint(&flag.item)
497                    ),
498                )
499                .with_labels(vec![
500                    Label::primary(0, flag.span).with_message(
501                    format!("unexpected flag (try {} -h)", &command.item))
502                    ]),
503                ArgumentError::MissingMandatoryFlag(name) => Diagnostic::error().with_message(                    format!(
504                        "{} requires {}{}",
505                        Color::Cyan.paint(&command.item),
506                        Color::Green.bold().paint("--"),
507                        Color::Green.bold().paint(name)
508                    ),
509                )
510                .with_labels(vec![Label::primary(0, command.span)]),
511                ArgumentError::MissingMandatoryPositional(name) => Diagnostic::error().with_message(
512                    format!(
513                        "{} requires {} parameter",
514                        Color::Cyan.paint(&command.item),
515                        Color::Green.bold().paint(name.clone())
516                    ),
517                )
518                .with_labels(
519                    vec![Label::primary(0, command.span).with_message(format!("requires {} parameter", name))],
520                ),
521                ArgumentError::MissingValueForName(name) => Diagnostic::error().with_message(
522                    format!(
523                        "{} is missing value for flag {}{}",
524                        Color::Cyan.paint(&command.item),
525                        Color::Green.bold().paint("--"),
526                        Color::Green.bold().paint(name)
527                    ),
528                )
529                .with_labels(vec![Label::primary(0, command.span)]),
530                ArgumentError::BadValue(msg) => Diagnostic::error().with_message(msg.clone()).with_labels(vec![Label::primary(0, command.span).with_message(msg)])
531            },
532            ProximateShellError::TypeError {
533                expected,
534                actual:
535                    Spanned {
536                        item: Some(actual),
537                        span,
538                    },
539            } => Diagnostic::error().with_message("Type Error").with_labels(
540                vec![Label::primary(0, span)
541                    .with_message(format!("Expected {}, found {}", expected, actual))],
542            ),
543            ProximateShellError::TypeError {
544                expected,
545                actual:
546                    Spanned {
547                        item: None,
548                        span
549                    },
550            } => Diagnostic::error().with_message("Type Error")
551                .with_labels(vec![Label::primary(0, span).with_message(expected)]),
552
553            ProximateShellError::UnexpectedEof {
554                expected, span
555            } => Diagnostic::error().with_message("Unexpected end of input")
556                .with_labels(vec![Label::primary(0, span).with_message(format!("Expected {}", expected))]),
557
558            ProximateShellError::RangeError {
559                kind,
560                operation,
561                actual_kind:
562                    Spanned {
563                        item,
564                        span
565                    },
566            } => Diagnostic::error().with_message("Range Error").with_labels(
567                vec![Label::primary(0, span).with_message(format!(
568                    "Expected to convert {} to {} while {}, but it was out of range",
569                    item,
570                    kind.display(),
571                    operation
572                ))],
573            ),
574
575            ProximateShellError::SyntaxError {
576                problem:
577                    Spanned {
578                        span,
579                        item
580                    },
581            } => Diagnostic::error().with_message("Syntax Error")
582                .with_labels(vec![Label::primary(0, span).with_message(item)]),
583
584            ProximateShellError::MissingProperty { subpath, expr, .. } => {
585
586                let mut diag = Diagnostic::error().with_message("Missing property");
587
588                if subpath.span == Span::unknown() {
589                    diag.message = format!("Missing property (for {})", subpath.item);
590                } else {
591                    let subpath = Label::primary(0, subpath.span).with_message(subpath.item);
592                    let mut labels = vec![subpath];
593
594                    if expr.span != Span::unknown() {
595                        let expr = Label::primary(0, expr.span).with_message(expr.item);
596                        labels.push(expr);
597                    }
598                    diag = diag.with_labels(labels);
599                }
600
601                diag
602            }
603
604            ProximateShellError::InvalidIntegerIndex { subpath,integer } => {
605                let mut diag = Diagnostic::error().with_message("Invalid integer property");
606                let mut labels = vec![];
607                if subpath.span == Span::unknown() {
608                    diag.message = format!("Invalid integer property (for {})", subpath.item)
609                } else {
610                    let label = Label::primary(0, subpath.span).with_message(subpath.item);
611                    labels.push(label);
612                }
613
614                labels.push(Label::secondary(0, integer).with_message("integer"));
615                diag = diag.with_labels(labels);
616
617                diag
618            }
619
620            ProximateShellError::Diagnostic(diag) => diag.diagnostic,
621            ProximateShellError::CoerceError { left, right } => {
622                Diagnostic::error().with_message("Coercion error")
623                    .with_labels(vec![Label::primary(0, left.span).with_message(left.item),
624                    Label::secondary(0, right.span).with_message(right.item)])
625            }
626
627            ProximateShellError::UntaggedRuntimeError { reason } => Diagnostic::error().with_message(format!("Error: {}", reason)),
628            ProximateShellError::Unimplemented { reason } => Diagnostic::error().with_message(format!("Unimplemented: {}", reason)),
629
630        };
631
632        let notes = self.notes.clone();
633        d.with_notes(notes)
634    }
635
636    pub fn labeled_error(
637        msg: impl Into<String>,
638        label: impl Into<String>,
639        span: impl Into<Span>,
640    ) -> ShellError {
641        ShellError::diagnostic(
642            Diagnostic::error()
643                .with_message(msg.into())
644                .with_labels(vec![
645                    Label::primary(0, span.into()).with_message(label.into())
646                ]),
647        )
648    }
649
650    pub fn labeled_error_with_secondary(
651        msg: impl Into<String>,
652        primary_label: impl Into<String>,
653        primary_span: impl Into<Span>,
654        secondary_label: impl Into<String>,
655        secondary_span: impl Into<Span>,
656    ) -> ShellError {
657        ShellError::diagnostic(
658            Diagnostic::error()
659                .with_message(msg.into())
660                .with_labels(vec![
661                    Label::primary(0, primary_span.into()).with_message(primary_label.into()),
662                    Label::secondary(0, secondary_span.into()).with_message(secondary_label.into()),
663                ]),
664        )
665    }
666
667    pub fn unimplemented(title: impl Into<String>) -> ShellError {
668        ShellError::untagged_runtime_error(&format!("Unimplemented: {}", title.into()))
669    }
670
671    pub fn unexpected(title: impl Into<String>) -> ShellError {
672        ShellError::untagged_runtime_error(&format!("Unexpected: {}", title.into()))
673    }
674
675    pub fn is_unimplemented(&self) -> bool {
676        matches!(self.error, ProximateShellError::Unimplemented { .. })
677    }
678}
679
680/// `ExpectedRange` describes a range of values that was expected by a command. In addition
681/// to typical ranges, this enum allows an error to specify that the range of allowed values
682/// corresponds to a particular numeric type (which is a dominant use-case for the
683/// [RangeError](ProximateShellError::RangeError) error type).
684#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
685pub enum ExpectedRange {
686    I8,
687    I16,
688    I32,
689    I64,
690    I128,
691    U8,
692    U16,
693    U32,
694    U64,
695    U128,
696    F32,
697    F64,
698    Usize,
699    Size,
700    BigInt,
701    BigDecimal,
702    Range { start: usize, end: usize },
703}
704
705/// Convert a Rust range into an [ExpectedRange](ExpectedRange).
706impl From<Range<usize>> for ExpectedRange {
707    fn from(range: Range<usize>) -> Self {
708        ExpectedRange::Range {
709            start: range.start,
710            end: range.end,
711        }
712    }
713}
714
715impl PrettyDebug for ExpectedRange {
716    fn pretty(&self) -> DebugDocBuilder {
717        DbgDocBldr::description(match self {
718            ExpectedRange::I8 => "an 8-bit signed integer",
719            ExpectedRange::I16 => "a 16-bit signed integer",
720            ExpectedRange::I32 => "a 32-bit signed integer",
721            ExpectedRange::I64 => "a 64-bit signed integer",
722            ExpectedRange::I128 => "a 128-bit signed integer",
723            ExpectedRange::U8 => "an 8-bit unsigned integer",
724            ExpectedRange::U16 => "a 16-bit unsigned integer",
725            ExpectedRange::U32 => "a 32-bit unsigned integer",
726            ExpectedRange::U64 => "a 64-bit unsigned integer",
727            ExpectedRange::U128 => "a 128-bit unsigned integer",
728            ExpectedRange::F32 => "a 32-bit float",
729            ExpectedRange::F64 => "a 64-bit float",
730            ExpectedRange::Usize => "an list index",
731            ExpectedRange::Size => "a list offset",
732            ExpectedRange::BigDecimal => "a decimal",
733            ExpectedRange::BigInt => "an integer",
734            ExpectedRange::Range { start, end } => {
735                return DbgDocBldr::description(format!("{} to {}", start, end))
736            }
737        })
738    }
739}
740
741#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize, Hash)]
742pub enum ProximateShellError {
743    SyntaxError {
744        problem: Spanned<String>,
745    },
746    UnexpectedEof {
747        expected: String,
748        span: Span,
749    },
750    TypeError {
751        expected: String,
752        actual: Spanned<Option<String>>,
753    },
754    MissingProperty {
755        subpath: Spanned<String>,
756        expr: Spanned<String>,
757    },
758    InvalidIntegerIndex {
759        subpath: Spanned<String>,
760        integer: Span,
761    },
762    MissingValue {
763        span: Option<Span>,
764        reason: String,
765    },
766    ArgumentError {
767        command: Spanned<String>,
768        error: ArgumentError,
769    },
770    RangeError {
771        kind: ExpectedRange,
772        actual_kind: Spanned<String>,
773        operation: String,
774    },
775    Diagnostic(ShellDiagnostic),
776    CoerceError {
777        left: Spanned<String>,
778        right: Spanned<String>,
779    },
780    UntaggedRuntimeError {
781        reason: String,
782    },
783    Unimplemented {
784        reason: String,
785    },
786}
787
788impl ProximateShellError {
789    fn start(self) -> ShellError {
790        ShellError {
791            error: self,
792            notes: vec![],
793        }
794    }
795}
796
797impl HasFallibleSpan for ShellError {
798    fn maybe_span(&self) -> Option<Span> {
799        self.error.maybe_span()
800    }
801}
802
803impl HasFallibleSpan for ProximateShellError {
804    fn maybe_span(&self) -> Option<Span> {
805        Some(match self {
806            ProximateShellError::SyntaxError { problem } => problem.span,
807            ProximateShellError::UnexpectedEof { span, .. } => *span,
808            ProximateShellError::TypeError { actual, .. } => actual.span,
809            ProximateShellError::MissingProperty { subpath, .. } => subpath.span,
810            ProximateShellError::InvalidIntegerIndex { subpath, .. } => subpath.span,
811            ProximateShellError::MissingValue { span, .. } => return *span,
812            ProximateShellError::ArgumentError { command, .. } => command.span,
813            ProximateShellError::RangeError { actual_kind, .. } => actual_kind.span,
814            ProximateShellError::Diagnostic(_) => return None,
815            ProximateShellError::CoerceError { left, right } => left.span.until(right.span),
816            ProximateShellError::UntaggedRuntimeError { .. } => return None,
817            ProximateShellError::Unimplemented { .. } => return None,
818        })
819    }
820}
821
822#[derive(Debug, Clone, Serialize, Deserialize)]
823pub struct ShellDiagnostic {
824    pub diagnostic: Diagnostic<usize>,
825}
826
827impl std::hash::Hash for ShellDiagnostic {
828    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
829        self.diagnostic.severity.hash(state);
830        self.diagnostic.code.hash(state);
831        self.diagnostic.message.hash(state);
832
833        for label in &self.diagnostic.labels {
834            label.range.hash(state);
835            label.message.hash(state);
836            match label.style {
837                codespan_reporting::diagnostic::LabelStyle::Primary => 0.hash(state),
838                codespan_reporting::diagnostic::LabelStyle::Secondary => 1.hash(state),
839            }
840        }
841    }
842}
843
844impl PartialEq for ShellDiagnostic {
845    fn eq(&self, _other: &ShellDiagnostic) -> bool {
846        false
847    }
848}
849
850impl Eq for ShellDiagnostic {}
851
852impl std::cmp::PartialOrd for ShellDiagnostic {
853    fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
854        Some(std::cmp::Ordering::Less)
855    }
856}
857
858impl std::cmp::Ord for ShellDiagnostic {
859    fn cmp(&self, _other: &Self) -> std::cmp::Ordering {
860        std::cmp::Ordering::Less
861    }
862}
863
864#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, new, Clone, Serialize, Deserialize)]
865pub struct StringError {
866    title: String,
867    error: String,
868}
869
870impl std::error::Error for ShellError {}
871
872impl std::convert::From<Box<dyn std::error::Error>> for ShellError {
873    fn from(input: Box<dyn std::error::Error>) -> ShellError {
874        ShellError::untagged_runtime_error(input.to_string())
875    }
876}
877
878impl std::convert::From<std::io::Error> for ShellError {
879    fn from(input: std::io::Error) -> ShellError {
880        ShellError::untagged_runtime_error(input.to_string())
881    }
882}
883
884impl std::convert::From<std::string::FromUtf8Error> for ShellError {
885    fn from(input: std::string::FromUtf8Error) -> ShellError {
886        ShellError::untagged_runtime_error(input.to_string())
887    }
888}
889
890impl std::convert::From<std::str::Utf8Error> for ShellError {
891    fn from(input: std::str::Utf8Error) -> ShellError {
892        ShellError::untagged_runtime_error(input.to_string())
893    }
894}
895
896impl std::convert::From<serde_yaml::Error> for ShellError {
897    fn from(input: serde_yaml::Error) -> ShellError {
898        ShellError::untagged_runtime_error(format!("{:?}", input))
899    }
900}
901
902impl std::convert::From<toml::ser::Error> for ShellError {
903    fn from(input: toml::ser::Error) -> ShellError {
904        ShellError::untagged_runtime_error(format!("{:?}", input))
905    }
906}
907
908impl std::convert::From<serde_json::Error> for ShellError {
909    fn from(input: serde_json::Error) -> ShellError {
910        ShellError::untagged_runtime_error(format!("{:?}", input))
911    }
912}
913
914impl std::convert::From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
915    fn from(input: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
916        ShellError::untagged_runtime_error(format!("{:?}", input))
917    }
918}
919
920impl std::convert::From<glob::PatternError> for ShellError {
921    fn from(input: glob::PatternError) -> ShellError {
922        ShellError::untagged_runtime_error(format!("{:?}", input))
923    }
924}
925
926pub trait CoerceInto<U> {
927    fn coerce_into(self, operation: impl Into<String>) -> Result<U, ShellError>;
928}
929
930trait ToExpectedRange {
931    fn to_expected_range() -> ExpectedRange;
932}
933
934macro_rules! ranged_int {
935    ($ty:tt -> $op:tt -> $variant:tt) => {
936        impl ToExpectedRange for $ty {
937            fn to_expected_range() -> ExpectedRange {
938                ExpectedRange::$variant
939            }
940        }
941
942        impl CoerceInto<$ty> for nu_source::Tagged<BigInt> {
943            fn coerce_into(self, operation: impl Into<String>) -> Result<$ty, ShellError> {
944                self.$op().ok_or_else(|| {
945                    ShellError::range_error(
946                        $ty::to_expected_range(),
947                        &self.item.spanned(self.tag.span),
948                        operation.into(),
949                    )
950                })
951            }
952        }
953
954        impl CoerceInto<$ty> for nu_source::Tagged<&BigInt> {
955            fn coerce_into(self, operation: impl Into<String>) -> Result<$ty, ShellError> {
956                self.$op().ok_or_else(|| {
957                    ShellError::range_error(
958                        $ty::to_expected_range(),
959                        &self.item.spanned(self.tag.span),
960                        operation.into(),
961                    )
962                })
963            }
964        }
965    };
966}
967
968ranged_int!(u8  -> to_u8  -> U8);
969ranged_int!(u16 -> to_u16 -> U16);
970ranged_int!(u32 -> to_u32 -> U32);
971ranged_int!(u64 -> to_u64 -> U64);
972ranged_int!(i8  -> to_i8  -> I8);
973ranged_int!(i16 -> to_i16 -> I16);
974ranged_int!(i32 -> to_i32 -> I32);
975ranged_int!(i64 -> to_i64 -> I64);
976
977macro_rules! ranged_decimal {
978    ($ty:tt -> $op:tt -> $variant:tt) => {
979        impl ToExpectedRange for $ty {
980            fn to_expected_range() -> ExpectedRange {
981                ExpectedRange::$variant
982            }
983        }
984
985        impl CoerceInto<$ty> for nu_source::Tagged<BigDecimal> {
986            fn coerce_into(self, operation: impl Into<String>) -> Result<$ty, ShellError> {
987                self.$op().ok_or_else(|| {
988                    ShellError::range_error(
989                        $ty::to_expected_range(),
990                        &self.item.spanned(self.tag.span),
991                        operation.into(),
992                    )
993                })
994            }
995        }
996
997        impl CoerceInto<$ty> for nu_source::Tagged<&BigDecimal> {
998            fn coerce_into(self, operation: impl Into<String>) -> Result<$ty, ShellError> {
999                self.$op().ok_or_else(|| {
1000                    ShellError::range_error(
1001                        $ty::to_expected_range(),
1002                        &self.item.spanned(self.tag.span),
1003                        operation.into(),
1004                    )
1005                })
1006            }
1007        }
1008    };
1009}
1010
1011ranged_decimal!(f32 -> to_f32 -> F32);
1012ranged_decimal!(f64 -> to_f64 -> F64);