use std::num::ParseFloatError;
use lalrpop_util::ParseError;
use thiserror::Error;
use crate::{
errors::diagnostics::{ExprDiagnosisSeverity, ExprDiagnostic, get_range},
lexer::Token,
span::{Span, Spanned},
types::Type,
};
pub type ExprResult<T> = std::result::Result<T, Vec<ExprErrorS>>;
#[derive(Debug, Error, PartialEq)]
pub enum ExprError {
#[error("There was an error lexing expression: {0}")]
LexError(#[from] LexicalError),
#[error("There was an error in the expression syntax: {0}")]
SyntaxError(#[from] SyntaxError),
#[error("There was a compliation error with the expression: {0}")]
CompileError(#[from] CompileError),
#[error("There was a runtime error with the expression: {0}")]
RuntimeError(#[from] RuntimeError),
}
impl diagnostics::AsDiagnostic for ExprError {
fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
match self {
ExprError::LexError(e) => e.as_diagnostic(source, span),
ExprError::CompileError(e) => e.as_diagnostic(source, span),
ExprError::SyntaxError(e) => e.as_diagnostic(source, span),
ExprError::RuntimeError(e) => e.as_diagnostic(source, span),
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Error)]
pub enum LexicalError {
#[default]
#[error("Invalid token")]
InvalidToken,
#[error("Invalid number $0")]
InvalidNumber(ParseFloatError),
}
impl diagnostics::AsDiagnostic for LexicalError {
fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
let error_code = "lexical".to_string();
ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
}
}
}
#[derive(Debug, Clone, Error, PartialEq)]
pub enum SyntaxError {
#[error("extraneous input: {token:?}")]
ExtraToken { token: String },
#[error("invalid input")]
InvalidToken,
#[error("unexpected input: {token:?}")]
UnexpectedInput { token: String },
#[error("unexpected end of file; expected: {expected:?}")]
UnrecognizedEOF { expected: Vec<String> },
#[error("unexpected {token:?}; expected: {expected:?}")]
UnrecognizedToken {
token: String,
expected: Vec<String>,
},
#[error("unterminated string")]
UnterminatedString,
}
impl SyntaxError {
pub fn from_parser_error(
err: ParseError<usize, Token, ExprErrorS>,
source: &str,
) -> ExprErrorS {
match err {
ParseError::InvalidToken { location } => {
(SyntaxError::InvalidToken.into(), location..location)
}
ParseError::UnrecognizedEof { location, expected } => (
SyntaxError::UnrecognizedEOF { expected }.into(),
location..location,
),
ParseError::UnrecognizedToken {
token: (start, _, end),
expected,
} => (
SyntaxError::UnrecognizedToken {
token: source[start..end].to_string(),
expected,
}
.into(),
start..end,
),
ParseError::ExtraToken {
token: (start, _, end),
} => (
SyntaxError::ExtraToken {
token: source[start..end].to_string(),
}
.into(),
start..end,
),
ParseError::User { error } => error,
}
}
}
impl diagnostics::AsDiagnostic for SyntaxError {
fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
let error_code = "syntax".to_string();
match self {
SyntaxError::ExtraToken { token: _ } => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
SyntaxError::InvalidToken => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
SyntaxError::UnexpectedInput { token: _ } => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
SyntaxError::UnrecognizedEOF { expected: _ } => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
SyntaxError::UnrecognizedToken {
token: _,
expected: _,
} => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
SyntaxError::UnterminatedString => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Error)]
pub enum CompileError {
#[error("undefined: {0}")]
Undefined(String),
#[error("expects {expected} arguments but received {actual}")]
WrongNumberOfArgs { expected: usize, actual: usize },
#[error("call expression without a callee")]
NoCallee,
#[error("expected type {expected} but received {actual}")]
TypeMismatch { expected: Type, actual: Type },
#[error("invalid lookup type: {0}")]
InvalidLookupType(u8),
}
impl diagnostics::AsDiagnostic for CompileError {
fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
let error_code = "compiler".to_string();
match self {
CompileError::Undefined(_) => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
CompileError::WrongNumberOfArgs {
expected: _,
actual: _,
} => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
CompileError::NoCallee => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
CompileError::TypeMismatch {
expected: _,
actual: _,
} => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
CompileError::InvalidLookupType(_) => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Error)]
pub enum RuntimeError {
#[error("attempting to pop from an empty stack")]
EmptyStack,
#[error("expected type {expected} but received {actual}")]
TypeMismatch { expected: Type, actual: Type },
}
impl diagnostics::AsDiagnostic for RuntimeError {
fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
let error_code = "runtime".to_string();
match self {
RuntimeError::EmptyStack => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
RuntimeError::TypeMismatch {
expected: _,
actual: _,
} => ExprDiagnostic {
code: error_code,
range: get_range(source, span),
severity: Some(ExprDiagnosisSeverity::ERROR),
message: format!("{self}"),
},
}
}
}
pub type ExprErrorS = Spanned<ExprError>;
pub mod diagnostics {
use codespan_reporting::diagnostic::{Diagnostic, Label, Severity};
use line_col::LineColLookup;
use crate::{errors::ExprErrorS, span::Span};
pub fn get_diagnostics(errs: &[ExprErrorS], source: &str) -> Vec<Diagnostic<()>> {
errs.iter()
.map(|(err, span)| {
let a = err.as_diagnostic(source, span);
a.to_diagnostic(span).with_message(a.message.clone())
})
.collect()
}
pub trait AsDiagnostic {
fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic;
}
#[derive(Debug, Eq, PartialEq, Clone, Default)]
pub struct ExprDiagnostic {
pub code: String,
pub range: ExprDiagnosticRange,
pub severity: Option<ExprDiagnosisSeverity>,
pub message: String,
}
impl ExprDiagnostic {
pub fn to_diagnostic(&self, span: &Span) -> codespan_reporting::diagnostic::Diagnostic<()> {
codespan_reporting::diagnostic::Diagnostic {
severity: ExprDiagnosisSeverity::ERROR.to_severity(),
code: Some(self.code.clone()),
message: self.message.clone(),
labels: vec![Label::primary((), span.clone())],
notes: vec![],
}
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
pub struct ExprDiagnosisSeverity(i32);
#[allow(dead_code)]
impl ExprDiagnosisSeverity {
pub const ERROR: ExprDiagnosisSeverity = ExprDiagnosisSeverity(1);
pub const WARNING: ExprDiagnosisSeverity = ExprDiagnosisSeverity(2);
pub const INFORMATION: ExprDiagnosisSeverity = ExprDiagnosisSeverity(3);
pub const HINT: ExprDiagnosisSeverity = ExprDiagnosisSeverity(4);
}
impl ExprDiagnosisSeverity {
fn to_severity(self) -> Severity {
match self {
ExprDiagnosisSeverity::HINT => Severity::Help,
ExprDiagnosisSeverity::INFORMATION => Severity::Note,
ExprDiagnosisSeverity::WARNING => Severity::Warning,
ExprDiagnosisSeverity::ERROR => Severity::Error,
_ => panic!("Invalid diagnosis severity: {}", self.0),
}
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Default)]
pub struct ExprDiagnosticPosition {
pub line: u32,
pub character: u32,
}
impl ExprDiagnosticPosition {
pub fn new(line: u32, character: u32) -> ExprDiagnosticPosition {
ExprDiagnosticPosition { line, character }
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub struct ExprDiagnosticRange {
pub start: ExprDiagnosticPosition,
pub end: ExprDiagnosticPosition,
}
impl ExprDiagnosticRange {
pub fn new(
start: ExprDiagnosticPosition,
end: ExprDiagnosticPosition,
) -> ExprDiagnosticRange {
ExprDiagnosticRange { start, end }
}
}
pub fn get_range(source: &str, span: &Span) -> ExprDiagnosticRange {
ExprDiagnosticRange::new(
get_position(source, span.start),
get_position(source, span.end),
)
}
pub fn get_position(source: &str, idx: usize) -> ExprDiagnosticPosition {
let (line, character) = index_to_position(source, idx);
ExprDiagnosticPosition::new(line as u32, character as u32)
}
pub fn index_to_position(source: &str, index: usize) -> (usize, usize) {
let lookup = LineColLookup::new(source);
let (line, char) = lookup.get(index);
(line - 1, char - 1)
}
pub fn position_to_index(source: &str, position: (usize, usize)) -> usize {
let (line, character) = position;
let lines = source.split('\n');
let lines_before = lines.take(line);
let line_chars_before = lines_before.fold(0usize, |acc, e| acc + e.len() + 1);
let chars = character;
line_chars_before + chars
}
#[cfg(test)]
mod index_position_fn_tests {
use super::*;
#[test]
fn it_should_convert_index_to_position() {
let source = "let a = 123;\nlet b = 456;";
let index = 17usize;
let expected_position = (1, 4);
let index_to_position = index_to_position(source, index);
let actual_position = index_to_position;
assert_eq!(expected_position, actual_position);
}
#[test]
fn it_should_convert_position_to_index() {
let source = "let a = 123;\nlet b = 456;";
let position = (1, 4);
let expected_index = 17usize;
let actual_index = position_to_index(source, position);
assert_eq!(expected_index, actual_index);
}
#[test]
fn it_should_convert_position_to_index_and_back() {
let source = "let a = 123;\nlet b = 456;";
let position = (1, 4);
let actual_index = position_to_index(source, position);
assert_eq!(position, index_to_position(source, actual_index));
}
#[test]
fn it_should_convert_position_to_index_and_back_b() {
let source = "let a = 123;\n{\n let b = 456;\n}";
let position = (2, 12);
let actual_index = position_to_index(source, position);
assert_eq!(position, index_to_position(source, actual_index));
}
#[test]
fn it_should_convert_position_to_index_b() {
let source = "let a = 123;\n{\n let b = 456;\n}";
let position = (2, 12);
let actual_index = position_to_index(source, position);
assert_eq!(27, actual_index);
}
#[test]
fn it_should_convert_position_to_index_c() {
let source = "let a = 123;\nlet b = 456;\nlet c = 789;";
let position = (2, 8);
let actual_index = position_to_index(source, position);
assert_eq!(34, actual_index);
}
#[test]
fn it_should_convert_position_to_index_d() {
let source = "let a = 123;\nlet b = 456;\nlet c = 789;\nlet d = 000;";
let position = (3, 8);
let actual_index = position_to_index(source, position);
assert_eq!(47, actual_index);
}
#[test]
fn it_should_convert_position_to_index_e() {
let source = "let a = 123;\nlet b = 456;\nlet c = 789;\nlet d = 000;\nlet e = 999;";
let position = (4, 8);
let actual_index = position_to_index(source, position);
assert_eq!(60, actual_index);
}
#[test]
fn it_should_convert_position_to_index_f() {
let source = "let a = 123;\nlet b = 456;\nlet c = 789;\nlet d = 000;\nlet e = 999;\n";
let position = (4, 8);
let actual_index = position_to_index(source, position);
assert_eq!(60, actual_index);
}
}
#[cfg(test)]
mod error_to_diagnostics_tests {
use crate::{
errors::{CompileError, ExprError, LexicalError, RuntimeError, SyntaxError},
types::Type,
};
use super::*;
use std::ops::Range;
fn dummy_source() -> &'static str {
"fn test_function(x: i32) -> i32 { x + 1 }"
}
fn dummy_range() -> Span {
Range { start: 0, end: 5 }
}
#[test]
fn it_converts_lexerror_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::LexError(LexicalError::InvalidToken);
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("lexical".to_string()));
assert_eq!(diagnostic.message, "Invalid token".to_string());
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_compileerror_undefined_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::CompileError(CompileError::Undefined("var".to_string()));
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("compiler".to_string()));
assert_eq!(diagnostic.message, "undefined: var".to_string());
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_compileerror_wrong_number_of_args_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::CompileError(CompileError::WrongNumberOfArgs {
expected: 2,
actual: 3,
});
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("compiler".to_string()));
assert_eq!(
diagnostic.message,
"expects 2 arguments but received 3".to_string()
);
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_compileerror_no_callee_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::CompileError(CompileError::NoCallee);
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("compiler".to_string()));
assert_eq!(
diagnostic.message,
"call expression without a callee".to_string()
);
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_compileerror_type_mismatch_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::CompileError(CompileError::TypeMismatch {
expected: Type::String,
actual: Type::Bool,
});
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("compiler".to_string()));
assert_eq!(
diagnostic.message,
"expected type String but received Bool".to_string()
);
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_compileerror_invalid_lookup_type_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::CompileError(CompileError::InvalidLookupType(99));
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("compiler".to_string()));
assert_eq!(diagnostic.message, "invalid lookup type: 99".to_string());
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_runtimeerror_undefined_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::RuntimeError(RuntimeError::TypeMismatch {
expected: Type::Bool,
actual: Type::String,
});
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("runtime".to_string()));
assert_eq!(
diagnostic.message,
"expected type Bool but received String".to_string()
);
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_runtimeerror_empty_stack_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::RuntimeError(RuntimeError::EmptyStack);
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("runtime".to_string()));
assert_eq!(
diagnostic.message,
"attempting to pop from an empty stack".to_string()
);
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_syntaxerror_unrecognized_eof_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::SyntaxError(SyntaxError::UnrecognizedEOF {
expected: vec!["string".to_string()],
});
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("syntax".to_string()));
assert_eq!(
diagnostic.message,
"unexpected end of file; expected: [\"string\"]".to_string()
);
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_syntaxerror_invalid_token_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::SyntaxError(SyntaxError::InvalidToken);
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("syntax".to_string()));
assert_eq!(diagnostic.message, "invalid input".to_string());
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_syntaxerror_unexpected_input_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::SyntaxError(SyntaxError::UnexpectedInput {
token: "number".to_string(),
});
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("syntax".to_string()));
assert_eq!(
diagnostic.message,
"unexpected input: \"number\"".to_string()
);
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_syntaxerror_unrecognized_token_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::SyntaxError(SyntaxError::UnrecognizedToken {
token: "number".to_string(),
expected: vec![",".to_string(), "number".to_string(), "]".to_string()],
});
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("syntax".to_string()));
assert_eq!(
diagnostic.message,
"unexpected \"number\"; expected: [\",\", \"number\", \"]\"]".to_string()
);
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_syntaxerror_unterminated_string_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::SyntaxError(SyntaxError::UnterminatedString);
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("syntax".to_string()));
assert_eq!(diagnostic.message, "unterminated string".to_string());
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
#[test]
fn it_converts_syntaxerror_extra_token_to_diagnostic() {
let source = dummy_source();
let range = dummy_range();
let error = ExprError::SyntaxError(SyntaxError::ExtraToken {
token: ",".to_string(),
});
let diagnostics = get_diagnostics(&[(error, range.clone())], source);
assert_eq!(diagnostics.len(), 1);
let diagnostic = &diagnostics[0];
assert_eq!(diagnostic.code, Some("syntax".to_string()));
assert_eq!(diagnostic.message, "extraneous input: \",\"".to_string());
assert_eq!(diagnostic.severity, Severity::Error);
assert_eq!(diagnostic.labels.len(), 1);
assert_eq!(diagnostic.labels[0], Label::primary((), range));
}
}
#[cfg(test)]
mod to_severity_tests {
use codespan_reporting::diagnostic::Severity;
use crate::errors::diagnostics::ExprDiagnosisSeverity;
#[test]
fn from_hint() {
assert_eq!(Severity::Help, ExprDiagnosisSeverity::HINT.to_severity());
}
#[test]
fn from_information() {
assert_eq!(
Severity::Note,
ExprDiagnosisSeverity::INFORMATION.to_severity()
);
}
#[test]
fn from_warning() {
assert_eq!(
Severity::Warning,
ExprDiagnosisSeverity::WARNING.to_severity()
);
}
#[test]
#[should_panic(expected = "Invalid diagnosis severity: 99")]
fn from_invalid() {
ExprDiagnosisSeverity(99).to_severity();
}
}
}