use crate::core::TypeId;
use crate::validate::stream::SourcePosition;
#[derive(Debug, Clone)]
pub enum ValidationError {
TypeMismatch {
expected: TypeId,
found: TypeId,
},
UndefinedIdentifier(String),
UndefinedField {
typ: TypeId,
field: String,
},
UndefinedMethod {
typ: TypeId,
method: String,
},
ArityMismatch {
expected: usize,
found: usize,
},
InvalidOperation {
op: String,
typ: TypeId,
},
InterfaceNotSatisfied {
typ: TypeId,
interface: TypeId,
missing_methods: Vec<String>,
},
CyclicType,
GenericArityMismatch {
expected: usize,
found: usize,
},
ConstraintViolation {
param: String,
constraint: String,
},
}
impl ValidationError {
pub fn soften(self) -> SoftError {
let (message, suggestion, severity) = match &self {
Self::TypeMismatch { expected, found } => (
format!("Type mismatch: expected {:?}, found {:?}", expected, found),
Some(format!("Consider converting or using a different type")),
ErrorSeverity::Warning,
),
Self::UndefinedIdentifier(name) => {
(
format!("'{}' is not defined", name),
Some(format!("Define '{}' or import it", name)),
ErrorSeverity::Warning, )
}
Self::UndefinedField { typ: _, field } => (
format!("Field '{}' not found", field),
None,
ErrorSeverity::Error,
),
Self::ArityMismatch { expected, found } => (
format!("Expected {} arguments, found {}", expected, found),
Some(format!("Adjust the number of arguments")),
ErrorSeverity::Warning,
),
_ => (format!("{:?}", self), None, ErrorSeverity::Warning),
};
SoftError {
message,
suggestion,
severity,
}
}
}
#[derive(Debug, Clone)]
pub struct SoftError {
pub message: String,
pub suggestion: Option<String>,
pub severity: ErrorSeverity,
}
impl SoftError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
suggestion: None,
severity: ErrorSeverity::Warning,
}
}
pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
self.suggestion = Some(suggestion.into());
self
}
pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
self.severity = severity;
self
}
pub fn is_blocking(&self) -> bool {
matches!(self.severity, ErrorSeverity::Error | ErrorSeverity::Fatal)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
Hint,
Warning,
Error,
Fatal,
}
impl ErrorSeverity {
pub fn as_str(&self) -> &'static str {
match self {
Self::Hint => "hint",
Self::Warning => "warning",
Self::Error => "error",
Self::Fatal => "fatal",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ErrorCollection {
errors: Vec<ValidationError>,
soft_errors: Vec<SoftError>,
}
impl ErrorCollection {
pub fn new() -> Self {
Self::default()
}
pub fn add_error(&mut self, error: ValidationError) {
self.errors.push(error);
}
pub fn add_soft_error(&mut self, error: SoftError) {
self.soft_errors.push(error);
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty() || self.soft_errors.iter().any(|e| e.is_blocking())
}
pub fn has_soft_errors(&self) -> bool {
!self.soft_errors.is_empty()
}
pub fn is_empty(&self) -> bool {
self.errors.is_empty() && self.soft_errors.is_empty()
}
pub fn len(&self) -> usize {
self.errors.len() + self.soft_errors.len()
}
pub fn soften(self) -> Vec<SoftError> {
let mut result: Vec<SoftError> = self.errors.into_iter().map(|e| e.soften()).collect();
result.extend(self.soft_errors);
result
}
pub fn filter_by_severity(&self, min_severity: ErrorSeverity) -> Vec<&SoftError> {
let severity_order = |s: &ErrorSeverity| match s {
ErrorSeverity::Fatal => 3,
ErrorSeverity::Error => 2,
ErrorSeverity::Warning => 1,
ErrorSeverity::Hint => 0,
};
let min_order = severity_order(&min_severity);
self.soft_errors
.iter()
.filter(|e| severity_order(&e.severity) >= min_order)
.collect()
}
pub fn iter_errors(&self) -> impl Iterator<Item = &ValidationError> {
self.errors.iter()
}
pub fn iter_soft_errors(&self) -> impl Iterator<Item = &SoftError> {
self.soft_errors.iter()
}
}
#[derive(Debug, Clone)]
pub struct LocatedError {
pub error: ValidationError,
pub position: SourcePosition,
pub context: String,
}
impl LocatedError {
pub fn new(
error: ValidationError,
position: SourcePosition,
context: impl Into<String>,
) -> Self {
Self {
error,
position,
context: context.into(),
}
}
pub fn format(&self) -> String {
format!(
"{}:{}: {}\n Context: {}",
self.position.line,
self.position.column,
format!("{:?}", self.error),
self.context
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_soft_error_creation() {
let error = SoftError::new("Type mismatch")
.with_suggestion("Use int instead")
.with_severity(ErrorSeverity::Warning);
assert!(!error.is_blocking());
assert_eq!(error.message, "Type mismatch");
}
#[test]
fn test_error_collection() {
let mut collection = ErrorCollection::new();
collection.add_soft_error(SoftError::new("Warning 1"));
collection.add_soft_error(SoftError::new("Error 1").with_severity(ErrorSeverity::Error));
assert!(collection.has_soft_errors());
assert!(collection.has_errors()); }
#[test]
fn test_error_soften() {
let error = ValidationError::UndefinedIdentifier("foo".to_string());
let soft = error.soften();
assert!(!soft.is_blocking());
assert!(soft.suggestion.is_some());
}
#[test]
fn test_error_collection_empty() {
let collection = ErrorCollection::new();
assert!(collection.is_empty());
assert!(!collection.has_errors());
assert_eq!(collection.len(), 0);
}
#[test]
fn test_severity_levels() {
use super::ErrorSeverity;
let hint = SoftError::new("hint").with_severity(ErrorSeverity::Hint);
let warning = SoftError::new("warning").with_severity(ErrorSeverity::Warning);
let error = SoftError::new("error").with_severity(ErrorSeverity::Error);
assert!(!hint.is_blocking());
assert!(!warning.is_blocking());
assert!(error.is_blocking());
}
#[test]
fn test_error_display() {
let error = ValidationError::UndefinedIdentifier("x".to_string());
let soft = error.soften();
assert!(!soft.message.is_empty());
}
#[test]
fn test_error_filtering() {
let mut collection = ErrorCollection::new();
collection.add_soft_error(SoftError::new("hint").with_severity(ErrorSeverity::Hint));
collection.add_soft_error(SoftError::new("error").with_severity(ErrorSeverity::Error));
let warnings_and_above = collection.filter_by_severity(ErrorSeverity::Warning);
assert_eq!(warnings_and_above.len(), 1);
}
#[test]
fn test_located_error() {
use super::super::stream::SourcePosition;
let error = ValidationError::CyclicType;
let position = SourcePosition::new(10, 5, 100);
let located = LocatedError::new(error, position, "test context");
let formatted = located.format();
assert!(formatted.contains("10"));
assert!(formatted.contains("test context"));
}
}