Skip to main content

assura_resolve/
errors.rs

1//! Resolution error types and the resolved file result.
2
3use assura_parser::ast::{SourceFile, Span};
4
5use crate::imports::ResolvedImport;
6use crate::symbols::SymbolTable;
7
8/// An error produced during name resolution.
9#[derive(Debug, Clone)]
10pub struct ResolutionError {
11    pub code: assura_diagnostics::ErrorCode,
12    pub message: String,
13    pub span: Span,
14    /// Optional secondary span (e.g., previous definition site).
15    pub secondary: Option<(Span, String)>,
16    /// Optional "did you mean?" suggestion.
17    pub suggestion: Option<String>,
18}
19
20impl From<ResolutionError> for assura_diagnostics::Diagnostic {
21    fn from(e: ResolutionError) -> Self {
22        let error_span = e.span.clone();
23        let mut d = assura_diagnostics::Diagnostic::error(e.code, e.message, e.span);
24        if let Some((span, label)) = e.secondary {
25            d.secondary.push(assura_diagnostics::SecondaryLabel {
26                span,
27                message: label,
28            });
29        }
30        if let Some(hint) = e.suggestion {
31            d = d.with_suggestion(hint, error_span, String::new());
32        }
33        d
34    }
35}
36
37/// The result of successful name resolution: the original AST plus the
38/// symbol table and resolved imports.
39#[derive(Debug, Clone)]
40pub struct ResolvedFile {
41    pub source: SourceFile,
42    pub symbols: SymbolTable,
43    /// All import declarations with their resolution status.
44    pub imports: Vec<ResolvedImport>,
45    /// Non-fatal warnings (e.g., unused imports). These don't prevent
46    /// resolution from succeeding.
47    pub warnings: Vec<ResolutionError>,
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn resolution_error_to_diagnostic() {
56        let err = ResolutionError {
57            code: "A02001".into(),
58            message: "undefined name `x`".into(),
59            span: 0..5,
60            secondary: None,
61            suggestion: Some("did you mean `y`?".into()),
62        };
63        let diag: assura_diagnostics::Diagnostic = err.into();
64        assert_eq!(diag.code, "A02001");
65        assert!(diag.message.contains("undefined name `x`"));
66    }
67
68    #[test]
69    fn resolution_error_with_secondary() {
70        let err = ResolutionError {
71            code: "A02003".into(),
72            message: "duplicate name `x`".into(),
73            span: 10..15,
74            secondary: Some((0..5, "previously defined here".into())),
75            suggestion: None,
76        };
77        let diag: assura_diagnostics::Diagnostic = err.into();
78        assert_eq!(diag.secondary.len(), 1);
79        assert!(diag.secondary[0].message.contains("previously defined"));
80    }
81}