use crate::prelude::*; use ansi_term::Color; use derive_new::new; use language_reporting::{Diagnostic, Label, Severity}; use serde::{Deserialize, Serialize}; use std::fmt; #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] pub enum Description { Source(Tagged), Synthetic(String), } impl Description { pub fn from(value: Tagged>) -> Description { let value_span = value.span(); let value_tag = value.tag(); match value_span { Span { start: 0, end: 0 } => Description::Synthetic(value.item.into()), _ => Description::Source(Tagged::from_item(value.item.into(), value_tag)), } } } impl Description { 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), } } } #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] pub enum ArgumentError { MissingMandatoryFlag(String), MissingMandatoryPositional(String), MissingValueForName(String), InvalidExternalWord, } #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] pub struct ShellError { error: ProximateShellError, cause: Option>, } impl ToDebug for ShellError { fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result { self.error.fmt_debug(f, source) } } impl serde::de::Error for ShellError { fn custom(msg: T) -> Self where T: std::fmt::Display, { ShellError::string(msg.to_string()) } } impl ShellError { pub(crate) fn type_error( expected: impl Into, actual: Tagged>, ) -> ShellError { ProximateShellError::TypeError { expected: expected.into(), actual: actual.map(|i| Some(i.into())), } .start() } pub(crate) fn range_error( expected: impl Into, actual: &Tagged, operation: String, ) -> ShellError { ProximateShellError::RangeError { kind: expected.into(), actual_kind: actual.copy_span(format!("{:?}", actual.item)), operation, } .start() } pub(crate) fn syntax_error(problem: Tagged>) -> ShellError { ProximateShellError::SyntaxError { problem: problem.map(|p| p.into()), } .start() } pub(crate) fn invalid_command(problem: impl Into) -> ShellError { ProximateShellError::InvalidCommand { command: problem.into(), } .start() } pub(crate) fn coerce_error( left: Tagged>, right: Tagged>, ) -> ShellError { ProximateShellError::CoerceError { left: left.map(|l| l.into()), right: right.map(|r| r.into()), } .start() } pub(crate) fn missing_property(subpath: Description, expr: Description) -> ShellError { ProximateShellError::MissingProperty { subpath, expr }.start() } pub(crate) fn missing_value(span: Option, reason: impl Into) -> ShellError { ProximateShellError::MissingValue { span, reason: reason.into(), } .start() } pub(crate) fn argument_error( command: impl Into, kind: ArgumentError, span: Span, ) -> ShellError { ProximateShellError::ArgumentError { command: command.into(), error: kind, span, } .start() } pub(crate) fn invalid_external_word(span: Span) -> ShellError { ProximateShellError::ArgumentError { command: "Invalid argument to Nu command (did you mean to call an external command?)" .into(), error: ArgumentError::InvalidExternalWord, span, } .start() } pub(crate) fn parse_error( error: nom::Err<(nom5_locate::LocatedSpan<&str>, 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::String(StringError { title, .. }) => { Diagnostic::new(Severity::Error, title) } ProximateShellError::InvalidCommand { command } => { Diagnostic::new(Severity::Error, "Invalid command") .with_label(Label::new_primary(command.span)) } 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, span, } => 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(span)), ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new( Severity::Error, format!( "{} requires {}{}", Color::Cyan.paint(command), Color::Black.bold().paint("--"), Color::Black.bold().paint(name) ), ) .with_label(Label::new_primary(span)), ArgumentError::MissingMandatoryPositional(name) => Diagnostic::new( Severity::Error, format!( "{} requires {} parameter", Color::Cyan.paint(command), Color::Green.bold().paint(name.clone()) ), ) .with_label( Label::new_primary(span).with_message(format!("requires {} parameter", name)), ), ArgumentError::MissingValueForName(name) => Diagnostic::new( Severity::Error, format!( "{} is missing value for flag {}{}", Color::Cyan.paint(command), Color::Black.bold().paint("--"), Color::Black.bold().paint(name) ), ) .with_label(Label::new_primary(span)), }, ProximateShellError::TypeError { expected, actual: Tagged { item: Some(actual), tag: Tag { span, .. }, }, } => Diagnostic::new(Severity::Error, "Type Error").with_label( Label::new_primary(span) .with_message(format!("Expected {}, found {}", expected, actual)), ), ProximateShellError::TypeError { expected, actual: Tagged { item: None, tag: Tag { span, .. }, }, } => Diagnostic::new(Severity::Error, "Type Error") .with_label(Label::new_primary(span).with_message(expected)), ProximateShellError::RangeError { kind, operation, actual_kind: Tagged { item, tag: Tag { 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: Tagged { tag: Tag { span, .. }, .. }, } => Diagnostic::new(Severity::Error, "Syntax Error") .with_label(Label::new_primary(span).with_message("Unexpected external command")), 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::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)) } } } 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: Span, secondary_label: impl Into, secondary_span: Span, ) -> ShellError { ShellError::diagnostic( Diagnostic::new_error(msg.into()) .with_label(Label::new_primary(primary_span).with_message(primary_label.into())) .with_label( Label::new_secondary(secondary_span).with_message(secondary_label.into()), ), ) } pub fn string(title: impl Into) -> ShellError { ProximateShellError::String(StringError::new(title.into(), Value::nothing())).start() } pub(crate) fn unimplemented(title: impl Into) -> ShellError { ShellError::string(&format!("Unimplemented: {}", title.into())) } pub(crate) fn unexpected(title: impl Into) -> ShellError { ShellError::string(&format!("Unexpected: {}", title.into())) } } #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] pub enum ExpectedRange { I8, I16, I32, I64, I128, U8, U16, U32, U64, U128, F32, F64, BigInt, BigDecimal, } impl ExpectedRange { fn desc(&self) -> &'static str { 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::BigDecimal => "a decimal", ExpectedRange::BigInt => "an integer", } } } #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)] pub enum ProximateShellError { String(StringError), SyntaxError { problem: Tagged, }, InvalidCommand { command: Tag, }, TypeError { expected: String, actual: Tagged>, }, MissingProperty { subpath: Description, expr: Description, }, MissingValue { span: Option, reason: String, }, ArgumentError { command: String, error: ArgumentError, span: Span, }, RangeError { kind: ExpectedRange, actual_kind: Tagged, operation: String, }, Diagnostic(ShellDiagnostic), CoerceError { left: Tagged, right: Tagged, }, } impl ProximateShellError { fn start(self) -> ShellError { ShellError { cause: None, error: self, } } } impl ToDebug for ProximateShellError { fn fmt_debug(&self, f: &mut fmt::Formatter, _source: &str) -> fmt::Result { // TODO: Custom debug for inner spans write!(f, "{:?}", self) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ShellDiagnostic { pub(crate) diagnostic: Diagnostic, } 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: Value, } impl std::fmt::Display for ShellError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match &self.error { ProximateShellError::String(s) => write!(f, "{}", &s.title), ProximateShellError::MissingValue { .. } => write!(f, "MissingValue"), ProximateShellError::InvalidCommand { .. } => write!(f, "InvalidCommand"), ProximateShellError::TypeError { .. } => write!(f, "TypeError"), ProximateShellError::RangeError { .. } => write!(f, "RangeError"), ProximateShellError::SyntaxError { .. } => write!(f, "SyntaxError"), ProximateShellError::MissingProperty { .. } => write!(f, "MissingProperty"), ProximateShellError::ArgumentError { .. } => write!(f, "ArgumentError"), ProximateShellError::Diagnostic(_) => write!(f, ""), ProximateShellError::CoerceError { .. } => write!(f, "CoerceError"), } } } impl std::error::Error for ShellError {} impl std::convert::From> for ShellError { fn from(input: Box) -> ShellError { ProximateShellError::String(StringError { title: format!("{}", input), error: Value::nothing(), }) .start() } } impl std::convert::From for ShellError { fn from(input: std::io::Error) -> ShellError { ProximateShellError::String(StringError { title: format!("{}", input), error: Value::nothing(), }) .start() } } impl std::convert::From for ShellError { fn from(input: subprocess::PopenError) -> ShellError { ProximateShellError::String(StringError { title: format!("{}", input), error: Value::nothing(), }) .start() } } impl std::convert::From for ShellError { fn from(input: serde_yaml::Error) -> ShellError { ProximateShellError::String(StringError { title: format!("{:?}", input), error: Value::nothing(), }) .start() } } impl std::convert::From for ShellError { fn from(input: toml::ser::Error) -> ShellError { ProximateShellError::String(StringError { title: format!("{:?}", input), error: Value::nothing(), }) .start() } } impl std::convert::From for ShellError { fn from(input: serde_json::Error) -> ShellError { ProximateShellError::String(StringError { title: format!("{:?}", input), error: Value::nothing(), }) .start() } } impl std::convert::From> for ShellError { fn from(input: Box) -> ShellError { ProximateShellError::String(StringError { title: format!("{:?}", input), error: Value::nothing(), }) .start() } } pub trait ShellErrorUtils { fn unwrap_error(self, desc: impl Into) -> Result; } impl ShellErrorUtils> for Option> { fn unwrap_error(self, desc: impl Into) -> Result, ShellError> { match self { Some(value) => Ok(value), None => Err(ShellError::missing_value(None, desc.into())), } } } 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 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, operation.into(), )), } } } impl CoerceInto<$ty> for 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, 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 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, operation.into(), )), } } } impl CoerceInto<$ty> for 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, operation.into(), )), } } } }; } ranged_decimal!(f32 -> to_f32 -> F32); ranged_decimal!(f64 -> to_f64 -> F64);