sea_core/
validation_error.rs

1use crate::units::Dimension;
2#[cfg(feature = "python")]
3use pyo3::{IntoPyObject, PyAny, Python};
4use std::fmt;
5
6/// Threshold for fuzzy matching suggestions (Levenshtein distance)
7pub const FUZZY_MATCH_THRESHOLD: usize = 2;
8
9/// Position in source code (line and column, 1-indexed)
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct Position {
12    pub line: usize,
13    pub column: usize,
14}
15
16impl Position {
17    pub fn new(line: usize, column: usize) -> Result<Self, String> {
18        if line == 0 || column == 0 {
19            Err(format!(
20                "Position line and column must be >= 1. Got line={}, column={}",
21                line, column
22            ))
23        } else {
24            Ok(Self { line, column })
25        }
26    }
27}
28
29impl fmt::Display for Position {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "{}:{}", self.line, self.column)
32    }
33}
34
35/// Source range with start and end positions
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct SourceRange {
38    pub start: Position,
39    pub end: Position,
40}
41
42impl SourceRange {
43    pub fn new(start: Position, end: Position) -> Self {
44        Self { start, end }
45    }
46
47    pub fn from_line_col(
48        start_line: usize,
49        start_col: usize,
50        end_line: usize,
51        end_col: usize,
52    ) -> Result<Self, String> {
53        Ok(Self {
54            start: Position::new(start_line, start_col)?,
55            end: Position::new(end_line, end_col)?,
56        })
57    }
58
59    pub fn single_position(line: usize, column: usize) -> Result<Self, String> {
60        let pos = Position::new(line, column)?;
61        Ok(Self {
62            start: pos,
63            end: pos,
64        })
65    }
66}
67
68impl fmt::Display for SourceRange {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        if self.start == self.end {
71            write!(f, "{}", self.start)
72        } else if self.start.line == self.end.line {
73            write!(
74                f,
75                "{}:{}-{}",
76                self.start.line, self.start.column, self.end.column
77            )
78        } else {
79            write!(f, "{} to {}", self.start, self.end)
80        }
81    }
82}
83
84/// Error codes for all validation errors
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[allow(non_camel_case_types)]
87pub enum ErrorCode {
88    // E001-E099: Syntax and parsing errors
89    E001_UndefinedEntity,
90    E002_UndefinedResource,
91    E003_UnitMismatch,
92    E004_TypeMismatch,
93    E005_SyntaxError,
94    E006_InvalidExpression,
95    E007_DuplicateDeclaration,
96    E008_UndefinedVariable,
97    E009_InvalidQuantity,
98    E010_InvalidIdentifier,
99
100    // E100-E199: Type system errors
101    E100_IncompatibleTypes,
102    E101_InvalidTypeConversion,
103    E102_TypeInferenceFailed,
104    E103_InvalidOperandType,
105    E104_InvalidComparisonType,
106
107    // E200-E299: Unit and dimension errors
108    E200_DimensionMismatch,
109    E201_InvalidUnit,
110    E202_UnitConversionFailed,
111    E203_IncompatibleDimensions,
112
113    // E300-E399: Scope and reference errors
114    E300_VariableNotInScope,
115    E301_UndefinedReference,
116    E302_CircularReference,
117    E303_InvalidReference,
118
119    // E400-E499: Policy validation errors
120    E400_PolicyEvaluationFailed,
121    E401_InvalidPolicyExpression,
122    E402_DeterminismViolation,
123    E403_InvalidModality,
124
125    // E500-E599: Namespace and module errors
126    E500_NamespaceNotFound,
127    E501_AmbiguousNamespace,
128    E502_InvalidNamespace,
129    E503_ModuleNotFound,
130    E504_SymbolNotExported,
131    E505_CircularDependency,
132    E506_AmbiguousImport,
133    E507_InvalidExport,
134}
135
136impl ErrorCode {
137    pub fn as_str(&self) -> &'static str {
138        match self {
139            // Syntax and parsing
140            ErrorCode::E001_UndefinedEntity => "E001",
141            ErrorCode::E002_UndefinedResource => "E002",
142            ErrorCode::E003_UnitMismatch => "E003",
143            ErrorCode::E004_TypeMismatch => "E004",
144            ErrorCode::E005_SyntaxError => "E005",
145            ErrorCode::E006_InvalidExpression => "E006",
146            ErrorCode::E007_DuplicateDeclaration => "E007",
147            ErrorCode::E008_UndefinedVariable => "E008",
148            ErrorCode::E009_InvalidQuantity => "E009",
149            ErrorCode::E010_InvalidIdentifier => "E010",
150
151            // Type system
152            ErrorCode::E100_IncompatibleTypes => "E100",
153            ErrorCode::E101_InvalidTypeConversion => "E101",
154            ErrorCode::E102_TypeInferenceFailed => "E102",
155            ErrorCode::E103_InvalidOperandType => "E103",
156            ErrorCode::E104_InvalidComparisonType => "E104",
157
158            // Units and dimensions
159            ErrorCode::E200_DimensionMismatch => "E200",
160            ErrorCode::E201_InvalidUnit => "E201",
161            ErrorCode::E202_UnitConversionFailed => "E202",
162            ErrorCode::E203_IncompatibleDimensions => "E203",
163
164            // Scope and references
165            ErrorCode::E300_VariableNotInScope => "E300",
166            ErrorCode::E301_UndefinedReference => "E301",
167            ErrorCode::E302_CircularReference => "E302",
168            ErrorCode::E303_InvalidReference => "E303",
169
170            // Policy validation
171            ErrorCode::E400_PolicyEvaluationFailed => "E400",
172            ErrorCode::E401_InvalidPolicyExpression => "E401",
173            ErrorCode::E402_DeterminismViolation => "E402",
174            ErrorCode::E403_InvalidModality => "E403",
175
176            // Namespace and modules
177            ErrorCode::E500_NamespaceNotFound => "E500",
178            ErrorCode::E501_AmbiguousNamespace => "E501",
179            ErrorCode::E502_InvalidNamespace => "E502",
180            ErrorCode::E503_ModuleNotFound => "E503",
181            ErrorCode::E504_SymbolNotExported => "E504",
182            ErrorCode::E505_CircularDependency => "E505",
183            ErrorCode::E506_AmbiguousImport => "E506",
184            ErrorCode::E507_InvalidExport => "E507",
185        }
186    }
187
188    pub fn description(&self) -> &'static str {
189        match self {
190            ErrorCode::E001_UndefinedEntity => "Undefined entity",
191            ErrorCode::E002_UndefinedResource => "Undefined resource",
192            ErrorCode::E003_UnitMismatch => "Unit mismatch",
193            ErrorCode::E004_TypeMismatch => "Type mismatch",
194            ErrorCode::E005_SyntaxError => "Syntax error",
195            ErrorCode::E006_InvalidExpression => "Invalid expression",
196            ErrorCode::E007_DuplicateDeclaration => "Duplicate declaration",
197            ErrorCode::E008_UndefinedVariable => "Undefined variable",
198            ErrorCode::E009_InvalidQuantity => "Invalid quantity",
199            ErrorCode::E010_InvalidIdentifier => "Invalid identifier",
200            ErrorCode::E100_IncompatibleTypes => "Incompatible types",
201            ErrorCode::E101_InvalidTypeConversion => "Invalid type conversion",
202            ErrorCode::E102_TypeInferenceFailed => "Type inference failed",
203            ErrorCode::E103_InvalidOperandType => "Invalid operand type",
204            ErrorCode::E104_InvalidComparisonType => "Invalid comparison type",
205            ErrorCode::E200_DimensionMismatch => "Dimension mismatch",
206            ErrorCode::E201_InvalidUnit => "Invalid unit",
207            ErrorCode::E202_UnitConversionFailed => "Unit conversion failed",
208            ErrorCode::E203_IncompatibleDimensions => "Incompatible dimensions",
209            ErrorCode::E300_VariableNotInScope => "Variable not in scope",
210            ErrorCode::E301_UndefinedReference => "Undefined reference",
211            ErrorCode::E302_CircularReference => "Circular reference",
212            ErrorCode::E303_InvalidReference => "Invalid reference",
213            ErrorCode::E400_PolicyEvaluationFailed => "Policy evaluation failed",
214            ErrorCode::E401_InvalidPolicyExpression => "Invalid policy expression",
215            ErrorCode::E402_DeterminismViolation => "Determinism violation",
216            ErrorCode::E403_InvalidModality => "Invalid modality",
217            ErrorCode::E500_NamespaceNotFound => "Namespace not found",
218            ErrorCode::E501_AmbiguousNamespace => "Ambiguous namespace",
219            ErrorCode::E502_InvalidNamespace => "Invalid namespace",
220            ErrorCode::E503_ModuleNotFound => "Module not found",
221            ErrorCode::E504_SymbolNotExported => "Imported symbol is not exported",
222            ErrorCode::E505_CircularDependency => "Circular dependency detected",
223            ErrorCode::E506_AmbiguousImport => "Ambiguous import",
224            ErrorCode::E507_InvalidExport => "Invalid export",
225        }
226    }
227}
228
229impl fmt::Display for ErrorCode {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        write!(f, "{}", self.as_str())
232    }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
236pub enum ReferenceType {
237    Entity,
238    Resource,
239    Variable,
240    Flow,
241    Other(String),
242}
243
244impl fmt::Display for ReferenceType {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        match self {
247            ReferenceType::Entity => write!(f, "Entity"),
248            ReferenceType::Resource => write!(f, "Resource"),
249            ReferenceType::Variable => write!(f, "Variable"),
250            ReferenceType::Flow => write!(f, "Flow"),
251            ReferenceType::Other(s) => write!(f, "{}", s),
252        }
253    }
254}
255
256#[cfg(feature = "python")]
257impl<'py> IntoPyObject<'py> for ReferenceType {
258    type Target = PyAny;
259    type Output = pyo3::Bound<'py, PyAny>;
260    type Error = pyo3::PyErr;
261
262    fn into_pyobject(self, py: Python<'py>) -> pyo3::PyResult<Self::Output> {
263        Ok(pyo3::types::PyString::new(py, &self.to_string()).into_any())
264    }
265}
266
267#[derive(Debug, Clone)]
268pub enum ValidationError {
269    SyntaxError {
270        message: String,
271        line: usize,
272        column: usize,
273        end_line: Option<usize>,
274        end_column: Option<usize>,
275    },
276    TypeError {
277        message: String,
278        location: String,
279        expected_type: Option<String>,
280        found_type: Option<String>,
281        suggestion: Option<String>,
282    },
283    UnitError {
284        expected: Dimension,
285        found: Dimension,
286        location: String,
287        suggestion: Option<String>,
288    },
289    ScopeError {
290        variable: String,
291        available_in: Vec<String>,
292        location: String,
293        suggestion: Option<String>,
294    },
295    DeterminismError {
296        message: String,
297        hint: String,
298    },
299    UndefinedReference {
300        reference_type: ReferenceType,
301        name: String,
302        location: String,
303        suggestion: Option<String>,
304    },
305    DuplicateDeclaration {
306        name: String,
307        first_location: String,
308        second_location: String,
309    },
310    InvalidExpression {
311        message: String,
312        location: String,
313        suggestion: Option<String>,
314    },
315}
316
317impl ValidationError {
318    /// Get the error code for this validation error
319    pub fn error_code(&self) -> ErrorCode {
320        match self {
321            ValidationError::SyntaxError { .. } => ErrorCode::E005_SyntaxError,
322            ValidationError::TypeError { .. } => ErrorCode::E004_TypeMismatch,
323            ValidationError::UnitError { .. } => ErrorCode::E003_UnitMismatch,
324            ValidationError::ScopeError { .. } => ErrorCode::E300_VariableNotInScope,
325            ValidationError::DeterminismError { .. } => ErrorCode::E402_DeterminismViolation,
326            ValidationError::UndefinedReference { reference_type, .. } => match reference_type {
327                ReferenceType::Entity => ErrorCode::E001_UndefinedEntity,
328                ReferenceType::Resource => ErrorCode::E002_UndefinedResource,
329                ReferenceType::Variable => ErrorCode::E008_UndefinedVariable,
330                _ => ErrorCode::E301_UndefinedReference,
331            },
332            ValidationError::DuplicateDeclaration { .. } => ErrorCode::E007_DuplicateDeclaration,
333            ValidationError::InvalidExpression { .. } => ErrorCode::E006_InvalidExpression,
334        }
335    }
336
337    /// Get the source range for this error (if available)
338    pub fn range(&self) -> Option<SourceRange> {
339        match self {
340            ValidationError::SyntaxError {
341                line,
342                column,
343                end_line,
344                end_column,
345                ..
346            } => {
347                // We ignore errors here as this is just for reporting
348                let start =
349                    Position::new(*line, *column).unwrap_or_else(|_| Position::new(1, 1).unwrap());
350                let end = match (end_line, end_column) {
351                    (Some(el), Some(ec)) => Position::new(*el, *ec).unwrap_or(start),
352                    _ => start,
353                };
354                Some(SourceRange::new(start, end))
355            }
356            _ => None, // Other variants use string locations for now
357        }
358    }
359
360    /// Get a user-friendly location string
361    pub fn location_string(&self) -> Option<String> {
362        match self {
363            ValidationError::SyntaxError { line, column, .. } => {
364                Some(format!("{}:{}", line, column))
365            }
366            ValidationError::TypeError { location, .. }
367            | ValidationError::UnitError { location, .. }
368            | ValidationError::ScopeError { location, .. }
369            | ValidationError::UndefinedReference { location, .. }
370            | ValidationError::InvalidExpression { location, .. } => Some(location.clone()),
371            ValidationError::DuplicateDeclaration {
372                second_location, ..
373            } => Some(second_location.clone()),
374            ValidationError::DeterminismError { .. } => None,
375        }
376    }
377
378    pub fn syntax_error(message: impl Into<String>, line: usize, column: usize) -> Self {
379        Self::SyntaxError {
380            message: message.into(),
381            line,
382            column,
383            end_line: None,
384            end_column: None,
385        }
386    }
387
388    pub fn syntax_error_with_range(
389        message: impl Into<String>,
390        line: usize,
391        column: usize,
392        end_line: usize,
393        end_column: usize,
394    ) -> Self {
395        Self::SyntaxError {
396            message: message.into(),
397            line,
398            column,
399            end_line: Some(end_line),
400            end_column: Some(end_column),
401        }
402    }
403
404    pub fn type_error(message: impl Into<String>, location: impl Into<String>) -> Self {
405        Self::TypeError {
406            message: message.into(),
407            location: location.into(),
408            expected_type: None,
409            found_type: None,
410            suggestion: None,
411        }
412    }
413
414    pub fn unit_error(expected: Dimension, found: Dimension, location: impl Into<String>) -> Self {
415        Self::UnitError {
416            expected,
417            found,
418            location: location.into(),
419            suggestion: None,
420        }
421    }
422
423    pub fn scope_error(
424        variable: impl Into<String>,
425        available_in: Vec<String>,
426        location: impl Into<String>,
427    ) -> Self {
428        Self::ScopeError {
429            variable: variable.into(),
430            available_in,
431            location: location.into(),
432            suggestion: None,
433        }
434    }
435
436    pub fn determinism_error(message: impl Into<String>, hint: impl Into<String>) -> Self {
437        Self::DeterminismError {
438            message: message.into(),
439            hint: hint.into(),
440        }
441    }
442
443    pub fn undefined_reference(
444        reference_type: impl Into<String>,
445        name: impl Into<String>,
446        location: impl Into<String>,
447    ) -> Self {
448        Self::UndefinedReference {
449            reference_type: ReferenceType::Other(reference_type.into()),
450            name: name.into(),
451            location: location.into(),
452            suggestion: None,
453        }
454    }
455
456    pub fn duplicate_declaration(
457        name: impl Into<String>,
458        first_location: impl Into<String>,
459        second_location: impl Into<String>,
460    ) -> Self {
461        Self::DuplicateDeclaration {
462            name: name.into(),
463            first_location: first_location.into(),
464            second_location: second_location.into(),
465        }
466    }
467
468    pub fn invalid_expression(message: impl Into<String>, location: impl Into<String>) -> Self {
469        Self::InvalidExpression {
470            message: message.into(),
471            location: location.into(),
472            suggestion: None,
473        }
474    }
475
476    pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
477        match &mut self {
478            ValidationError::UnitError { suggestion: s, .. } => {
479                *s = Some(suggestion.into());
480            }
481            ValidationError::TypeError { suggestion: s, .. } => {
482                *s = Some(suggestion.into());
483            }
484            ValidationError::ScopeError { suggestion: s, .. } => {
485                *s = Some(suggestion.into());
486            }
487            ValidationError::UndefinedReference { suggestion: s, .. } => {
488                *s = Some(suggestion.into());
489            }
490            ValidationError::InvalidExpression { suggestion: s, .. } => {
491                *s = Some(suggestion.into());
492            }
493            _ => {}
494        }
495        self
496    }
497
498    pub fn with_types(
499        mut self,
500        expected_type: impl Into<String>,
501        found_type: impl Into<String>,
502    ) -> Self {
503        if let ValidationError::TypeError {
504            expected_type: e,
505            found_type: f,
506            ..
507        } = &mut self
508        {
509            *e = Some(expected_type.into());
510            *f = Some(found_type.into());
511        }
512        self
513    }
514
515    // Additional convenience constructors for common error patterns
516
517    /// Create an error for an undefined Entity with a helpful suggestion
518    pub fn undefined_entity(name: impl Into<String>, location: impl Into<String>) -> Self {
519        let name = name.into();
520        Self::UndefinedReference {
521            reference_type: ReferenceType::Entity,
522            name: name.clone(),
523            location: location.into(),
524            suggestion: Some(format!("Did you mean to define 'Entity \"{}\"'?", name)),
525        }
526    }
527
528    /// Create an error for an undefined Resource with a helpful suggestion
529    pub fn undefined_resource(name: impl Into<String>, location: impl Into<String>) -> Self {
530        let name = name.into();
531        Self::UndefinedReference {
532            reference_type: ReferenceType::Resource,
533            name: name.clone(),
534            location: location.into(),
535            suggestion: Some(format!("Did you mean to define 'Resource \"{}\"'?", name)),
536        }
537    }
538
539    /// Create an error for an undefined Flow with a helpful suggestion
540    pub fn undefined_flow(name: impl Into<String>, location: impl Into<String>) -> Self {
541        let name = name.into();
542        Self::UndefinedReference {
543            reference_type: ReferenceType::Flow,
544            name: name.clone(),
545            location: location.into(),
546            suggestion: Some(format!(
547                "Did you mean to define a Flow involving '{}'?",
548                name
549            )),
550        }
551    }
552
553    /// Create a unit mismatch error with automatic suggestion
554    pub fn unit_mismatch(
555        expected: Dimension,
556        found: Dimension,
557        location: impl Into<String>,
558    ) -> Self {
559        let suggestion = Some(format!(
560            "Expected dimension {:?} but found {:?}. Consider using unit conversion or checking your unit definitions.",
561            expected, found
562        ));
563        Self::UnitError {
564            expected,
565            found,
566            location: location.into(),
567            suggestion,
568        }
569    }
570
571    /// Create a type mismatch error with types and suggestion
572    pub fn type_mismatch(
573        expected: impl Into<String>,
574        found: impl Into<String>,
575        location: impl Into<String>,
576    ) -> Self {
577        let expected = expected.into();
578        let found = found.into();
579        Self::TypeError {
580            message: format!("Type mismatch: expected {}, found {}", expected, found),
581            location: location.into(),
582            expected_type: Some(expected.clone()),
583            found_type: Some(found.clone()),
584            suggestion: Some(format!(
585                "Convert {} to {} or adjust the expression",
586                found, expected
587            )),
588        }
589    }
590
591    /// Create a scope error with available variables listed
592    pub fn variable_not_in_scope(
593        variable: impl Into<String>,
594        available: Vec<String>,
595        location: impl Into<String>,
596    ) -> Self {
597        let variable = variable.into();
598        let suggestion = if !available.is_empty() {
599            Some(format!(
600                "Available variables: {}. Did you mean one of these?",
601                available.join(", ")
602            ))
603        } else {
604            Some("No variables are currently in scope.".to_string())
605        };
606
607        Self::ScopeError {
608            variable,
609            available_in: available,
610            location: location.into(),
611            suggestion,
612        }
613    }
614
615    /// Create an undefined entity error with fuzzy matching suggestions
616    ///
617    /// # Arguments
618    /// * `name` - The undefined entity name
619    /// * `location` - Source location of the error
620    /// * `candidates` - Available entity names to suggest
621    fn undefined_reference_with_candidates(
622        reference_type: ReferenceType,
623        name: String,
624        location: String,
625        candidates: &[String],
626    ) -> Self {
627        use crate::error::fuzzy::find_best_match;
628
629        let suggestion = find_best_match(&name, candidates, FUZZY_MATCH_THRESHOLD)
630            .map(|match_name| format!("Did you mean '{}'?", match_name))
631            .or_else(|| {
632                Some(format!(
633                    "Did you mean to define '{} \"{}\"'?",
634                    reference_type, name
635                ))
636            });
637
638        Self::UndefinedReference {
639            reference_type,
640            name,
641            location,
642            suggestion,
643        }
644    }
645
646    /// Create an undefined entity error with fuzzy matching suggestions
647    ///
648    /// # Arguments
649    /// * `name` - The undefined entity name
650    /// * `location` - Source location of the error
651    /// * `candidates` - Available entity names to suggest
652    pub fn undefined_entity_with_candidates(
653        name: impl Into<String>,
654        location: impl Into<String>,
655        candidates: &[String],
656    ) -> Self {
657        Self::undefined_reference_with_candidates(
658            ReferenceType::Entity,
659            name.into(),
660            location.into(),
661            candidates,
662        )
663    }
664
665    /// Create an undefined resource error with fuzzy matching suggestions
666    pub fn undefined_resource_with_candidates(
667        name: impl Into<String>,
668        location: impl Into<String>,
669        candidates: &[String],
670    ) -> Self {
671        Self::undefined_reference_with_candidates(
672            ReferenceType::Resource,
673            name.into(),
674            location.into(),
675            candidates,
676        )
677    }
678
679    /// Create an undefined variable error with fuzzy matching suggestions
680    pub fn undefined_variable_with_candidates(
681        name: impl Into<String>,
682        location: impl Into<String>,
683        candidates: &[String],
684    ) -> Self {
685        use crate::error::fuzzy::suggest_similar;
686
687        let name = name.into();
688        let matches = suggest_similar(&name, candidates, FUZZY_MATCH_THRESHOLD);
689        let suggestion = if !matches.is_empty() {
690            let quoted: Vec<String> = matches.iter().map(|m| format!("'{}'", m)).collect();
691            Some(format!("Did you mean {}?", quoted.join(", ")))
692        } else {
693            Some("No similar variables found in scope.".to_string())
694        };
695
696        Self::UndefinedReference {
697            reference_type: ReferenceType::Variable,
698            name,
699            location: location.into(),
700            suggestion,
701        }
702    }
703}
704
705impl fmt::Display for ValidationError {
706    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
707        match self {
708            ValidationError::SyntaxError {
709                message,
710                line,
711                column,
712                end_line,
713                end_column,
714            } => {
715                if let (Some(el), Some(ec)) = (end_line, end_column) {
716                    write!(
717                        f,
718                        "Syntax error at {}:{} to {}:{}: {}",
719                        line, column, el, ec, message
720                    )
721                } else {
722                    write!(f, "Syntax error at {}:{}: {}", line, column, message)
723                }
724            }
725            ValidationError::TypeError {
726                message,
727                location,
728                expected_type,
729                found_type,
730                suggestion,
731            } => {
732                write!(f, "Type error at {}: {}", location, message)?;
733                if let (Some(exp), Some(fnd)) = (expected_type, found_type) {
734                    write!(f, " (expected {}, found {})", exp, fnd)?;
735                }
736                if let Some(sug) = suggestion {
737                    write!(f, "\n  Suggestion: {}", sug)?;
738                }
739                Ok(())
740            }
741            ValidationError::UnitError {
742                expected,
743                found,
744                location,
745                suggestion,
746            } => {
747                write!(
748                    f,
749                    "Unit error at {}: incompatible dimensions (expected {:?}, found {:?})",
750                    location, expected, found
751                )?;
752                if let Some(sug) = suggestion {
753                    write!(f, "\n  Suggestion: {}", sug)?;
754                }
755                Ok(())
756            }
757            ValidationError::ScopeError {
758                variable,
759                available_in,
760                location,
761                suggestion,
762            } => {
763                write!(
764                    f,
765                    "Scope error at {}: variable '{}' not in scope",
766                    location, variable
767                )?;
768                if !available_in.is_empty() {
769                    write!(f, "\n  Available in: {}", available_in.join(", "))?;
770                }
771                if let Some(sug) = suggestion {
772                    write!(f, "\n  Suggestion: {}", sug)?;
773                }
774                Ok(())
775            }
776            ValidationError::DeterminismError { message, hint } => {
777                write!(f, "Determinism error: {}", message)?;
778                write!(f, "\n  Hint: {}", hint)
779            }
780            ValidationError::UndefinedReference {
781                reference_type,
782                name,
783                location,
784                suggestion,
785            } => {
786                write!(f, "Undefined {} '{}' at {}", reference_type, name, location)?;
787                if let Some(sug) = suggestion {
788                    write!(f, "\n  Suggestion: {}", sug)?;
789                }
790                Ok(())
791            }
792            ValidationError::DuplicateDeclaration {
793                name,
794                first_location,
795                second_location,
796            } => {
797                write!(
798                    f,
799                    "Duplicate declaration of '{}': first at {}, duplicate at {}",
800                    name, first_location, second_location
801                )
802            }
803            ValidationError::InvalidExpression {
804                message,
805                location,
806                suggestion,
807            } => {
808                write!(f, "Invalid expression at {}: {}", location, message)?;
809                if let Some(sug) = suggestion {
810                    write!(f, "\n  Suggestion: {}", sug)?;
811                }
812                Ok(())
813            }
814        }
815    }
816}
817
818impl std::error::Error for ValidationError {}