use crate::prelude::*; use ansi_term::Color; use derive_new::new; use language_reporting::{Diagnostic, Label, Severity}; use nu_source::{Spanned, TracableContext}; use serde::{Deserialize, Serialize}; use std::fmt; use std::ops::Range; // TODO: Spanned -> HasSpanAndItem ? #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash, Serialize, Deserialize)] pub enum Description { Source(Spanned), Synthetic(String), } impl Description { fn from_spanned(item: Spanned>) -> Description { Description::Source(item.map(|s| s.into())) } fn into_label(self) -> Result, String> { match self { Description::Source(s) => Ok(Label::new_primary(s.span).with_message(s.item)), Description::Synthetic(s) => Err(s), } } } impl PrettyDebug for Description { fn pretty(&self) -> DebugDocBuilder { match self { Description::Source(s) => b::description(&s.item), Description::Synthetic(s) => b::description(s), } } } #[derive(Debug, Clone)] pub enum ParseErrorReason { Eof { expected: &'static str, span: Span, }, Mismatch { expected: &'static str, actual: Spanned, }, ArgumentError { command: Spanned, error: ArgumentError, }, } #[derive(Debug, Clone)] pub struct ParseError { reason: ParseErrorReason, } impl ParseError { pub fn unexpected_eof(expected: &'static str, span: Span) -> ParseError { ParseError { reason: ParseErrorReason::Eof { expected, span }, } } pub fn mismatch(expected: &'static str, actual: Spanned>) -> ParseError { let Spanned { span, item } = actual; ParseError { reason: ParseErrorReason::Mismatch { expected, actual: item.into().spanned(span), }, } } pub fn argument_error(command: Spanned>, kind: ArgumentError) -> ParseError { ParseError { reason: ParseErrorReason::ArgumentError { command: command.item.into().spanned(command.span), error: kind, }, } } } impl From for ShellError { fn from(error: ParseError) -> ShellError { match error.reason { ParseErrorReason::Eof { expected, span } => ShellError::unexpected_eof(expected, span), ParseErrorReason::Mismatch { actual, expected } => { ShellError::type_error(expected, actual.clone()) } ParseErrorReason::ArgumentError { command, error } => { ShellError::argument_error(command, error) } } } } #[derive(Debug, Eq, PartialEq, Clone, Ord, Hash, PartialOrd, Serialize, Deserialize)] pub enum ArgumentError { MissingMandatoryFlag(String), MissingMandatoryPositional(String), MissingValueForName(String), InvalidExternalWord, } impl PrettyDebug for ArgumentError { fn pretty(&self) -> DebugDocBuilder { match self { ArgumentError::MissingMandatoryFlag(flag) => { b::description("missing `") + b::description(flag) + b::description("` as mandatory flag") } ArgumentError::MissingMandatoryPositional(pos) => { b::description("missing `") + b::description(pos) + b::description("` as mandatory positional argument") } ArgumentError::MissingValueForName(name) => { b::description("missing value for flag `") + b::description(name) + b::description("`") } ArgumentError::InvalidExternalWord => b::description("invalid word"), } } } #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize, Deserialize, Hash)] pub struct ShellError { error: ProximateShellError, cause: Option>, } impl PrettyDebug for ShellError { fn pretty(&self) -> DebugDocBuilder { match &self.error { ProximateShellError::SyntaxError { problem } => { b::error("Syntax Error") + b::space() + b::delimit("(", b::description(&problem.item), ")") } ProximateShellError::UnexpectedEof { .. } => b::error("Unexpected end"), ProximateShellError::TypeError { expected, actual } => { b::error("Type Error") + b::space() + b::delimit( "(", b::description("expected:") + b::space() + b::description(expected) + b::description(",") + b::space() + b::description("actual:") + b::space() + b::option(actual.item.as_ref().map(|actual| b::description(actual))), ")", ) } ProximateShellError::MissingProperty { subpath, expr } => { b::error("Missing Property") + b::space() + b::delimit( "(", b::description("expr:") + b::space() + expr.pretty() + b::description(",") + b::space() + b::description("subpath:") + b::space() + subpath.pretty(), ")", ) } ProximateShellError::InvalidIntegerIndex { subpath, .. } => { b::error("Invalid integer index") + b::space() + b::delimit( "(", b::description("subpath:") + b::space() + subpath.pretty(), ")", ) } ProximateShellError::MissingValue { reason, .. } => { b::error("Missing Value") + b::space() + b::delimit( "(", b::description("reason:") + b::space() + b::description(reason), ")", ) } ProximateShellError::ArgumentError { command, error } => { b::error("Argument Error") + b::space() + b::delimit( "(", b::description("command:") + b::space() + b::description(&command.item) + b::description(",") + b::space() + b::description("error:") + b::space() + error.pretty(), ")", ) } ProximateShellError::RangeError { kind, actual_kind, operation, } => { b::error("Range Error") + b::space() + b::delimit( "(", b::description("expected:") + b::space() + kind.pretty() + b::description(",") + b::space() + b::description("actual:") + b::space() + b::description(&actual_kind.item) + b::description(",") + b::space() + b::description("operation:") + b::space() + b::description(operation), ")", ) } ProximateShellError::Diagnostic(_) => b::error("diagnostic"), ProximateShellError::CoerceError { left, right } => { b::error("Coercion Error") + b::space() + b::delimit( "(", b::description("left:") + b::space() + b::description(&left.item) + b::description(",") + b::space() + b::description("right:") + b::space() + b::description(&right.item), ")", ) } ProximateShellError::UntaggedRuntimeError { reason } => { b::error("Unknown Error") + b::delimit("(", b::description(reason), ")") } } } } impl std::fmt::Display for ShellError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.pretty().display()) } } impl serde::de::Error for ShellError { fn custom(msg: T) -> Self where T: std::fmt::Display, { ShellError::untagged_runtime_error(msg.to_string()) } } impl ShellError { pub fn type_error( expected: impl Into, actual: Spanned>, ) -> ShellError { ProximateShellError::TypeError { expected: expected.into(), actual: actual.map(|i| Some(i.into())), } .start() } pub fn missing_property( subpath: Spanned>, expr: Spanned>, ) -> ShellError { ProximateShellError::MissingProperty { subpath: Description::from_spanned(subpath), expr: Description::from_spanned(expr), } .start() } pub fn invalid_integer_index( subpath: Spanned>, integer: impl Into, ) -> ShellError { ProximateShellError::InvalidIntegerIndex { subpath: Description::from_spanned(subpath), integer: integer.into(), } .start() } pub fn untagged_runtime_error(error: impl Into) -> ShellError { ProximateShellError::UntaggedRuntimeError { reason: error.into(), } .start() } pub(crate) fn unexpected_eof(expected: impl Into, span: impl Into) -> ShellError { ProximateShellError::UnexpectedEof { expected: expected.into(), span: span.into(), } .start() } pub(crate) fn range_error( expected: impl Into, actual: &Spanned, operation: impl Into, ) -> ShellError { ProximateShellError::RangeError { kind: expected.into(), actual_kind: format!("{:?}", actual.item).spanned(actual.span), operation: operation.into(), } .start() } pub(crate) fn syntax_error(problem: Spanned>) -> ShellError { ProximateShellError::SyntaxError { problem: problem.map(|p| p.into()), } .start() } pub(crate) fn coerce_error( left: Spanned>, right: Spanned>, ) -> ShellError { ProximateShellError::CoerceError { left: left.map(|l| l.into()), right: right.map(|r| r.into()), } .start() } pub(crate) fn argument_error( command: Spanned>, kind: ArgumentError, ) -> ShellError { ProximateShellError::ArgumentError { command: command.map(|c| c.into()), error: kind, } .start() } pub(crate) fn parse_error( error: nom::Err<( nom_locate::LocatedSpanEx<&str, TracableContext>, nom::error::ErrorKind, )>, ) -> ShellError { use language_reporting::*; match error { nom::Err::Incomplete(_) => { // TODO: Get span of EOF let diagnostic = Diagnostic::new( Severity::Error, format!("Parse Error: Unexpected end of line"), ); ShellError::diagnostic(diagnostic) } nom::Err::Failure(span) | nom::Err::Error(span) => { let diagnostic = Diagnostic::new(Severity::Error, format!("Parse Error")) .with_label(Label::new_primary(Span::from(span.0))); ShellError::diagnostic(diagnostic) } } } pub(crate) fn diagnostic(diagnostic: Diagnostic) -> ShellError { ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }).start() } pub(crate) fn to_diagnostic(self) -> Diagnostic { match self.error { ProximateShellError::MissingValue { span, reason } => { let mut d = Diagnostic::new( Severity::Bug, format!("Internal Error (missing value) :: {}", reason), ); if let Some(span) = span { d = d.with_label(Label::new_primary(span)); } d } ProximateShellError::ArgumentError { command, error, } => match error { ArgumentError::InvalidExternalWord => Diagnostic::new( Severity::Error, format!("Invalid bare word for Nu command (did you intend to invoke an external command?)")) .with_label(Label::new_primary(command.span)), ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new( Severity::Error, format!( "{} requires {}{}", Color::Cyan.paint(&command.item), Color::Black.bold().paint("--"), Color::Black.bold().paint(name) ), ) .with_label(Label::new_primary(command.span)), ArgumentError::MissingMandatoryPositional(name) => Diagnostic::new( Severity::Error, format!( "{} requires {} parameter", Color::Cyan.paint(&command.item), Color::Green.bold().paint(name.clone()) ), ) .with_label( Label::new_primary(command.span).with_message(format!("requires {} parameter", name)), ), ArgumentError::MissingValueForName(name) => Diagnostic::new( Severity::Error, format!( "{} is missing value for flag {}{}", Color::Cyan.paint(&command.item), Color::Black.bold().paint("--"), Color::Black.bold().paint(name) ), ) .with_label(Label::new_primary(command.span)), }, ProximateShellError::TypeError { expected, actual: Spanned { item: Some(actual), span, }, } => Diagnostic::new(Severity::Error, "Type Error").with_label( Label::new_primary(span) .with_message(format!("Expected {}, found {}", expected, actual)), ), ProximateShellError::TypeError { expected, actual: Spanned { item: None, span }, } => Diagnostic::new(Severity::Error, "Type Error") .with_label(Label::new_primary(span).with_message(expected)), ProximateShellError::UnexpectedEof { expected, span } => Diagnostic::new(Severity::Error, format!("Unexpected end of input")) .with_label(Label::new_primary(span).with_message(format!("Expected {}", expected))), ProximateShellError::RangeError { kind, operation, actual_kind: Spanned { item, span }, } => Diagnostic::new(Severity::Error, "Range Error").with_label( Label::new_primary(span).with_message(format!( "Expected to convert {} to {} while {}, but it was out of range", item, kind.desc(), operation )), ), ProximateShellError::SyntaxError { problem: Spanned { span, item }, } => Diagnostic::new(Severity::Error, "Syntax Error") .with_label(Label::new_primary(span).with_message(item)), ProximateShellError::MissingProperty { subpath, expr, .. } => { let subpath = subpath.into_label(); let expr = expr.into_label(); let mut diag = Diagnostic::new(Severity::Error, "Missing property"); match subpath { Ok(label) => diag = diag.with_label(label), Err(ty) => diag.message = format!("Missing property (for {})", ty), } if let Ok(label) = expr { diag = diag.with_label(label); } diag } ProximateShellError::InvalidIntegerIndex { subpath,integer } => { let subpath = subpath.into_label(); let mut diag = Diagnostic::new(Severity::Error, "Invalid integer property"); match subpath { Ok(label) => diag = diag.with_label(label), Err(ty) => diag.message = format!("Invalid integer property (for {})", ty) } diag = diag.with_label(Label::new_secondary(integer).with_message("integer")); diag } ProximateShellError::Diagnostic(diag) => diag.diagnostic, ProximateShellError::CoerceError { left, right } => { Diagnostic::new(Severity::Error, "Coercion error") .with_label(Label::new_primary(left.span).with_message(left.item)) .with_label(Label::new_secondary(right.span).with_message(right.item)) } ProximateShellError::UntaggedRuntimeError { reason } => Diagnostic::new(Severity::Error, format!("Error: {}", reason)) } } pub fn labeled_error( msg: impl Into, label: impl Into, span: impl Into, ) -> ShellError { ShellError::diagnostic( Diagnostic::new(Severity::Error, msg.into()) .with_label(Label::new_primary(span.into()).with_message(label.into())), ) } pub fn labeled_error_with_secondary( msg: impl Into, primary_label: impl Into, primary_span: impl Into, secondary_label: impl Into, secondary_span: impl Into, ) -> ShellError { ShellError::diagnostic( Diagnostic::new_error(msg.into()) .with_label( Label::new_primary(primary_span.into()).with_message(primary_label.into()), ) .with_label( Label::new_secondary(secondary_span.into()) .with_message(secondary_label.into()), ), ) } // pub fn string(title: impl Into) -> ShellError { // ProximateShellError::String(StringError::new(title.into(), String::new())).start() // } // // pub(crate) fn unreachable(title: impl Into) -> ShellError { // ShellError::untagged_runtime_error(&format!("BUG: Unreachable: {}", title.into())) // } pub(crate) fn unimplemented(title: impl Into) -> ShellError { ShellError::untagged_runtime_error(&format!("Unimplemented: {}", title.into())) } pub(crate) fn unexpected(title: impl Into) -> ShellError { ShellError::untagged_runtime_error(&format!("Unexpected: {}", title.into())) } } #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)] pub enum ExpectedRange { I8, I16, I32, I64, I128, U8, U16, U32, U64, U128, F32, F64, Usize, Size, BigInt, BigDecimal, Range { start: usize, end: usize }, } impl From> for ExpectedRange { fn from(range: Range) -> Self { ExpectedRange::Range { start: range.start, end: range.end, } } } impl PrettyDebug for ExpectedRange { fn pretty(&self) -> DebugDocBuilder { b::description(self.desc()) } } impl ExpectedRange { fn desc(&self) -> String { match self { ExpectedRange::I8 => "an 8-bit signed integer", ExpectedRange::I16 => "a 16-bit signed integer", ExpectedRange::I32 => "a 32-bit signed integer", ExpectedRange::I64 => "a 64-bit signed integer", ExpectedRange::I128 => "a 128-bit signed integer", ExpectedRange::U8 => "an 8-bit unsigned integer", ExpectedRange::U16 => "a 16-bit unsigned integer", ExpectedRange::U32 => "a 32-bit unsigned integer", ExpectedRange::U64 => "a 64-bit unsigned integer", ExpectedRange::U128 => "a 128-bit unsigned integer", ExpectedRange::F32 => "a 32-bit float", ExpectedRange::F64 => "a 64-bit float", ExpectedRange::Usize => "an list index", ExpectedRange::Size => "a list offset", ExpectedRange::BigDecimal => "a decimal", ExpectedRange::BigInt => "an integer", ExpectedRange::Range { start, end } => return format!("{} to {}", start, end), } .to_string() } } #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize, Hash)] pub enum ProximateShellError { SyntaxError { problem: Spanned, }, UnexpectedEof { expected: String, span: Span, }, TypeError { expected: String, actual: Spanned>, }, MissingProperty { subpath: Description, expr: Description, }, InvalidIntegerIndex { subpath: Description, integer: Span, }, MissingValue { span: Option, reason: String, }, ArgumentError { command: Spanned, error: ArgumentError, }, RangeError { kind: ExpectedRange, actual_kind: Spanned, operation: String, }, Diagnostic(ShellDiagnostic), CoerceError { left: Spanned, right: Spanned, }, UntaggedRuntimeError { reason: String, }, } impl ProximateShellError { fn start(self) -> ShellError { ShellError { cause: None, error: self, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ShellDiagnostic { pub(crate) diagnostic: Diagnostic, } impl std::hash::Hash for ShellDiagnostic { fn hash(&self, state: &mut H) { self.diagnostic.severity.hash(state); self.diagnostic.code.hash(state); self.diagnostic.message.hash(state); for label in &self.diagnostic.labels { label.span.hash(state); label.message.hash(state); match label.style { language_reporting::LabelStyle::Primary => 0.hash(state), language_reporting::LabelStyle::Secondary => 1.hash(state), } } } } impl PartialEq for ShellDiagnostic { fn eq(&self, _other: &ShellDiagnostic) -> bool { false } } impl Eq for ShellDiagnostic {} impl std::cmp::PartialOrd for ShellDiagnostic { fn partial_cmp(&self, _other: &Self) -> Option { Some(std::cmp::Ordering::Less) } } impl std::cmp::Ord for ShellDiagnostic { fn cmp(&self, _other: &Self) -> std::cmp::Ordering { std::cmp::Ordering::Less } } #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, new, Clone, Serialize, Deserialize)] pub struct StringError { title: String, error: String, } impl std::error::Error for ShellError {} impl std::convert::From> for ShellError { fn from(input: Box) -> ShellError { ShellError::untagged_runtime_error(format!("{}", input)) } } impl std::convert::From for ShellError { fn from(input: std::io::Error) -> ShellError { ShellError::untagged_runtime_error(format!("{}", input)) } } impl std::convert::From for ShellError { fn from(input: subprocess::PopenError) -> ShellError { ShellError::untagged_runtime_error(format!("{}", input)) } } impl std::convert::From for ShellError { fn from(input: serde_yaml::Error) -> ShellError { ShellError::untagged_runtime_error(format!("{:?}", input)) } } impl std::convert::From for ShellError { fn from(input: toml::ser::Error) -> ShellError { ShellError::untagged_runtime_error(format!("{:?}", input)) } } impl std::convert::From for ShellError { fn from(input: serde_json::Error) -> ShellError { ShellError::untagged_runtime_error(format!("{:?}", input)) } } impl std::convert::From> for ShellError { fn from(input: Box) -> ShellError { ShellError::untagged_runtime_error(format!("{:?}", input)) } } pub trait CoerceInto { fn coerce_into(self, operation: impl Into) -> Result; } trait ToExpectedRange { fn to_expected_range() -> ExpectedRange; } macro_rules! ranged_int { ($ty:tt -> $op:tt -> $variant:tt) => { impl ToExpectedRange for $ty { fn to_expected_range() -> ExpectedRange { ExpectedRange::$variant } } impl CoerceInto<$ty> for nu_source::Tagged { fn coerce_into(self, operation: impl Into) -> Result<$ty, ShellError> { match self.$op() { Some(v) => Ok(v), None => Err(ShellError::range_error( $ty::to_expected_range(), &self.item.spanned(self.tag.span), operation.into(), )), } } } impl CoerceInto<$ty> for nu_source::Tagged<&BigInt> { fn coerce_into(self, operation: impl Into) -> Result<$ty, ShellError> { match self.$op() { Some(v) => Ok(v), None => Err(ShellError::range_error( $ty::to_expected_range(), &self.item.spanned(self.tag.span), operation.into(), )), } } } }; } ranged_int!(u8 -> to_u8 -> U8); ranged_int!(u16 -> to_u16 -> U16); ranged_int!(u32 -> to_u32 -> U32); ranged_int!(u64 -> to_u64 -> U64); ranged_int!(i8 -> to_i8 -> I8); ranged_int!(i16 -> to_i16 -> I16); ranged_int!(i32 -> to_i32 -> I32); ranged_int!(i64 -> to_i64 -> I64); macro_rules! ranged_decimal { ($ty:tt -> $op:tt -> $variant:tt) => { impl ToExpectedRange for $ty { fn to_expected_range() -> ExpectedRange { ExpectedRange::$variant } } impl CoerceInto<$ty> for nu_source::Tagged { fn coerce_into(self, operation: impl Into) -> Result<$ty, ShellError> { match self.$op() { Some(v) => Ok(v), None => Err(ShellError::range_error( $ty::to_expected_range(), &self.item.spanned(self.tag.span), operation.into(), )), } } } impl CoerceInto<$ty> for nu_source::Tagged<&BigDecimal> { fn coerce_into(self, operation: impl Into) -> Result<$ty, ShellError> { match self.$op() { Some(v) => Ok(v), None => Err(ShellError::range_error( $ty::to_expected_range(), &self.item.spanned(self.tag.span), operation.into(), )), } } } }; } ranged_decimal!(f32 -> to_f32 -> F32); ranged_decimal!(f64 -> to_f64 -> F64);