rukalang/modules/
error.rs

1use thiserror::Error;
2
3use crate::syntax::{self, ast::SourceSpan};
4
5/// Module resolution error produced while loading imports.
6#[derive(Debug, Error)]
7pub enum ModuleError {
8    /// Imported module failed to parse.
9    #[error("failed to parse module `{module}` from {source}: {error}")]
10    Parse {
11        /// Logical module name requested by the importer.
12        module: String,
13        /// Source file path where parsing failed.
14        source: String,
15        /// Underlying syntax error from parsing.
16        #[source]
17        error: syntax::SyntaxError,
18    },
19    /// Imported module path could not be resolved.
20    #[error(
21        "module `{module}` imported from `{from}` at {source_name}:{span} was not found at {path}"
22    )]
23    NotFound {
24        /// Logical module name requested by the importer.
25        module: String,
26        /// Module containing the import statement.
27        from: String,
28        /// Candidate filesystem path that was attempted.
29        path: String,
30        /// Source file where the import appears.
31        source_name: String,
32        /// Span of the import clause.
33        span: SourceSpan,
34    },
35    /// Cyclic import chain was detected.
36    #[error("cyclic module import detected at {source_name}:{span}: {cycle}")]
37    ImportCycle {
38        /// Human-readable cycle path.
39        cycle: String,
40        /// Source file where cycle detection surfaced.
41        source_name: String,
42        /// Span of the triggering import.
43        span: SourceSpan,
44    },
45    /// The same import name was declared twice in one module.
46    #[error(
47        "duplicate import `{import}` in module `{module}` at {source_name}:{duplicate_span}; first declared at {first_span}"
48    )]
49    DuplicateImport {
50        /// Module containing the duplicate import.
51        module: String,
52        /// Duplicate imported module name.
53        import: String,
54        /// Source file where the duplicate appears.
55        source_name: String,
56        /// Span of the first import declaration.
57        first_span: SourceSpan,
58        /// Span of the second import declaration.
59        duplicate_span: SourceSpan,
60    },
61    /// Browser import violated static-import restrictions.
62    #[error(
63        "web playground only supports static import `{allowed}`; found `{found}` at {source_name}:{span}"
64    )]
65    BrowserStaticImportUnsupported {
66        /// Required static import path.
67        allowed: String,
68        /// Import path found in source.
69        found: String,
70        /// Source file containing the unsupported import.
71        source_name: String,
72        /// Span of the unsupported import.
73        span: SourceSpan,
74    },
75    /// Reading a module file from disk failed.
76    #[error("failed to read module file {path}: {error}")]
77    Io {
78        /// Filesystem path that failed to read.
79        path: String,
80        /// Underlying I/O error.
81        #[source]
82        error: std::io::Error,
83    },
84}
85
86impl ModuleError {
87    /// Return the primary diagnostic line/column when one is available.
88    pub fn diagnostic_line_column(&self) -> Option<(usize, usize)> {
89        match self {
90            Self::NotFound { span, .. } => Some((span.start.line, span.start.column)),
91            Self::ImportCycle { span, .. } => Some((span.start.line, span.start.column)),
92            Self::DuplicateImport { duplicate_span, .. } => {
93                Some((duplicate_span.start.line, duplicate_span.start.column))
94            }
95            Self::BrowserStaticImportUnsupported { span, .. } => {
96                Some((span.start.line, span.start.column))
97            }
98            Self::Parse { .. } | Self::Io { .. } => None,
99        }
100    }
101}